Skip to content

Commit 9a6f6c5

Browse files
committed
[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 <[email protected]> Reviewed-by: Johnni Winther <[email protected]>
1 parent 846ad95 commit 9a6f6c5

File tree

5 files changed

+309
-4
lines changed

5 files changed

+309
-4
lines changed

pkg/front_end/test/spell_checking_list_code.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ danrubel
405405
daringfireball
406406
dartaotruntime
407407
dartbug
408+
dartdev
408409
dartdoc
409410
dartfix
410411
dartino

pkg/front_end/test/spell_checking_list_common.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,6 +1400,7 @@ groups
14001400
grow
14011401
growable
14021402
guaranteed
1403+
guaranteeing
14031404
guarantees
14041405
guard
14051406
guarding

pkg/frontend_server/lib/src/resident_frontend_server.dart

Lines changed: 172 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,59 @@ class ResidentCompiler {
183183
incrementalCompile: incremental);
184184
}
185185

186+
/// WARNING: [compile] must be called on this compiler to populate the
187+
/// required context in it before [compileExpression] can be called on it.
188+
Future<String> compileExpression(
189+
String expression,
190+
List<String> definitions,
191+
List<String> definitionTypes,
192+
List<String> typeDefinitions,
193+
List<String> typeBounds,
194+
List<String> typeDefaults,
195+
String libraryUri,
196+
String? klass,
197+
String? method,
198+
int offset,
199+
String? scriptUri,
200+
bool isStatic,
201+
) async {
202+
await _compiler.compileExpression(
203+
expression,
204+
definitions,
205+
definitionTypes,
206+
typeDefinitions,
207+
typeBounds,
208+
typeDefaults,
209+
libraryUri,
210+
klass,
211+
method,
212+
offset,
213+
scriptUri,
214+
isStatic,
215+
);
216+
217+
_compilerOutput.clear();
218+
// [incrementalMode] can only ever be [false] if `--aot` was passed in the
219+
// 'compileExpression' request received by the [ResidentFrontendServer],
220+
// which should be impossible.
221+
assert(incrementalMode);
222+
// Force the compiler to produce complete kernel files on each request, even
223+
// when incrementally compiled.
224+
_compiler
225+
..acceptLastDelta()
226+
..resetIncrementalCompiler();
227+
resetStateToWaitingForFirstCompile();
228+
229+
final List<String> errors = _compiler.errors;
230+
final int errorCount = errors.length;
231+
return jsonEncode({
232+
'success': errorCount == 0,
233+
'errorCount': errorCount,
234+
if (errorCount > 0) 'compilerOutputLines': errors,
235+
'kernelBytes': base64Encode(_outputDill.readAsBytesSync()),
236+
});
237+
}
238+
186239
/// Reads the compiler's [outputLines] to keep track of which files
187240
/// need to be tracked. Adds correctly ANSI formatted output to
188241
/// the [_formattedOutput] list.
@@ -266,7 +319,23 @@ class ResidentFrontendServer {
266319
static const String _compileString = 'compile';
267320
static const String _executableString = 'executable';
268321
static const String _packageString = 'packages';
322+
static const String _successString = 'success';
269323
static const String _outputString = 'output-dill';
324+
static const String _compileExpressionString = 'compileExpression';
325+
static const String _libraryUriString = 'libraryUri';
326+
static const String _rootLibraryUriString = 'rootLibraryUri';
327+
static const String _dillExtensionString = '.dill';
328+
static const String _expressionString = 'expression';
329+
static const String _definitionsString = 'definitions';
330+
static const String _definitionTypesString = 'definitionTypes';
331+
static const String _typeDefinitionsString = 'typeDefinitions';
332+
static const String _typeBoundsString = 'typeBounds';
333+
static const String _typeDefaultsString = 'typeDefaults';
334+
static const String _classString = 'class';
335+
static const String _methodString = 'method';
336+
static const String _offsetString = 'offset';
337+
static const String _scriptUriString = 'scriptUri';
338+
static const String _isStaticString = 'isStatic';
270339
static const String _shutdownString = 'shutdown';
271340
static const int _compilerLimit = 3;
272341

@@ -341,7 +410,7 @@ class ResidentFrontendServer {
341410
}
342411

343412
return jsonEncode({
344-
"success": true,
413+
_successString: true,
345414
});
346415
}
347416

