Add compile-expression service. (#16161)

This registers compile-expression service that is used by dart vm to evaluate expressions when debugging flutter applications.
diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart
index 396aa98..e639f2c 100644
--- a/packages/flutter_tools/lib/src/compile.dart
+++ b/packages/flutter_tools/lib/src/compile.dart
@@ -299,6 +299,28 @@
     return stdoutHandler.compilerOutput.future;
   }
 
+  Future<CompilerOutput> compileExpression(String expression, List<String> definitions,
+      List<String> typeDefinitions, String libraryUri, String klass, bool isStatic) {
+    stdoutHandler.reset();
+
+    // 'compile-expression' should be invoked after compiler has been started,
+    // program was compiled.
+    if (_server == null)
+      return null;
+
+    final String inputKey = new Uuid().generateV4();
+    _server.stdin.writeln('compile-expression $inputKey');
+    _server.stdin.writeln(expression);
+    definitions?.forEach(_server.stdin.writeln);
+    _server.stdin.writeln(inputKey);
+    typeDefinitions?.forEach(_server.stdin.writeln);
+    _server.stdin.writeln(inputKey);
+    _server.stdin.writeln(libraryUri ?? '');
+    _server.stdin.writeln(klass ?? '');
+    _server.stdin.writeln(isStatic ?? false);
+
+    return stdoutHandler.compilerOutput.future;
+  }
 
   /// Should be invoked when results of compilation are accepted by the client.
   ///
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 5265114..f6da760 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -64,16 +64,19 @@
   /// The 'reloadSources' service can be used by other Service Protocol clients
   /// connected to the VM (e.g. Observatory) to request a reload of the source
   /// code of the running application (a.k.a. HotReload).
+  /// The 'compileExpression' service can be used to compile user-provided
+  /// expressions requested during debugging of the application.
   /// This ensures that the reload process follows the normal orchestration of
   /// the Flutter Tools and not just the VM internal service.
-  Future<Null> _connect({ReloadSources reloadSources}) async {
+  Future<Null> _connect({ReloadSources reloadSources, CompileExpression compileExpression}) async {
     if (vmServices != null)
       return;
     vmServices = new List<VMService>(observatoryUris.length);
     for (int i = 0; i < observatoryUris.length; i++) {
       printTrace('Connecting to service protocol: ${observatoryUris[i]}');
       vmServices[i] = await VMService.connect(observatoryUris[i],
-          reloadSources: reloadSources);
+          reloadSources: reloadSources,
+          compileExpression: compileExpression);
       printTrace('Successfully connected to service protocol: ${observatoryUris[i]}');
     }
   }
@@ -622,14 +625,15 @@
   /// If the [reloadSources] parameter is not null the 'reloadSources' service
   /// will be registered
   Future<Null> connectToServiceProtocol({String viewFilter,
-      ReloadSources reloadSources}) async {
+      ReloadSources reloadSources, CompileExpression compileExpression}) async {
     if (!debuggingOptions.debuggingEnabled)
       return new Future<Null>.error('Error the service protocol is not enabled.');
 
     bool viewFound = false;
     for (FlutterDevice device in flutterDevices) {
       device.viewFilter = viewFilter;
-      await device._connect(reloadSources: reloadSources);
+      await device._connect(reloadSources: reloadSources,
+          compileExpression: compileExpression);
       await device.getVMs();
       await device.waitForViews();
       if (device.views == null)
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 77be01f..9eeb8df 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:convert';
 
 import 'package:json_rpc_2/error_code.dart' as rpc_error_code;
 import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
@@ -13,6 +14,7 @@
 import 'base/logger.dart';
 import 'base/utils.dart';
 import 'build_info.dart';
+import 'compile.dart';
 import 'dart/dependencies.dart';
 import 'device.dart';
 import 'globals.dart';
@@ -105,6 +107,23 @@
     }
   }
 
