From 9a6f6c5a94961f0d6f037bc3675bbd0650befa03 Mon Sep 17 00:00:00 2001 From: Derek Xu Date: Fri, 20 Dec 2024 09:00:17 -0800 Subject: [PATCH] [ResidentFrontendServer] Add 'compileExpression' endpoint TEST=test cases added to `pkg/frontend_server/test/src/resident_frontend_server_test.dart` Change-Id: I6ffb810d38fc4b13326cb828785aaf6eb6de90f2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/394764 Reviewed-by: Ben Konyi Reviewed-by: Johnni Winther --- .../test/spell_checking_list_code.txt | 1 + .../test/spell_checking_list_common.txt | 1 + .../lib/src/resident_frontend_server.dart | 176 +++++++++++++++++- .../src/resident_frontend_server_test.dart | 130 +++++++++++++ runtime/vm/service.cc | 5 + 5 files changed, 309 insertions(+), 4 deletions(-) diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt index f1e5dc26053..58cf978d8f4 100644 --- a/pkg/front_end/test/spell_checking_list_code.txt +++ b/pkg/front_end/test/spell_checking_list_code.txt @@ -405,6 +405,7 @@ danrubel daringfireball dartaotruntime dartbug +dartdev dartdoc dartfix dartino diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt index 6a8e406d0c6..4f6203e040f 100644 --- a/pkg/front_end/test/spell_checking_list_common.txt +++ b/pkg/front_end/test/spell_checking_list_common.txt @@ -1400,6 +1400,7 @@ groups grow growable guaranteed +guaranteeing guarantees guard guarding diff --git a/pkg/frontend_server/lib/src/resident_frontend_server.dart b/pkg/frontend_server/lib/src/resident_frontend_server.dart index 18b44b8b22e..93c5ffa1383 100644 --- a/pkg/frontend_server/lib/src/resident_frontend_server.dart +++ b/pkg/frontend_server/lib/src/resident_frontend_server.dart @@ -183,6 +183,59 @@ class ResidentCompiler { incrementalCompile: incremental); } + /// WARNING: [compile] must be called on this compiler to populate the + /// required context in it before [compileExpression] can be called on it. + Future compileExpression( + String expression, + List definitions, + List definitionTypes, + List typeDefinitions, + List typeBounds, + List typeDefaults, + String libraryUri, + String? klass, + String? method, + int offset, + String? scriptUri, + bool isStatic, + ) async { + await _compiler.compileExpression( + expression, + definitions, + definitionTypes, + typeDefinitions, + typeBounds, + typeDefaults, + libraryUri, + klass, + method, + offset, + scriptUri, + isStatic, + ); + + _compilerOutput.clear(); + // [incrementalMode] can only ever be [false] if `--aot` was passed in the + // 'compileExpression' request received by the [ResidentFrontendServer], + // which should be impossible. + assert(incrementalMode); + // Force the compiler to produce complete kernel files on each request, even + // when incrementally compiled. + _compiler + ..acceptLastDelta() + ..resetIncrementalCompiler(); + resetStateToWaitingForFirstCompile(); + + final List errors = _compiler.errors; + final int errorCount = errors.length; + return jsonEncode({ + 'success': errorCount == 0, + 'errorCount': errorCount, + if (errorCount > 0) 'compilerOutputLines': errors, + 'kernelBytes': base64Encode(_outputDill.readAsBytesSync()), + }); + } + /// Reads the compiler's [outputLines] to keep track of which files /// need to be tracked. Adds correctly ANSI formatted output to /// the [_formattedOutput] list. @@ -266,7 +319,23 @@ class ResidentFrontendServer { static const String _compileString = 'compile'; static const String _executableString = 'executable'; static const String _packageString = 'packages'; + static const String _successString = 'success'; static const String _outputString = 'output-dill'; + static const String _compileExpressionString = 'compileExpression'; + static const String _libraryUriString = 'libraryUri'; + static const String _rootLibraryUriString = 'rootLibraryUri'; + static const String _dillExtensionString = '.dill'; + static const String _expressionString = 'expression'; + static const String _definitionsString = 'definitions'; + static const String _definitionTypesString = 'definitionTypes'; + static const String _typeDefinitionsString = 'typeDefinitions'; + static const String _typeBoundsString = 'typeBounds'; + static const String _typeDefaultsString = 'typeDefaults'; + static const String _classString = 'class'; + static const String _methodString = 'method'; + static const String _offsetString = 'offset'; + static const String _scriptUriString = 'scriptUri'; + static const String _isStaticString = 'isStatic'; static const String _shutdownString = 'shutdown'; static const int _compilerLimit = 3; @@ -341,7 +410,7 @@ class ResidentFrontendServer { } return jsonEncode({ - "success": true, + _successString: true, }); } @@ -368,6 +437,7 @@ class ResidentFrontendServer { final ArgResults options = _generateCompilerOptions( request: request, outputDillOverride: cachedDillPath, + initializeFromDillPath: cachedDillPath, ); final ResidentCompiler residentCompiler = _getResidentCompilerForEntrypoint( canonicalizedExecutablePath, @@ -375,7 +445,7 @@ class ResidentFrontendServer { ); final Map response = await residentCompiler.compile(); - if (response['success'] != true) { + if (response[_successString] != true) { return jsonEncode(response); } @@ -392,6 +462,90 @@ class ResidentFrontendServer { return jsonEncode({...response, _outputString: outputDillPath}); } + static Future _handleCompileExpressionRequest( + Map request, + ) async { + final String canonicalizedLibraryPath; + try { + if ((request[_libraryUriString] as String).startsWith('dart:')) { + // An argument to the [entrypoint] parameter of + // [FrontendCompiler.compile] is mandatory, and + // [canonicalizedLibraryPath] is what we will use as that argument, so + // if the library URI provided in the request begins with 'dart:', then + // we use the URI of the root library of the isolate group in which the + // evaluation is taking place to compute [canonicalizedLibraryPath] + // instead. + canonicalizedLibraryPath = path.canonicalize( + Uri.parse(request[_rootLibraryUriString]).toFilePath(), + ); + } else { + canonicalizedLibraryPath = path + .canonicalize(Uri.parse(request[_libraryUriString]).toFilePath()); + } + } catch (e) { + return _encodeErrorMessage( + "Request contains invalid '$_libraryUriString' property", + ); + } + + final String cachedDillPath = + computeCachedDillPath(canonicalizedLibraryPath); + // Make the [ResidentCompiler] output the compiled expression to + // [compiledExpressionDillPath] to prevent it from overwriting the + // cached program dill. + assert(cachedDillPath.endsWith(_dillExtensionString)); + final String compiledExpressionDillPath = cachedDillPath.replaceRange( + cachedDillPath.length - _dillExtensionString.length, + null, + '.expr.dill', + ); + final ArgResults options = _generateCompilerOptions( + request: request, + outputDillOverride: compiledExpressionDillPath, + initializeFromDillPath: cachedDillPath, + ); + + final ResidentCompiler residentCompiler = + _getResidentCompilerForEntrypoint(canonicalizedLibraryPath, options); + + final String expression = request[_expressionString]; + final List definitions = + (request[_definitionsString] as List).cast(); + final List definitionTypes = + (request[_definitionTypesString] as List).cast(); + final List typeDefinitions = + (request[_typeDefinitionsString] as List).cast(); + final List typeBounds = + (request[_typeBoundsString] as List).cast(); + final List typeDefaults = + (request[_typeDefaultsString] as List).cast(); + final String libraryUri = request[_libraryUriString]; + final String? klass = request[_classString]; + final String? method = request[_methodString]; + final int offset = request[_offsetString]; + final String? scriptUri = request[_scriptUriString]; + final bool isStatic = request[_isStaticString]; + + // [residentCompiler.compile] must be called before + // [residentCompiler.compileExpression] can be called. See the + // documentation of [ResidentCompiler.compile] for more information. + await residentCompiler.compile(); + return await residentCompiler.compileExpression( + expression, + definitions, + definitionTypes, + typeDefinitions, + typeBounds, + typeDefaults, + libraryUri, + klass, + method, + offset, + scriptUri, + isStatic, + ); + } + /// Takes in JSON [input] from the socket and compiles the request, /// using incremental compilation if possible. Returns a JSON string to be /// sent back to the client socket containing either an error message or the @@ -413,6 +567,8 @@ class ResidentFrontendServer { return _handleReplaceCachedDillRequest(request); case _compileString: return _handleCompileRequest(request); + case _compileExpressionString: + return _handleCompileExpressionRequest(request); case _shutdownString: return _shutdownJsonResponse; default: @@ -428,12 +584,23 @@ class ResidentFrontendServer { /// The compiled kernel file will be stored at this path, and not at /// [request['--output-dill']]. required String outputDillOverride, + required String initializeFromDillPath, }) { return argParser.parse([ '--sdk-root=${_sdkUri.toFilePath()}', if (!(request['aot'] ?? false)) '--incremental', '--platform=${_platformKernelUri.path}', '--output-dill=$outputDillOverride', + '--initialize-from-dill=$initializeFromDillPath', + // We can assume that the cached dill is up-to-date when handling + // 'compileExpression' requests because if dartdev was given a source file + // to run, then it must have compiled it with the resident frontend + // compiler, guaranteeing that the cached dill is up-to-date, and if + // dartdev was given a dill file to run, then it must have used the + // resident frontend compiler's 'replaceCachedDill' endpoint to update the + // dill cache. + if (request[_commandString] == _compileExpressionString) + '--assume-initialize-from-dill-up-to-date', '--target=vm', '--filesystem-scheme', 'org-dartlang-root', @@ -459,8 +626,9 @@ class ResidentFrontendServer { } /// Encodes the [message] in JSON to be sent over the socket. - static String _encodeErrorMessage(String message) => - jsonEncode({"success": false, "errorMessage": message}); + static String _encodeErrorMessage(String message) => jsonEncode( + {_successString: false, 'errorMessage': message}, + ); /// Used to create compile requests for the ResidentFrontendServer. /// Returns a JSON string that the resident compiler will be able to diff --git a/pkg/frontend_server/test/src/resident_frontend_server_test.dart b/pkg/frontend_server/test/src/resident_frontend_server_test.dart index 1b7cf1458e4..6e390c2982f 100644 --- a/pkg/frontend_server/test/src/resident_frontend_server_test.dart +++ b/pkg/frontend_server/test/src/resident_frontend_server_test.dart @@ -611,6 +611,136 @@ void main() async { }); }); + group("Resident Frontend Server: 'compileExpression' command tests: ", () { + late Directory d; + late File executable, outputDill; + + setUp(() async { + d = Directory.systemTemp.createTempSync(); + executable = new File(path.join(d.path, 'src.dart')) + ..createSync() + ..writeAsStringSync('void main() {print("hello " "there");}'); + outputDill = new File(path.join(d.path, 'src.dart.dill')); + }); + + tearDown(() async { + d.deleteSync(recursive: true); + ResidentFrontendServer.compilers.clear(); + }); + + test('basic', () async { + final Map compileResult = jsonDecode( + await ResidentFrontendServer.handleRequest( + ResidentFrontendServer.createCompileJSON( + executable: executable.path, + outputDill: outputDill.path, + ), + ), + ); + expect(compileResult['success'], true); + + final Map compileExpressionResult = jsonDecode( + await ResidentFrontendServer.handleRequest( + jsonEncode({ + 'command': 'compileExpression', + 'expression': '101 + 22', + 'definitions': [], + 'definitionTypes': [], + 'typeDefinitions': [], + 'typeBounds': [], + 'typeDefaults': [], + 'libraryUri': executable.uri.toString(), + 'offset': 0, + 'isStatic': true, + 'method': 'main', + }), + ), + ); + + expect(compileExpressionResult['success'], true); + expect(compileExpressionResult['errorCount'], 0); + expect(compileExpressionResult['kernelBytes'], isA()); + }); + + test("when the 'libraryUri' argument begins with 'dart:'", () async { + final Map compileResult = jsonDecode( + await ResidentFrontendServer.handleRequest( + ResidentFrontendServer.createCompileJSON( + executable: executable.path, + outputDill: outputDill.path, + ), + ), + ); + expect(compileResult['success'], true); + + final Map compileExpressionResult = jsonDecode( + await ResidentFrontendServer.handleRequest( + jsonEncode({ + 'command': 'compileExpression', + 'expression': 'this + 5', + 'definitions': [], + 'definitionTypes': [], + 'typeDefinitions': [], + 'typeBounds': [], + 'typeDefaults': [], + 'libraryUri': 'dart:core', + 'offset': -1, + 'isStatic': false, + 'class': 'int', + 'rootLibraryUri': executable.uri.toString(), + }), + ), + ); + + expect(compileExpressionResult['success'], true); + expect(compileExpressionResult['errorCount'], 0); + expect(compileExpressionResult['kernelBytes'], isA()); + }); + + test('invalid expression', () async { + final Map compileResult = jsonDecode( + await ResidentFrontendServer.handleRequest( + ResidentFrontendServer.createCompileJSON( + executable: executable.path, + outputDill: outputDill.path, + ), + ), + ); + expect(compileResult['success'], true); + + final Map compileExpressionResult = jsonDecode( + await ResidentFrontendServer.handleRequest( + jsonEncode({ + 'command': 'compileExpression', + 'expression': '101 ++ "abc"', + 'definitions': [], + 'definitionTypes': [], + 'typeDefinitions': [], + 'typeBounds': [], + 'typeDefaults': [], + 'libraryUri': executable.uri.toString(), + 'offset': 0, + 'isStatic': true, + 'method': 'main', + }), + ), + ); + + expect(compileExpressionResult['success'], false); + expect(compileExpressionResult['errorCount'], isPositive); + expect(compileExpressionResult['compilerOutputLines'], [ + "org-dartlang-debug:synthetic_debug_expression:1:1: Error: Can't " + 'assign to this.\n' + '101 ++ "abc"\n' + '^', + 'org-dartlang-debug:synthetic_debug_expression:1:8: Error: Expected ' + 'one expression, but found additional input.\n' + '101 ++ "abc"\n' + ' ^^^^^' + ]); + }); + }); + group('Resident Frontend Server: socket tests: ', () { late Directory d; late File serverInfo; diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc index a247e2d5a5a..27f2a3c56a3 100644 --- a/runtime/vm/service.cc +++ b/runtime/vm/service.cc @@ -3275,6 +3275,11 @@ static void BuildExpressionEvaluationScope(Thread* thread, JSONStream* js) { report.AddProperty("scriptUri", script_uri.ToCString()); } report.AddProperty("isStatic", isStatic); + + const Library& root_lib = + Library::Handle(isolate->group()->object_store()->root_library()); + report.AddProperty("rootLibraryUri", + String::Handle(root_lib.url()).ToCString()); } #if !defined(DART_PRECOMPILED_RUNTIME)