@@ -368,14 +437,15 @@ class ResidentFrontendServer {
368437
final ArgResults options = _generateCompilerOptions(
369438
request: request,
370439
outputDillOverride: cachedDillPath,
440+
initializeFromDillPath: cachedDillPath,
371441
);
372442
final ResidentCompiler residentCompiler = _getResidentCompilerForEntrypoint(
373443
canonicalizedExecutablePath,
374444
options,
375445
);
376446
final Map<String, dynamic> response = await residentCompiler.compile();
377447

378-
if (response['success'] != true) {
448+
if (response[_successString] != true) {
379449
return jsonEncode(response);
380450
}
381451

@@ -392,6 +462,90 @@ class ResidentFrontendServer {
392462
return jsonEncode({...response, _outputString: outputDillPath});
393463
}
394464

465+
static Future<String> _handleCompileExpressionRequest(
466+
Map<String, dynamic> request,
467+
) async {
468+
final String canonicalizedLibraryPath;
469+
try {
470+
if ((request[_libraryUriString] as String).startsWith('dart:')) {
471+
// An argument to the [entrypoint] parameter of
472+
// [FrontendCompiler.compile] is mandatory, and
473+
// [canonicalizedLibraryPath] is what we will use as that argument, so
474+
// if the library URI provided in the request begins with 'dart:', then
475+
// we use the URI of the root library of the isolate group in which the
476+
// evaluation is taking place to compute [canonicalizedLibraryPath]
477+
// instead.
478+
canonicalizedLibraryPath = path.canonicalize(
479+
Uri.parse(request[_rootLibraryUriString]).toFilePath(),
480+
);
481+
} else {
482+
canonicalizedLibraryPath = path
483+
.canonicalize(Uri.parse(request[_libraryUriString]).toFilePath());
484+
}
485+
} catch (e) {
486+
return _encodeErrorMessage(
487+
"Request contains invalid '$_libraryUriString' property",
488+
);
489+
}
490+
491+
final String cachedDillPath =
492+
computeCachedDillPath(canonicalizedLibraryPath);
493+
// Make the [ResidentCompiler] output the compiled expression to
494+
// [compiledExpressionDillPath] to prevent it from overwriting the
495+
// cached program dill.
496+
assert(cachedDillPath.endsWith(_dillExtensionString));
497+
final String compiledExpressionDillPath = cachedDillPath.replaceRange(
498+
cachedDillPath.length - _dillExtensionString.length,
499+
null,
500+
'.expr.dill',
501+
);
502+
final ArgResults options = _generateCompilerOptions(
503+
request: request,
504+
outputDillOverride: compiledExpressionDillPath,
505+
initializeFromDillPath: cachedDillPath,
506+
);
507+
508+
final ResidentCompiler residentCompiler =
509+
_getResidentCompilerForEntrypoint(canonicalizedLibraryPath, options);
510+
511+
final String expression = request[_expressionString];
512+
final List<String> definitions =
513+
(request[_definitionsString] as List<dynamic>).cast<String>();
514+
final List<String> definitionTypes =
515+
(request[_definitionTypesString] as List<dynamic>).cast<String>();
516+
final List<String> typeDefinitions =
517+
(request[_typeDefinitionsString] as List<dynamic>).cast<String>();
518+
final List<String> typeBounds =
519+
(request[_typeBoundsString] as List<dynamic>).cast<String>();
520+
final List<String> typeDefaults =
521+
(request[_typeDefaultsString] as List<dynamic>).cast<String>();
522+
final String libraryUri = request[_libraryUriString];
523+
final String? klass = request[_classString];
524+
final String? method = request[_methodString];
525+
final int offset = request[_offsetString];
526+
final String? scriptUri = request[_scriptUriString];
527+
final bool isStatic = request[_isStaticString];
528+
529+
// [residentCompiler.compile] must be called before
530+
// [residentCompiler.compileExpression] can be called. See the
531+
// documentation of [ResidentCompiler.compile] for more information.
532+
await residentCompiler.compile();
533+
return await residentCompiler.compileExpression(
534+
expression,
535+
definitions,
536+
definitionTypes,
537+
typeDefinitions,
538+
typeBounds,
539+
typeDefaults,
540+
libraryUri,
541+
klass,
542+
method,
543+
offset,
544+
scriptUri,
545+
isStatic,
546+
);
547+
}
548+
395549
/// Takes in JSON [input] from the socket and compiles the request,
396550
/// using incremental compilation if possible. Returns a JSON string to be
397551
/// sent back to the client socket containing either an error message or the
@@ -413,6 +567,8 @@ class ResidentFrontendServer {
413567
return _handleReplaceCachedDillRequest(request);
414568
case _compileString:
415569
return _handleCompileRequest(request);
570+
case _compileExpressionString:
571+
return _handleCompileExpressionRequest(request);
416572
case _shutdownString:
417573
return _shutdownJsonResponse;
418574
default:
@@ -428,12 +584,23 @@ class ResidentFrontendServer {
428584
/// The compiled kernel file will be stored at this path, and not at
429585
/// [request['--output-dill']].
430586
required String outputDillOverride,
587+
required String initializeFromDillPath,
431588
}) {
432589
return argParser.parse(<String>[
433590
'--sdk-root=${_sdkUri.toFilePath()}',
434591
if (!(request['aot'] ?? false)) '--incremental',
435592
'--platform=${_platformKernelUri.path}',
436593
'--output-dill=$outputDillOverride',
594+
'--initialize-from-dill=$initializeFromDillPath',
595+
// We can assume that the cached dill is up-to-date when handling
596+
// 'compileExpression' requests because if dartdev was given a source file
597+
// to run, then it must have compiled it with the resident frontend
598+
// compiler, guaranteeing that the cached dill is up-to-date, and if
599+
// dartdev was given a dill file to run, then it must have used the
600+
// resident frontend compiler's 'replaceCachedDill' endpoint to update the
601+
// dill cache.
602+
if (request[_commandString] == _compileExpressionString)
603+
'--assume-initialize-from-dill-up-to-date',
437604
'--target=vm',
438605
'--filesystem-scheme',
439606
'org-dartlang-root',
@@ -459,8 +626,9 @@ class ResidentFrontendServer {
459626
}
460627

461628
/// Encodes the [message] in JSON to be sent over the socket.
462-
static String _encodeErrorMessage(String message) =>
463-
jsonEncode(<String, Object>{"success": false, "errorMessage": message});
629+
static String _encodeErrorMessage(String message) => jsonEncode(
630+
<String, Object>{_successString: false, 'errorMessage': message},
631+
);
464632

465633
/// Used to create compile requests for the ResidentFrontendServer.
466634
/// Returns a JSON string that the resident compiler will be able to

pkg/frontend_server/test/src/resident_frontend_server_test.dart

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,136 @@ void main() async {
611611
});
612612
});
613613

614+
group("Resident Frontend Server: 'compileExpression' command tests: ", () {
615+
late Directory d;
616+
late File executable, outputDill;
617+
618+
setUp(() async {
619+
d = Directory.systemTemp.createTempSync();
620+
executable = new File(path.join(d.path, 'src.dart'))
621+
..createSync()
622+
..writeAsStringSync('void main() {print("hello " "there");}');
623+
outputDill = new File(path.join(d.path, 'src.dart.dill'));
624+
});
625+
626+
tearDown(() async {
627+
d.deleteSync(recursive: true);
628+
ResidentFrontendServer.compilers.clear();
629+
});
630+
631+
test('basic', () async {
632+
final Map<String, dynamic> compileResult = jsonDecode(
633+
await ResidentFrontendServer.handleRequest(
634+
ResidentFrontendServer.createCompileJSON(
635+
executable: executable.path,
636+
outputDill: outputDill.path,
637+
),
638+
),
639+
);
640+
expect(compileResult['success'], true);
641+
642+
final Map<String, dynamic> compileExpressionResult = jsonDecode(
643+
await ResidentFrontendServer.handleRequest(
644+
jsonEncode({
645+
'command': 'compileExpression',
646+
'expression': '101 + 22',
647+
'definitions': [],
648+
'definitionTypes': [],
649+
'typeDefinitions': [],
650+
'typeBounds': [],
651+
'typeDefaults': [],
652+
'libraryUri': executable.uri.toString(),
653+
'offset': 0,
654+
'isStatic': true,
655+
'method': 'main',
656+
}),
657+
),
658+
);
659+
660+
expect(compileExpressionResult['success'], true);
661+
expect(compileExpressionResult['errorCount'], 0);
662+
expect(compileExpressionResult['kernelBytes'], isA<String>());
663+
});
664+
665+
test("when the 'libraryUri' argument begins with 'dart:'", () async {
666+
final Map<String, dynamic> compileResult = jsonDecode(
667+
await ResidentFrontendServer.handleRequest(
668+
ResidentFrontendServer.createCompileJSON(
669+
executable: executable.path,
670+
outputDill: outputDill.path,
671+
),
672+
),
673+
);
674+
expect(compileResult['success'], true);
675+
676+
final Map<String, dynamic> compileExpressionResult = jsonDecode(
677+
await ResidentFrontendServer.handleRequest(
678+
jsonEncode({
679+
'command': 'compileExpression',
680+
'expression': 'this + 5',
681+
'definitions': [],
682+
'definitionTypes': [],
683+
'typeDefinitions': [],
684+
'typeBounds': [],
685+
'typeDefaults': [],
686+
'libraryUri': 'dart:core',
687+
'offset': -1,
688+
'isStatic': false,
689+
'class': 'int',
690+
'rootLibraryUri': executable.uri.toString(),
691+
}),
692+
),
693+
);
694+
695+
expect(compileExpressionResult['success'], true);
696+
expect(compileExpressionResult['errorCount'], 0);
697+
expect(compileExpressionResult['kernelBytes'], isA<String>());
698+
});
699+
700+
test('invalid expression', () async {
701+
final Map<String, dynamic> compileResult = jsonDecode(
702+
await ResidentFrontendServer.handleRequest(
703+
ResidentFrontendServer.createCompileJSON(
704+
executable: executable.path,
705+
outputDill: outputDill.path,
706+
),
707+
),
708+
);
709+
expect(compileResult['success'], true);
710+
711+
final Map<String, dynamic> compileExpressionResult = jsonDecode(
712+
await ResidentFrontendServer.handleRequest(
713+
jsonEncode({
714+
'command': 'compileExpression',
715+
'expression': '101 ++ "abc"',
716+
'definitions': [],
717+
'definitionTypes': [],
718+
'typeDefinitions': [],
719+
'typeBounds': [],
720+
'typeDefaults': [],
721+
'libraryUri': executable.uri.toString(),
722+
'offset': 0,
723+
'isStatic': true,
724+
'method': 'main',
725+
}),
726+
),
727+
);
728+
729+
expect(compileExpressionResult['success'], false);
730+
expect(compileExpressionResult['errorCount'], isPositive);
731+
expect(compileExpressionResult['compilerOutputLines'], [
732+
"org-dartlang-debug:synthetic_debug_expression:1:1: Error: Can't "
733+
'assign to this.\n'
734+
'101 ++ "abc"\n'
735+
'^',
736+
'org-dartlang-debug:synthetic_debug_expression:1:8: Error: Expected '
737+
'one expression, but found additional input.\n'
738+
'101 ++ "abc"\n'
739+
' ^^^^^'
740+
]);
741+
});
742+
});
743+
614744
group('Resident Frontend Server: socket tests: ', () {
615745
late Directory d;
616746
late File serverInfo;

0 commit comments

Comments
 (0)