+  Future<String> _compileExpressionService(String isolateId, String expression,
+      List<String> definitions, List<String> typeDefinitions,
+      String libraryUri, String klass, bool isStatic,
+      ) async {
+    for (FlutterDevice device in flutterDevices) {
+      if (device.generator != null) {
+        final CompilerOutput compilerOutput =
+            await device.generator.compileExpression(expression, definitions,
+                typeDefinitions, libraryUri, klass, isStatic);
+        if (compilerOutput.outputFilename != null) {
+          return base64.encode(fs.file(compilerOutput.outputFilename).readAsBytesSync());
+        }
+      }
+    }
+    return null;
+  }
+
   Future<int> attach({
     Completer<DebugConnectionInfo> connectionInfoCompleter,
     Completer<Null> appStartedCompleter,
@@ -112,7 +131,8 @@
   }) async {
     try {
       await connectToServiceProtocol(viewFilter: viewFilter,
-          reloadSources: _reloadSourcesService);
+          reloadSources: _reloadSourcesService,
+          compileExpression: _compileExpressionService);
     } catch (error) {
       printError('Error connecting to the service protocol: $error');
       return 2;
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart
index 88420de..ef6210d 100644
--- a/packages/flutter_tools/lib/src/vmservice.dart
+++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -42,6 +42,16 @@
   bool pause,
 });
 
+typedef Future<String> CompileExpression(
+  String isolateId,
+  String expression,
+  List<String> definitions,
+  List<String> typeDefinitions,
+  String libraryUri,
+  String klass,
+  bool isStatic,
+);
+
 const String _kRecordingType = 'vmservice';
 
 Future<StreamChannel<String>> _defaultOpenChannel(Uri uri) async {
@@ -99,6 +109,7 @@
     this.wsAddress,
     this._requestTimeout,
     ReloadSources reloadSources,
+    CompileExpression compileExpression,
   ) {
     _vm = new VM._empty(this);
     _peer.listen().catchError(_connectionError.completeError);
@@ -138,6 +149,42 @@
         'alias': 'Flutter Tools'
       });
     }
+
+    if (compileExpression != null) {
+      _peer.registerMethod('compileExpression', (rpc.Parameters params) async {
+        final String isolateId = params['isolateId'].asString;
+        if (isolateId is! String || isolateId.isEmpty)
+          throw new rpc.RpcException.invalidParams(
+              'Invalid \'isolateId\': $isolateId');
+        final String expression = params['expression'].asString;
+        if (expression is! String || expression.isEmpty)
+          throw new rpc.RpcException.invalidParams(
+              'Invalid \'expression\': $expression');
+        final List<String> definitions = params['definitions'].asList;
+        final List<String> typeDefinitions = params['typeDefinitions'].asList;
+        final String libraryUri = params['libraryUri'].asString;
+        final String klass = params['klass'] != null ? params['klass'].asString : null;
+        final bool isStatic = params['isStatic'].asBoolOr(false);
+
+        try {
+          final String kernelBytesBase64 = await compileExpression(isolateId,
+              expression, definitions, typeDefinitions, libraryUri, klass,
+              isStatic);
+          return <String, dynamic>{'type': 'Success',
+            'result': <String, dynamic> {'kernelBytes': kernelBytesBase64}};
+        } on rpc.RpcException {
+          rethrow;
+        } catch (e, st) {
+          throw new rpc.RpcException(rpc_error_code.SERVER_ERROR,
+              'Error during expression compilation: $e\n$st');
+        }
+      });
+
+      _peer.sendNotification('_registerService', <String, String>{
+        'service': 'compileExpression',
+        'alias': 'Flutter Tools'
+      });
+    }
   }
 
   /// Enables recording of VMService JSON-rpc activity to the specified base
@@ -180,11 +227,12 @@
     Uri httpUri, {
     Duration requestTimeout = kDefaultRequestTimeout,
     ReloadSources reloadSources,
+    CompileExpression compileExpression,
   }) async {
     final Uri wsUri = httpUri.replace(scheme: 'ws', path: fs.path.join(httpUri.path, 'ws'));
     final StreamChannel<String> channel = await _openChannel(wsUri);
     final rpc.Peer peer = new rpc.Peer.withoutJson(jsonDocument.bind(channel));
-    final VMService service = new VMService._(peer, httpUri, wsUri, requestTimeout, reloadSources);
+    final VMService service = new VMService._(peer, httpUri, wsUri, requestTimeout, reloadSources, compileExpression);
     // This call is to ensure we are able to establish a connection instead of
     // keeping on trucking and failing farther down the process.
     await service._sendRequest('getVersion', const <String, dynamic>{});
diff --git a/packages/flutter_tools/test/compile_test.dart b/packages/flutter_tools/test/compile_test.dart
index 2fe63a8..61f1208 100644
--- a/packages/flutter_tools/test/compile_test.dart
+++ b/packages/flutter_tools/test/compile_test.dart
@@ -216,6 +216,89 @@
       ProcessManager: () => mockProcessManager,
     });
   });
+
+  group('compile expression', ()
+  {
+    ProcessManager mockProcessManager;
+    ResidentCompiler generator;
+    MockProcess mockFrontendServer;
+    MockStdIn mockFrontendServerStdIn;
+    MockStream mockFrontendServerStdErr;
+    StreamController<String> stdErrStreamController;
+
+    setUp(() {
+      generator = new ResidentCompiler('sdkroot');
+      mockProcessManager = new MockProcessManager();
+      mockFrontendServer = new MockProcess();
+      mockFrontendServerStdIn = new MockStdIn();
+      mockFrontendServerStdErr = new MockStream();
+
+      when(mockFrontendServer.stdin).thenReturn(mockFrontendServerStdIn);
+      when(mockFrontendServer.stderr)
+          .thenAnswer((Invocation invocation) => mockFrontendServerStdErr);
+      stdErrStreamController = new StreamController<String>();
+      when(mockFrontendServerStdErr.transform<String>(any))
+          .thenAnswer((Invocation invocation) => stdErrStreamController.stream);
+
+      when(mockProcessManager.canRun(any)).thenReturn(true);
+      when(mockProcessManager.start(any)).thenAnswer(
+              (Invocation invocation) =>
+          new Future<Process>.value(mockFrontendServer)
+      );
+    });
+
+    tearDown(() {
+      verifyNever(mockFrontendServer.exitCode);
+    });
+
+    testUsingContext('fails if not previously compiled', () async {
+      final CompilerOutput result = await generator.compileExpression(
+          '2+2', null, null, null, null, false);
+      expect(result, isNull);
+    });
+
+    testUsingContext('compile single expression', () async {
+      final BufferLogger logger = context[Logger];
+
+      final Completer<List<int>> compileResponseCompleter =
+          new Completer<List<int>>();
+      final Completer<List<int>> compileExpressionResponseCompleter =
+          new Completer<List<int>>();
+
+      when(mockFrontendServer.stdout)
+          .thenAnswer((Invocation invocation) =>
+      new Stream<List<int>>.fromFutures(
+        <Future<List<int>>>[
+          compileResponseCompleter.future,
+          compileExpressionResponseCompleter.future]));
+
+      compileResponseCompleter.complete(new Future<List<int>>.value(utf8.encode(
+        'result abc\nline1\nline2\nabc /path/to/main.dart.dill 0\n'
+      )));
+
+      final CompilerOutput output = await generator.recompile(
+          '/path/to/main.dart', null /* invalidatedFiles */
+      );
+      expect(mockFrontendServerStdIn.getAndClear(),
+          'compile /path/to/main.dart\n');
+      verifyNoMoreInteractions(mockFrontendServerStdIn);
+      expect(logger.errorText,
+          equals('compiler message: line1\ncompiler message: line2\n'));
+      expect(output.outputFilename, equals('/path/to/main.dart.dill'));
+
+      compileExpressionResponseCompleter.complete(new Future<List<int>>.value(utf8.encode(
+          'result def\nline1\nline2\ndef /path/to/main.dart.dill.incremental 0\n'
+      )));
+
+      final CompilerOutput outputExpression = await generator.compileExpression(
+          '2+2', null, null, null, null, false);
+      expect(outputExpression, isNotNull);
+      expect(outputExpression.outputFilename, equals('/path/to/main.dart.dill.incremental'));
+      expect(outputExpression.errorCount, 0);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => mockProcessManager,
+    });
+  });
 }
 
 Future<Null> _recompile(StreamController<List<int>> streamController,