Version 2.12.0-230.0.dev

Merge commit 'cb2aed626288fd63be35a4f681ffa0698bc26b0b' into 'dev'
diff --git a/benchmarks/FfiBoringssl/dart/FfiBoringssl.dart b/benchmarks/FfiBoringssl/dart/FfiBoringssl.dart
index b4ba0c3..3249be4 100644
--- a/benchmarks/FfiBoringssl/dart/FfiBoringssl.dart
+++ b/benchmarks/FfiBoringssl/dart/FfiBoringssl.dart
@@ -27,7 +27,7 @@
   return result;
 }
 
-Uint8List toUint8List(Bytes bytes, int length) {
+Uint8List toUint8List(Pointer<Bytes> bytes, int length) {
   final result = Uint8List(length);
   final uint8bytes = bytes.asUint8Pointer();
   for (int i = 0; i < length; i++) {
@@ -36,7 +36,7 @@
   return result;
 }
 
-void copyFromUint8ListToTarget(Uint8List source, Data target) {
+void copyFromUint8ListToTarget(Uint8List source, Pointer<Data> target) {
   final int length = source.length;
   final uint8target = target.asUint8Pointer();
   for (int i = 0; i < length; i++) {
@@ -52,7 +52,7 @@
   final Pointer<Bytes> result = calloc<Uint8>(resultSize).cast();
   EVP_DigestFinal(context, result, nullptr);
   EVP_MD_CTX_free(context);
-  final String hash = base64Encode(toUint8List(result.ref, resultSize));
+  final String hash = base64Encode(toUint8List(result, resultSize));
   calloc.free(result);
   return hash;
 }
@@ -85,7 +85,7 @@
   @override
   void setup() {
     data = calloc<Uint8>(L).cast();
-    copyFromUint8ListToTarget(inventData(L), data.ref);
+    copyFromUint8ListToTarget(inventData(L), data);
     hash(data, L, hashAlgorithm);
   }
 
@@ -115,7 +115,7 @@
   void setup() {
     data = inventData(L);
     final Pointer<Data> dataInC = calloc<Uint8>(L).cast();
-    copyFromUint8ListToTarget(data, dataInC.ref);
+    copyFromUint8ListToTarget(data, dataInC);
     hash(dataInC, L, hashAlgorithm);
     calloc.free(dataInC);
   }
@@ -126,7 +126,7 @@
   @override
   void run() {
     final Pointer<Data> dataInC = calloc<Uint8>(L).cast();
-    copyFromUint8ListToTarget(data, dataInC.ref);
+    copyFromUint8ListToTarget(data, dataInC);
     final String result = hash(dataInC, L, hashAlgorithm);
     calloc.free(dataInC);
     if (result != expectedHash) {
diff --git a/benchmarks/FfiBoringssl/dart/types.dart b/benchmarks/FfiBoringssl/dart/types.dart
index 2dd5f76..972555e 100644
--- a/benchmarks/FfiBoringssl/dart/types.dart
+++ b/benchmarks/FfiBoringssl/dart/types.dart
@@ -7,21 +7,25 @@
 import 'dart:ffi';
 
 /// digest algorithm.
-class EVP_MD extends Struct {}
+class EVP_MD extends Opaque {}
 
 /// digest context.
-class EVP_MD_CTX extends Struct {}
+class EVP_MD_CTX extends Opaque {}
 
 /// Type for `void*` used to represent opaque data.
-class Data extends Struct {
-  static Data fromUint8Pointer(Pointer<Uint8> p) => p.cast<Data>().ref;
+class Data extends Opaque {
+  static Pointer<Data> fromUint8Pointer(Pointer<Uint8> p) => p.cast<Data>();
+}
 
-  Pointer<Uint8> asUint8Pointer() => addressOf.cast();
+extension DataPointerAsUint8Pointer on Pointer<Data> {
+  Pointer<Uint8> asUint8Pointer() => cast();
 }
 
 /// Type for `uint8_t*` used to represent byte data.
-class Bytes extends Struct {
-  static Data fromUint8Pointer(Pointer<Uint8> p) => p.cast<Data>().ref;
+class Bytes extends Opaque {
+  static Pointer<Data> fromUint8Pointer(Pointer<Uint8> p) => p.cast<Data>();
+}
 
-  Pointer<Uint8> asUint8Pointer() => addressOf.cast();
+extension BytesPointerAsUint8Pointer on Pointer<Bytes> {
+  Pointer<Uint8> asUint8Pointer() => cast();
 }
diff --git a/benchmarks/FfiBoringssl/dart2/FfiBoringssl.dart b/benchmarks/FfiBoringssl/dart2/FfiBoringssl.dart
index 6bd064a..c749745 100644
--- a/benchmarks/FfiBoringssl/dart2/FfiBoringssl.dart
+++ b/benchmarks/FfiBoringssl/dart2/FfiBoringssl.dart
@@ -29,7 +29,7 @@
   return result;
 }
 
-Uint8List toUint8List(Bytes bytes, int length) {
+Uint8List toUint8List(Pointer<Bytes> bytes, int length) {
   final result = Uint8List(length);
   final uint8bytes = bytes.asUint8Pointer();
   for (int i = 0; i < length; i++) {
@@ -38,7 +38,7 @@
   return result;
 }
 
-void copyFromUint8ListToTarget(Uint8List source, Data target) {
+void copyFromUint8ListToTarget(Uint8List source, Pointer<Data> target) {
   final int length = source.length;
   final uint8target = target.asUint8Pointer();
   for (int i = 0; i < length; i++) {
@@ -54,7 +54,7 @@
   final Pointer<Bytes> result = calloc<Uint8>(resultSize).cast();
   EVP_DigestFinal(context, result, nullptr);
   EVP_MD_CTX_free(context);
-  final String hash = base64Encode(toUint8List(result.ref, resultSize));
+  final String hash = base64Encode(toUint8List(result, resultSize));
   calloc.free(result);
   return hash;
 }
@@ -87,7 +87,7 @@
   @override
   void setup() {
     data = calloc<Uint8>(L).cast();
-    copyFromUint8ListToTarget(inventData(L), data.ref);
+    copyFromUint8ListToTarget(inventData(L), data);
     hash(data, L, hashAlgorithm);
   }
 
@@ -117,7 +117,7 @@
   void setup() {
     data = inventData(L);
     final Pointer<Data> dataInC = calloc<Uint8>(L).cast();
-    copyFromUint8ListToTarget(data, dataInC.ref);
+    copyFromUint8ListToTarget(data, dataInC);
     hash(dataInC, L, hashAlgorithm);
     calloc.free(dataInC);
   }
@@ -128,7 +128,7 @@
   @override
   void run() {
     final Pointer<Data> dataInC = calloc<Uint8>(L).cast();
-    copyFromUint8ListToTarget(data, dataInC.ref);
+    copyFromUint8ListToTarget(data, dataInC);
     final String result = hash(dataInC, L, hashAlgorithm);
     calloc.free(dataInC);
     if (result != expectedHash) {
diff --git a/benchmarks/FfiBoringssl/dart2/types.dart b/benchmarks/FfiBoringssl/dart2/types.dart
index 2c1923b..5ceb6a9 100644
--- a/benchmarks/FfiBoringssl/dart2/types.dart
+++ b/benchmarks/FfiBoringssl/dart2/types.dart
@@ -9,21 +9,25 @@
 import 'dart:ffi';
 
 /// digest algorithm.
-class EVP_MD extends Struct {}
+class EVP_MD extends Opaque {}
 
 /// digest context.
-class EVP_MD_CTX extends Struct {}
+class EVP_MD_CTX extends Opaque {}
 
 /// Type for `void*` used to represent opaque data.
-class Data extends Struct {
-  static Data fromUint8Pointer(Pointer<Uint8> p) => p.cast<Data>().ref;
+class Data extends Opaque {
+  static Pointer<Data> fromUint8Pointer(Pointer<Uint8> p) => p.cast<Data>();
+}
 
-  Pointer<Uint8> asUint8Pointer() => addressOf.cast();
+extension DataPointerAsUint8Pointer on Pointer<Data> {
+  Pointer<Uint8> asUint8Pointer() => cast();
 }
 
 /// Type for `uint8_t*` used to represent byte data.
-class Bytes extends Struct {
-  static Data fromUint8Pointer(Pointer<Uint8> p) => p.cast<Data>().ref;
+class Bytes extends Opaque {
+  static Pointer<Data> fromUint8Pointer(Pointer<Uint8> p) => p.cast<Data>();
+}
 
-  Pointer<Uint8> asUint8Pointer() => addressOf.cast();
+extension BytesPointerAsUint8Pointer on Pointer<Bytes> {
+  Pointer<Uint8> asUint8Pointer() => cast();
 }
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index d2ba523..31b5e0f 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -988,6 +988,26 @@
 }
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeCompilingWithSoundNullSafety =
+    messageCompilingWithSoundNullSafety;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageCompilingWithSoundNullSafety = const MessageCode(
+    "CompilingWithSoundNullSafety",
+    severity: Severity.info,
+    message: r"""Compiling with sound null safety""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeCompilingWithUnsoundNullSafety =
+    messageCompilingWithUnsoundNullSafety;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageCompilingWithUnsoundNullSafety = const MessageCode(
+    "CompilingWithUnsoundNullSafety",
+    severity: Severity.info,
+    message: r"""Compiling with unsound null safety""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Template<
     Message Function(
         String string,
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/severity.dart b/pkg/_fe_analyzer_shared/lib/src/messages/severity.dart
index 0358608..9818cca 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/severity.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/severity.dart
@@ -10,6 +10,7 @@
   ignored,
   internalProblem,
   warning,
+  info,
 }
 
 const Map<String, String> severityEnumNames = const <String, String>{
@@ -18,6 +19,7 @@
   'IGNORED': 'ignored',
   'INTERNAL_PROBLEM': 'internalProblem',
   'WARNING': 'warning',
+  'INFO': 'info',
 };
 
 const Map<String, Severity> severityEnumValues = const <String, Severity>{
@@ -26,6 +28,7 @@
   'IGNORED': Severity.ignored,
   'INTERNAL_PROBLEM': Severity.internalProblem,
   'WARNING': Severity.warning,
+  'INFO': Severity.info,
 };
 
 const Map<Severity, String> severityPrefixes = const <Severity, String>{
@@ -33,6 +36,7 @@
   Severity.internalProblem: "Internal problem",
   Severity.warning: "Warning",
   Severity.context: "Context",
+  Severity.info: "Info",
 };
 
 const Map<Severity, String> severityTexts = const <Severity, String>{
@@ -40,4 +44,5 @@
   Severity.internalProblem: "internal problem",
   Severity.warning: "warning",
   Severity.context: "context",
+  Severity.info: "info",
 };
diff --git a/pkg/analyzer/test/src/diagnostics/field_in_struct_with_initializer_test.dart b/pkg/analyzer/test/src/diagnostics/field_in_struct_with_initializer_test.dart
index da75f22..9c908b5 100644
--- a/pkg/analyzer/test/src/diagnostics/field_in_struct_with_initializer_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/field_in_struct_with_initializer_test.dart
@@ -39,6 +39,7 @@
     await assertNoErrorsInCode(r'''
 import 'dart:ffi';
 class C extends Struct {
+  Pointer p;
   static String str = '';
 }
 ''');
diff --git a/pkg/analyzer/test/src/diagnostics/generic_struct_subclass_test.dart b/pkg/analyzer/test/src/diagnostics/generic_struct_subclass_test.dart
index 8b4aef4..710a812 100644
--- a/pkg/analyzer/test/src/diagnostics/generic_struct_subclass_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/generic_struct_subclass_test.dart
@@ -18,7 +18,9 @@
   test_genericStruct() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
-class S<T> extends Struct {}
+class S<T> extends Struct {
+  Pointer notEmpty;
+}
 ''', [
       error(FfiCode.GENERIC_STRUCT_SUBCLASS, 25, 1),
     ]);
@@ -27,7 +29,9 @@
   test_validStruct() async {
     await assertNoErrorsInCode(r'''
 import 'dart:ffi';
-class S extends Struct {}
+class S extends Struct {
+  Pointer notEmpty;
+}
 ''');
   }
 }
diff --git a/pkg/analyzer/test/src/diagnostics/invalid_field_type_in_struct_test.dart b/pkg/analyzer/test/src/diagnostics/invalid_field_type_in_struct_test.dart
index 06cd7f3..6fec555 100644
--- a/pkg/analyzer/test/src/diagnostics/invalid_field_type_in_struct_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/invalid_field_type_in_struct_test.dart
@@ -15,11 +15,14 @@
 
 @reflectiveTest
 class InvalidFieldTypeInStructTest extends PubPackageResolutionTest {
+  // TODO(https://dartbug.com/44677): Remove Pointer notEmpty field.
   test_instance_invalid() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
 class C extends Struct {
   String str;
+
+  Pointer notEmpty;
 }
 ''', [
       error(FfiCode.INVALID_FIELD_TYPE_IN_STRUCT, 46, 6),
@@ -35,11 +38,14 @@
 ''');
   }
 
+  // TODO(https://dartbug.com/44677): Remove Pointer notEmpty field.
   test_static() async {
     await assertNoErrorsInCode(r'''
 import 'dart:ffi';
 class C extends Struct {
   static String str;
+
+  Pointer notEmpty;
 }
 ''');
   }
diff --git a/pkg/analyzer/test/src/diagnostics/missing_field_type_in_struct_test.dart b/pkg/analyzer/test/src/diagnostics/missing_field_type_in_struct_test.dart
index cfae21f..c5ae82c 100644
--- a/pkg/analyzer/test/src/diagnostics/missing_field_type_in_struct_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/missing_field_type_in_struct_test.dart
@@ -20,6 +20,8 @@
 import 'dart:ffi';
 class C extends Struct {
   var str;
+
+  Pointer notEmpty;
 }
 ''', [
       error(FfiCode.MISSING_FIELD_TYPE_IN_STRUCT, 50, 3),
diff --git a/pkg/analyzer/test/src/diagnostics/subtype_of_ffi_class_test.dart b/pkg/analyzer/test/src/diagnostics/subtype_of_ffi_class_test.dart
index a5aed99..7073a39 100644
--- a/pkg/analyzer/test/src/diagnostics/subtype_of_ffi_class_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/subtype_of_ffi_class_test.dart
@@ -84,7 +84,9 @@
   test_Struct() async {
     await assertNoErrorsInCode(r'''
 import 'dart:ffi';
-class C extends Struct {}
+class C extends Struct {
+  Pointer notEmpty;
+}
 ''');
   }
 
diff --git a/pkg/analyzer/test/src/diagnostics/subtype_of_struct_class_test.dart b/pkg/analyzer/test/src/diagnostics/subtype_of_struct_class_test.dart
index 66f4192..d5ca275 100644
--- a/pkg/analyzer/test/src/diagnostics/subtype_of_struct_class_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/subtype_of_struct_class_test.dart
@@ -21,10 +21,12 @@
   test_extends() async {
     await assertErrorsInCode(r'''
 import 'dart:ffi';
-class S extends Struct {}
+class S extends Struct {
+  Pointer notEmpty;
+}
 class C extends S {}
 ''', [
-      error(FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS, 61, 1),
+      error(FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS, 82, 1),
     ]);
   }
 }
diff --git a/pkg/compiler/lib/src/commandline_options.dart b/pkg/compiler/lib/src/commandline_options.dart
index c93d471..bf9f4a7 100644
--- a/pkg/compiler/lib/src/commandline_options.dart
+++ b/pkg/compiler/lib/src/commandline_options.dart
@@ -127,6 +127,8 @@
 
   static const String conditionalDirectives = '--conditional-directives';
 
+  static const String cfeInvocationModes = '--cfe-invocation-modes';
+
   // The syntax-only level of support for generic methods is included in the
   // 1.50 milestone for Dart. It is not experimental, but also not permanent:
   // a full implementation is expected in the future. Hence, the
diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart
index 05bf1ff..6faa855 100644
--- a/pkg/compiler/lib/src/compiler.dart
+++ b/pkg/compiler/lib/src/compiler.dart
@@ -726,9 +726,15 @@
     reportDiagnosticInternal(message, infos, api.Diagnostic.HINT);
   }
 
+  @override
+  void reportInfo(DiagnosticMessage message,
+      [List<DiagnosticMessage> infos = const <DiagnosticMessage>[]]) {
+    reportDiagnosticInternal(message, infos, api.Diagnostic.INFO);
+  }
+
   @deprecated
   @override
-  void reportInfo(Spannable node, MessageKind messageKind,
+  void reportInfoMessage(Spannable node, MessageKind messageKind,
       [Map<String, String> arguments = const {}]) {
     reportDiagnosticInternal(createMessage(node, messageKind, arguments),
         const <DiagnosticMessage>[], api.Diagnostic.INFO);
diff --git a/pkg/compiler/lib/src/dart2js.dart b/pkg/compiler/lib/src/dart2js.dart
index 5a69e20..c528dfb 100644
--- a/pkg/compiler/lib/src/dart2js.dart
+++ b/pkg/compiler/lib/src/dart2js.dart
@@ -544,6 +544,7 @@
     new OptionHandler(Flags.useOldRti, passThrough),
     new OptionHandler(Flags.testMode, passThrough),
     new OptionHandler('${Flags.dumpSsa}=.+', passThrough),
+    new OptionHandler('${Flags.cfeInvocationModes}=.+', passThrough),
 
     // Experimental features.
     // We don't provide documentation for these yet.
diff --git a/pkg/compiler/lib/src/diagnostics/diagnostic_listener.dart b/pkg/compiler/lib/src/diagnostics/diagnostic_listener.dart
index 554e809..b009473 100644
--- a/pkg/compiler/lib/src/diagnostics/diagnostic_listener.dart
+++ b/pkg/compiler/lib/src/diagnostics/diagnostic_listener.dart
@@ -49,8 +49,11 @@
   void reportHint(DiagnosticMessage message,
       [List<DiagnosticMessage> infos = const <DiagnosticMessage>[]]);
 
+  void reportInfo(DiagnosticMessage message,
+      [List<DiagnosticMessage> infos = const <DiagnosticMessage>[]]);
+
   @deprecated
-  void reportInfo(Spannable node, MessageKind errorCode,
+  void reportInfoMessage(Spannable node, MessageKind errorCode,
       [Map<String, String> arguments = const {}]);
 
   /// Set current element of this reporter to [element]. This is used for
diff --git a/pkg/compiler/lib/src/dump_info.dart b/pkg/compiler/lib/src/dump_info.dart
index 3e82de2..691151c 100644
--- a/pkg/compiler/lib/src/dump_info.dart
+++ b/pkg/compiler/lib/src/dump_info.dart
@@ -581,7 +581,8 @@
         OutputType.dumpInfo)
       ..add(jsonBuffer.toString())
       ..close();
-    compiler.reporter.reportInfo(NO_LOCATION_SPANNABLE, MessageKind.GENERIC, {
+    compiler.reporter
+        .reportInfoMessage(NO_LOCATION_SPANNABLE, MessageKind.GENERIC, {
       'text': "View the dumped .info.json file at "
           "https://dart-lang.github.io/dump-info-visualizer"
     });
@@ -592,7 +593,8 @@
     Sink<List<int>> sink = new BinaryOutputSinkAdapter(compiler.outputProvider
         .createBinarySink(compiler.options.outputUri.resolve(name)));
     dump_info.encode(data, sink);
-    compiler.reporter.reportInfo(NO_LOCATION_SPANNABLE, MessageKind.GENERIC, {
+    compiler.reporter
+        .reportInfoMessage(NO_LOCATION_SPANNABLE, MessageKind.GENERIC, {
       'text': "Use `package:dart2js_info` to parse and process the dumped "
           ".info.data file."
     });
diff --git a/pkg/compiler/lib/src/helpers/helpers.dart b/pkg/compiler/lib/src/helpers/helpers.dart
index bf34252..910485e9 100644
--- a/pkg/compiler/lib/src/helpers/helpers.dart
+++ b/pkg/compiler/lib/src/helpers/helpers.dart
@@ -90,8 +90,8 @@
 
 /// Implementation of [reportHere]
 _reportHere(DiagnosticReporter reporter, Spannable node, String debugMessage) {
-  reporter
-      .reportInfo(node, MessageKind.GENERIC, {'text': 'HERE: $debugMessage'});
+  reporter.reportInfoMessage(
+      node, MessageKind.GENERIC, {'text': 'HERE: $debugMessage'});
 }
 
 /// Set of tracked objects used by [track] and [ifTracked].
diff --git a/pkg/compiler/lib/src/kernel/front_end_adapter.dart b/pkg/compiler/lib/src/kernel/front_end_adapter.dart
index 571f11f..25796fa 100644
--- a/pkg/compiler/lib/src/kernel/front_end_adapter.dart
+++ b/pkg/compiler/lib/src/kernel/front_end_adapter.dart
@@ -116,6 +116,9 @@
     case fe.Severity.warning:
       reporter.reportWarning(mainMessage, infos);
       break;
+    case fe.Severity.info:
+      reporter.reportInfo(mainMessage, infos);
+      break;
     default:
       throw new UnimplementedError('unhandled severity ${message.severity}');
   }
diff --git a/pkg/compiler/lib/src/kernel/loader.dart b/pkg/compiler/lib/src/kernel/loader.dart
index d40a061..a44d5c5 100644
--- a/pkg/compiler/lib/src/kernel/loader.dart
+++ b/pkg/compiler/lib/src/kernel/loader.dart
@@ -170,7 +170,9 @@
             explicitExperimentalFlags: _options.explicitExperimentalFlags,
             nnbdMode: _options.useLegacySubtyping
                 ? fe.NnbdMode.Weak
-                : fe.NnbdMode.Strong);
+                : fe.NnbdMode.Strong,
+            invocationModes:
+                fe.InvocationMode.parseArguments(_options.cfeInvocationModes));
         component = await fe.compile(initializedCompilerState, verbose,
             fileSystem, onDiagnostic, resolvedUri);
         if (component == null) return null;
diff --git a/pkg/compiler/lib/src/options.dart b/pkg/compiler/lib/src/options.dart
index 6e86321..2e6ffe7 100644
--- a/pkg/compiler/lib/src/options.dart
+++ b/pkg/compiler/lib/src/options.dart
@@ -424,6 +424,17 @@
   /// deserialize when using [readCodegenUri].
   int codegenShards;
 
+  /// Arguments passed to the front end about how it is invoked.
+  ///
+  /// This is used to selectively emit certain messages depending on how the
+  /// CFE is invoked. For instance to emit a message about the null safety
+  /// compilation mode when compiling an executable.
+  ///
+  /// See `InvocationMode` in
+  /// `pkg/front_end/lib/src/api_prototype/compiler_options.dart` for all
+  /// possible options.
+  String cfeInvocationModes = '';
+
   // -------------------------------------------------
   // Options for deprecated features
   // -------------------------------------------------
@@ -535,7 +546,9 @@
       .._soundNullSafety = _hasOption(options, Flags.soundNullSafety)
       .._noSoundNullSafety = _hasOption(options, Flags.noSoundNullSafety)
       .._mergeFragmentsThreshold =
-          _extractIntOption(options, '${Flags.mergeFragmentsThreshold}=');
+          _extractIntOption(options, '${Flags.mergeFragmentsThreshold}=')
+      ..cfeInvocationModes =
+          _extractStringOption(options, '${Flags.cfeInvocationModes}=', '');
   }
 
   void validate() {
diff --git a/pkg/compiler/test/end_to_end/diagnostic_reporter_helper.dart b/pkg/compiler/test/end_to_end/diagnostic_reporter_helper.dart
index 63d8596..0058660 100644
--- a/pkg/compiler/test/end_to_end/diagnostic_reporter_helper.dart
+++ b/pkg/compiler/test/end_to_end/diagnostic_reporter_helper.dart
@@ -48,9 +48,15 @@
   }
 
   @override
-  void reportInfo(Spannable node, MessageKind errorCode,
+  void reportInfo(DiagnosticMessage message,
+      [List<DiagnosticMessage> infos = const <DiagnosticMessage>[]]) {
+    reporter.reportInfo(message, infos);
+  }
+
+  @override
+  void reportInfoMessage(Spannable node, MessageKind errorCode,
       [Map<String, String> arguments = const {}]) {
-    reporter.reportInfo(node, errorCode, arguments);
+    reporter.reportInfoMessage(node, errorCode, arguments);
   }
 
   @override
diff --git a/pkg/dart2native/lib/dart2native.dart b/pkg/dart2native/lib/dart2native.dart
index 0139b24..a6d1deb 100644
--- a/pkg/dart2native/lib/dart2native.dart
+++ b/pkg/dart2native/lib/dart2native.dart
@@ -37,8 +37,14 @@
   return Process.run('chmod', ['+x', outputFile]);
 }
 
-Future generateAotKernel(String dart, String genKernel, String platformDill,
-    String sourceFile, String kernelFile, String packages, List<String> defines,
+Future<ProcessResult> generateAotKernel(
+    String dart,
+    String genKernel,
+    String platformDill,
+    String sourceFile,
+    String kernelFile,
+    String packages,
+    List<String> defines,
     {String enableExperiment = '',
     List<String> extraGenKernelOptions = const []}) {
   return Process.run(dart, [
diff --git a/pkg/dart2native/lib/generate.dart b/pkg/dart2native/lib/generate.dart
index eb70dcf..3579ec8 100644
--- a/pkg/dart2native/lib/generate.dart
+++ b/pkg/dart2native/lib/generate.dart
@@ -57,13 +57,24 @@
     final String kernelFile = path.join(tempDir.path, 'kernel.dill');
     final kernelResult = await generateAotKernel(Platform.executable, genKernel,
         productPlatformDill, sourcePath, kernelFile, packages, defines,
-        enableExperiment: enableExperiment);
+        enableExperiment: enableExperiment,
+        extraGenKernelOptions: ['--invocation-modes=compile']);
     if (kernelResult.exitCode != 0) {
-      stderr.writeln(kernelResult.stdout);
-      stderr.writeln(kernelResult.stderr);
+      // We pipe both stdout and stderr to stderr because the CFE doesn't print
+      // errors to stderr. This unfortunately does emit info-only output in
+      // stderr, though.
+      stderr.write(kernelResult.stdout);
+      stderr.write(kernelResult.stderr);
       await stderr.flush();
       throw 'Generating AOT kernel dill failed!';
     }
+    // Pipe info and warnings from the CFE to stdout since the compilation
+    // succeeded. Stderr should be empty but we pipe it to stderr for
+    // completeness.
+    stdout.write(kernelResult.stdout);
+    await stdout.flush();
+    stderr.write(kernelResult.stderr);
+    await stderr.flush();
 
     if (verbose) {
       print('Generating AOT snapshot.');
diff --git a/pkg/dartdev/lib/src/commands/compile.dart b/pkg/dartdev/lib/src/commands/compile.dart
index 6edd82b..29c5b3e 100644
--- a/pkg/dartdev/lib/src/commands/compile.dart
+++ b/pkg/dartdev/lib/src/commands/compile.dart
@@ -98,6 +98,7 @@
           '--libraries-spec=$librariesPath',
           if (argResults.enabledExperiments.isNotEmpty)
             "--enable-experiment=${argResults.enabledExperiments.join(',')}",
+          '--cfe-invocation-modes=compile',
           ...argResults.arguments,
         ],
         packageConfigOverride: null);
diff --git a/pkg/dartdev/test/commands/compile_test.dart b/pkg/dartdev/test/commands/compile_test.dart
index f2f5f31..0921ddd 100644
--- a/pkg/dartdev/test/commands/compile_test.dart
+++ b/pkg/dartdev/test/commands/compile_test.dart
@@ -15,6 +15,10 @@
   group('compile', defineCompileTests, timeout: longTimeout);
 }
 
+const String soundNullSafetyMessage = 'Info: Compiling with sound null safety';
+const String unsoundNullSafetyMessage =
+    'Info: Compiling with unsound null safety';
+
 void defineCompileTests() {
   // *** NOTE ***: These tests *must* be run with the `--use-sdk` option
   // as they depend on a fully built SDK to resolve various snapshot files
@@ -201,4 +205,155 @@
     expect(File(outFile).existsSync(), true,
         reason: 'File not found: $outFile');
   });
+
+  test('Compile exe with error', () {
+    final p = project(mainSrc: '''
+void main() {
+  int? i;
+  i.isEven;
+}
+''');
+    final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
+    final outFile = path.canonicalize(path.join(p.dirPath, 'myexe'));
+
+    var result = p.runSync(
+      [
+        'compile',
+        'exe',
+        '-o',
+        outFile,
+        inFile,
+      ],
+    );
+
+    expect(result.stdout, isEmpty);
+    expect(result.stderr, contains('Error: '));
+    // The CFE doesn't print to stderr, so all output is piped to stderr, even
+    // including info-only output:
+    expect(result.stderr, contains(soundNullSafetyMessage));
+    expect(result.exitCode, compileErrorExitCode);
+    expect(File(outFile).existsSync(), false,
+        reason: 'File not found: $outFile');
+  });
+
+  test('Compile exe with warning', () {
+    final p = project(mainSrc: '''
+void main() {
+  int i = 0;
+  i?.isEven;
+}
+''');
+    final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
+    final outFile = path.canonicalize(path.join(p.dirPath, 'myexe'));
+
+    var result = p.runSync(
+      [
+        'compile',
+        'exe',
+        '-o',
+        outFile,
+        inFile,
+      ],
+    );
+
+    expect(result.stdout, contains('Warning: '));
+    expect(result.stderr, isEmpty);
+    expect(result.exitCode, 0);
+    expect(File(outFile).existsSync(), true,
+        reason: 'File not found: $outFile');
+  });
+
+  test('Compile exe with sound null safety', () {
+    final p = project(mainSrc: '''void main() {}''');
+    final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
+    final outFile = path.canonicalize(path.join(p.dirPath, 'myexe'));
+
+    var result = p.runSync(
+      [
+        'compile',
+        'exe',
+        '-o',
+        outFile,
+        inFile,
+      ],
+    );
+
+    expect(result.stdout, contains(soundNullSafetyMessage));
+    expect(result.stderr, isEmpty);
+    expect(result.exitCode, 0);
+    expect(File(outFile).existsSync(), true,
+        reason: 'File not found: $outFile');
+  });
+
+  test('Compile exe with unsound null safety', () {
+    final p = project(mainSrc: '''
+// @dart=2.9
+void main() {}
+''');
+    final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
+    final outFile = path.canonicalize(path.join(p.dirPath, 'myexe'));
+
+    var result = p.runSync(
+      [
+        'compile',
+        'exe',
+        '-o',
+        outFile,
+        inFile,
+      ],
+    );
+
+    expect(result.stdout, contains(unsoundNullSafetyMessage));
+    expect(result.stderr, isEmpty);
+    expect(result.exitCode, 0);
+    expect(File(outFile).existsSync(), true,
+        reason: 'File not found: $outFile');
+  });
+
+  test('Compile JS with sound null safety', () {
+    final p = project(mainSrc: '''void main() {}''');
+    final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
+    final outFile = path.canonicalize(path.join(p.dirPath, 'myjs'));
+
+    var result = p.runSync(
+      [
+        'compile',
+        'js',
+        '-o',
+        outFile,
+        inFile,
+      ],
+    );
+
+    expect(result.stdout, contains(soundNullSafetyMessage));
+    expect(result.stderr, isEmpty);
+    expect(result.exitCode, 0);
+    expect(File(outFile).existsSync(), true,
+        reason: 'File not found: $outFile');
+  });
+
+  test('Compile JS with unsound null safety', () {
+    final p = project(mainSrc: '''
+// @dart=2.9
+void main() {}
+''');
+    final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
+    final outFile = path.canonicalize(path.join(p.dirPath, 'myjs'));
+
+    var result = p.runSync(
+      [
+        'compile',
+        'js',
+        '-o',
+        outFile,
+        inFile,
+      ],
+    );
+
+    expect(result.stdout, contains(unsoundNullSafetyMessage));
+    expect(result.stderr, isEmpty);
+    expect(result.exitCode, 0);
+    expect(File(outFile).existsSync(), true,
+        reason: 'File not found: $outFile');
+  });
 }
diff --git a/pkg/dev_compiler/lib/src/kernel/expression_compiler_worker.dart b/pkg/dev_compiler/lib/src/kernel/expression_compiler_worker.dart
index d90969a..74b352d 100644
--- a/pkg/dev_compiler/lib/src/kernel/expression_compiler_worker.dart
+++ b/pkg/dev_compiler/lib/src/kernel/expression_compiler_worker.dart
@@ -159,6 +159,7 @@
 
   static List<String> errors = <String>[];
   static List<String> warnings = <String>[];
+  static List<String> infos = <String>[];
 
   /// Create the worker and load the sdk outlines.
   static Future<ExpressionCompilerWorker> create({
@@ -188,7 +189,7 @@
       ..omitPlatform = true
       ..environmentDefines = environmentDefines
       ..explicitExperimentalFlags = explicitExperimentalFlags
-      ..onDiagnostic = _onDiagnosticHandler(errors, warnings)
+      ..onDiagnostic = _onDiagnosticHandler(errors, warnings, infos)
       ..nnbdMode = soundNullSafety ? NnbdMode.Strong : NnbdMode.Weak
       ..verbose = verbose;
     requestStream ??= stdin
@@ -275,6 +276,7 @@
 
     errors.clear();
     warnings.clear();
+    infos.clear();
 
     var incrementalCompiler = IncrementalCompiler.forExpressionCompilationOnly(
         CompilerContext(_processedOptions), component, /*resetTicker*/ false);
@@ -288,6 +290,7 @@
       return {
         'errors': errors,
         'warnings': warnings,
+        'infos': infos,
         'compiledProcedure': null,
         'succeeded': errors.isEmpty,
       };
@@ -332,6 +335,7 @@
     return {
       'errors': errors,
       'warnings': warnings,
+      'infos': infos,
       'compiledProcedure': compiledProcedure,
       'succeeded': errors.isEmpty,
     };
@@ -461,7 +465,7 @@
 }
 
 void Function(DiagnosticMessage) _onDiagnosticHandler(
-        List<String> errors, List<String> warnings) =>
+        List<String> errors, List<String> warnings, List<String> infos) =>
     (DiagnosticMessage message) {
       switch (message.severity) {
         case Severity.error:
@@ -471,6 +475,9 @@
         case Severity.warning:
           warnings.add(message.plainTextFormatted.join('\n'));
           break;
+        case Severity.info:
+          infos.add(message.plainTextFormatted.join('\n'));
+          break;
         case Severity.context:
         case Severity.ignored:
           throw 'Unexpected severity: ${message.severity}';
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart
index a8af750..501fe6d 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart
@@ -255,6 +255,7 @@
               'succeeded': true,
               'errors': isEmpty,
               'warnings': isEmpty,
+              'infos': isEmpty,
               'compiledProcedure': contains('return other;'),
             })
           ]));
@@ -288,6 +289,7 @@
               'succeeded': true,
               'errors': isEmpty,
               'warnings': isEmpty,
+              'infos': isEmpty,
               'compiledProcedure': contains('return formal;'),
             })
           ]));
@@ -320,6 +322,7 @@
               'succeeded': true,
               'errors': isEmpty,
               'warnings': isEmpty,
+              'infos': isEmpty,
               'compiledProcedure': contains('return count;'),
             })
           ]));
@@ -354,6 +357,7 @@
               'succeeded': true,
               'errors': isEmpty,
               'warnings': isEmpty,
+              'infos': isEmpty,
               'compiledProcedure': contains('return ret;'),
             })
           ]));
@@ -387,6 +391,7 @@
               'succeeded': true,
               'errors': isEmpty,
               'warnings': isEmpty,
+              'infos': isEmpty,
               'compiledProcedure':
                   contains('return new test_library.B.new().c().getNumber()'),
             })
diff --git a/pkg/front_end/lib/src/api_prototype/compiler_options.dart b/pkg/front_end/lib/src/api_prototype/compiler_options.dart
index 4b517ca..7639c95 100644
--- a/pkg/front_end/lib/src/api_prototype/compiler_options.dart
+++ b/pkg/front_end/lib/src/api_prototype/compiler_options.dart
@@ -256,6 +256,13 @@
   /// compiling the platform dill.
   bool emitDeps = true;
 
+  /// Set of invocation modes the describe how the compilation is performed.
+  ///
+  /// This used to selectively emit certain messages depending on how the
+  /// CFE is invoked. For instance to emit a message about the null safety
+  /// compilation mode when the modes includes [InvocationMode.compile].
+  Set<InvocationMode> invocationModes = {};
+
   bool isExperimentEnabledByDefault(ExperimentalFlag flag) {
     return flags.isExperimentEnabled(flag,
         defaultExperimentFlagsForTesting: defaultExperimentFlagsForTesting);
@@ -344,6 +351,7 @@
     if (nnbdMode != other.nnbdMode) return false;
     if (currentSdkVersion != other.currentSdkVersion) return false;
     if (emitDeps != other.emitDeps) return false;
+    if (!equalSets(invocationModes, other.invocationModes)) return false;
 
     return true;
   }
@@ -431,3 +439,44 @@
   }
   return flags;
 }
+
+class InvocationMode {
+  /// This mode is used for when the CFE is invoked in order to compile an
+  /// executable.
+  ///
+  /// If used, a message about the null safety compilation mode will be emitted.
+  static const InvocationMode compile = const InvocationMode('compile');
+
+  final String name;
+
+  const InvocationMode(this.name);
+
+  /// Returns the set of information modes from a comma-separated list of
+  /// invocation mode names.
+  static Set<InvocationMode> parseArguments(String arg) {
+    Set<InvocationMode> result = {};
+    for (String name in arg.split(',')) {
+      if (name.isNotEmpty) {
+        InvocationMode mode = fromName(name);
+        if (mode == null) {
+          throw new UnsupportedError("Unknown invocation mode '$name'.");
+        } else {
+          result.add(mode);
+        }
+      }
+    }
+    return result;
+  }
+
+  /// Returns the [InvocationMode] with the given [name].
+  static InvocationMode fromName(String name) {
+    for (InvocationMode invocationMode in values) {
+      if (name == invocationMode.name) {
+        return invocationMode;
+      }
+    }
+    return null;
+  }
+
+  static const List<InvocationMode> values = const [compile];
+}
diff --git a/pkg/front_end/lib/src/api_unstable/dart2js.dart b/pkg/front_end/lib/src/api_unstable/dart2js.dart
index d7fba2e..9a102ac 100644
--- a/pkg/front_end/lib/src/api_unstable/dart2js.dart
+++ b/pkg/front_end/lib/src/api_unstable/dart2js.dart
@@ -18,7 +18,8 @@
 
 import 'package:kernel/target/targets.dart' show Target;
 
-import '../api_prototype/compiler_options.dart' show CompilerOptions;
+import '../api_prototype/compiler_options.dart'
+    show CompilerOptions, InvocationMode;
 
 import '../api_prototype/experimental_flags.dart' show ExperimentalFlag;
 
@@ -40,7 +41,7 @@
 
 import 'compiler_state.dart' show InitializedCompilerState;
 
-import 'util.dart' show equalLists, equalMaps;
+import 'util.dart' show equalLists, equalMaps, equalSets;
 
 export 'package:_fe_analyzer_shared/src/messages/codes.dart'
     show LocatedMessage;
@@ -97,7 +98,11 @@
     show relativizeUri;
 
 export '../api_prototype/compiler_options.dart'
-    show CompilerOptions, parseExperimentalFlags, parseExperimentalArguments;
+    show
+        CompilerOptions,
+        InvocationMode,
+        parseExperimentalFlags,
+        parseExperimentalArguments;
 
 export '../api_prototype/experimental_flags.dart'
     show defaultExperimentalFlags, ExperimentalFlag, isExperimentEnabled;
@@ -138,7 +143,8 @@
     Uri packagesFileUri,
     {Map<ExperimentalFlag, bool> explicitExperimentalFlags,
     bool verify: false,
-    NnbdMode nnbdMode}) {
+    NnbdMode nnbdMode,
+    Set<InvocationMode> invocationModes: const <InvocationMode>{}}) {
   additionalDills.sort((a, b) => a.toString().compareTo(b.toString()));
 
   // We don't check `target` because it doesn't support '==' and each
@@ -151,7 +157,8 @@
       equalMaps(oldState.options.explicitExperimentalFlags,
           explicitExperimentalFlags) &&
       oldState.options.verify == verify &&
-      oldState.options.nnbdMode == nnbdMode) {
+      oldState.options.nnbdMode == nnbdMode &&
+      equalSets(oldState.options.invocationModes, invocationModes)) {
     return oldState;
   }
 
@@ -161,7 +168,8 @@
     ..librariesSpecificationUri = librariesSpecificationUri
     ..packagesFileUri = packagesFileUri
     ..explicitExperimentalFlags = explicitExperimentalFlags
-    ..verify = verify;
+    ..verify = verify
+    ..invocationModes = invocationModes;
   if (nnbdMode != null) options.nnbdMode = nnbdMode;
 
   ProcessedOptions processedOpts = new ProcessedOptions(options: options);
diff --git a/pkg/front_end/lib/src/api_unstable/vm.dart b/pkg/front_end/lib/src/api_unstable/vm.dart
index 6e45bfc..65963f9 100644
--- a/pkg/front_end/lib/src/api_unstable/vm.dart
+++ b/pkg/front_end/lib/src/api_unstable/vm.dart
@@ -8,7 +8,11 @@
 export 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity;
 
 export '../api_prototype/compiler_options.dart'
-    show CompilerOptions, parseExperimentalArguments, parseExperimentalFlags;
+    show
+        CompilerOptions,
+        InvocationMode,
+        parseExperimentalArguments,
+        parseExperimentalFlags;
 
 export '../api_prototype/experimental_flags.dart'
     show defaultExperimentalFlags, ExperimentalFlag;
diff --git a/pkg/front_end/lib/src/base/command_line_options.dart b/pkg/front_end/lib/src/base/command_line_options.dart
index a87824f..862187a 100644
--- a/pkg/front_end/lib/src/base/command_line_options.dart
+++ b/pkg/front_end/lib/src/base/command_line_options.dart
@@ -41,4 +41,6 @@
   static const String verify = "--verify";
   static const String verifySkipPlatform = "--verify-skip-platform";
   static const String warnOnReachabilityCheck = "--warn-on-reachability-check";
+
+  static const String invocationModes = "--invocation-modes";
 }
diff --git a/pkg/front_end/lib/src/base/processed_options.dart b/pkg/front_end/lib/src/base/processed_options.dart
index 97f49f0..ec366d0 100644
--- a/pkg/front_end/lib/src/base/processed_options.dart
+++ b/pkg/front_end/lib/src/base/processed_options.dart
@@ -24,7 +24,7 @@
 import 'package:package_config/package_config.dart';
 
 import '../api_prototype/compiler_options.dart'
-    show CompilerOptions, DiagnosticMessage;
+    show CompilerOptions, InvocationMode, DiagnosticMessage;
 
 import '../api_prototype/experimental_flags.dart' as flags;
 
@@ -45,6 +45,8 @@
         Message,
         messageCantInferPackagesFromManyInputs,
         messageCantInferPackagesFromPackageUri,
+        messageCompilingWithSoundNullSafety,
+        messageCompilingWithUnsoundNullSafety,
         messageInternalProblemProvidedBothCompileSdkAndSdkSummary,
         messageMissingInput,
         noLength,
@@ -267,6 +269,25 @@
     report(message.withoutLocation(), severity);
   }
 
+  /// If `CompilerOptions.invocationModes` contains `InvocationMode.compile`, an
+  /// info message about the null safety compilation mode is emitted.
+  void reportNullSafetyCompilationModeInfo() {
+    if (_raw.invocationModes.contains(InvocationMode.compile)) {
+      switch (nnbdMode) {
+        case NnbdMode.Weak:
+          reportWithoutLocation(messageCompilingWithUnsoundNullSafety,
+              messageCompilingWithUnsoundNullSafety.severity);
+          break;
+        case NnbdMode.Strong:
+          reportWithoutLocation(messageCompilingWithSoundNullSafety,
+              messageCompilingWithSoundNullSafety.severity);
+          break;
+        case NnbdMode.Agnostic:
+          break;
+      }
+    }
+  }
+
   /// Runs various validations checks on the input options. For instance,
   /// if an option is a path to a file, it checks that the file exists.
   Future<bool> validateOptions({bool errorOnMissingInput: true}) async {
diff --git a/pkg/front_end/lib/src/fasta/command_line_reporting.dart b/pkg/front_end/lib/src/fasta/command_line_reporting.dart
index 1247aea..d8f6c95 100644
--- a/pkg/front_end/lib/src/fasta/command_line_reporting.dart
+++ b/pkg/front_end/lib/src/fasta/command_line_reporting.dart
@@ -19,7 +19,7 @@
     show $CARET, $SPACE, $TAB;
 
 import 'package:_fe_analyzer_shared/src/util/colors.dart'
-    show enableColors, green, magenta, red;
+    show enableColors, green, magenta, red, yellow;
 
 import 'package:_fe_analyzer_shared/src/util/relativize.dart'
     show isWindows, relativizeUri;
@@ -73,6 +73,11 @@
           messageText = green(messageText);
           break;
 
+        case Severity.info:
+          messageText = yellow(messageText);
+          break;
+
+        case Severity.ignored:
         default:
           return unhandled("$severity", "format", -1, null);
       }
@@ -145,14 +150,15 @@
     case Severity.error:
     case Severity.internalProblem:
     case Severity.context:
+    case Severity.info:
       return false;
 
     case Severity.warning:
       return hideWarnings;
-
-    default:
-      return unhandled("$severity", "isHidden", -1, null);
+    case Severity.ignored:
+      return true;
   }
+  return unhandled("$severity", "isHidden", -1, null);
 }
 
 /// Are problems of [severity] fatal? That is, should the compiler terminate
@@ -168,12 +174,12 @@
     case Severity.warning:
       return CompilerContext.current.options.throwOnWarningsForDebugging;
 
+    case Severity.info:
+    case Severity.ignored:
     case Severity.context:
       return false;
-
-    default:
-      return unhandled("$severity", "shouldThrowOn", -1, null);
   }
+  return unhandled("$severity", "shouldThrowOn", -1, null);
 }
 
 bool isCompileTimeError(Severity severity) {
@@ -184,6 +190,7 @@
 
     case Severity.warning:
     case Severity.context:
+    case Severity.info:
       return false;
 
     case Severity.ignored:
diff --git a/pkg/front_end/lib/src/kernel_generator_impl.dart b/pkg/front_end/lib/src/kernel_generator_impl.dart
index ae668cb..d4ec55d 100644
--- a/pkg/front_end/lib/src/kernel_generator_impl.dart
+++ b/pkg/front_end/lib/src/kernel_generator_impl.dart
@@ -65,6 +65,7 @@
     bool retainDataForTesting: false,
     bool includeHierarchyAndCoreTypes: false}) async {
   ProcessedOptions options = CompilerContext.current.options;
+  options.reportNullSafetyCompilationModeInfo();
   FileSystem fs = options.fileSystem;
 
   Loader sourceLoader;
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index afaa87e..794f307 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -69,6 +69,8 @@
 CantUseSuperBoundedTypeForInstanceCreation/example: Fail
 ClassInNullAwareReceiver/analyzerCode: Fail
 ColonInPlaceOfIn/example: Fail
+CompilingWithSoundNullSafety/analyzerCode: Fail
+CompilingWithUnsoundNullSafety/analyzerCode: Fail
 ConflictingModifiers/part_wrapped_script1: Fail
 ConflictingModifiers/script1: Fail
 ConflictsWithConstructor/example: Fail
@@ -172,6 +174,8 @@
 DirectiveAfterDeclaration/part_wrapped_script2: Fail
 DirectiveAfterDeclaration/script1: Fail
 DirectiveAfterDeclaration/script2: Fail
+DuplicateDeferred/example: Fail
+DuplicatePrefix/example: Fail
 DuplicatedDeclarationUse/analyzerCode: Fail # No corresponding analyzer code.
 DuplicatedDeclarationUse/part_wrapped_script1: Fail
 DuplicatedDeclarationUse/part_wrapped_script2: Fail
@@ -179,7 +183,6 @@
 DuplicatedDeclarationUse/script2: Fail # Wrong error.
 DuplicatedDefinition/analyzerCode: Fail
 DuplicatedDefinition/example: Fail
-DuplicateDeferred/example: Fail
 DuplicatedExport/part_wrapped_script: Fail # Exporting file in the (now) part.
 DuplicatedExportInType/analyzerCode: Fail
 DuplicatedExportInType/example: Fail
@@ -191,7 +194,6 @@
 DuplicatedName/example: Fail
 DuplicatedNamedArgument/example: Fail
 DuplicatedParameterName/example: Fail
-DuplicatePrefix/example: Fail
 Encoding/analyzerCode: Fail
 EnumConstantSameNameAsEnclosing/example: Fail
 EnumInstantiation/example: Fail
@@ -271,7 +273,6 @@
 ExternalFactoryWithBody/script1: Fail
 ExternalFieldConstructorInitializer/analyzerCode: Fail
 ExternalFieldInitializer/analyzerCode: Fail
-ExtraneousModifier/part_wrapped_script1: Fail
 ExtraneousModifier/part_wrapped_script10: Fail
 ExtraneousModifier/part_wrapped_script11: Fail
 ExtraneousModifier/part_wrapped_script12: Fail
@@ -280,8 +281,9 @@
 ExtraneousModifier/part_wrapped_script17: Fail
 ExtraneousModifier/part_wrapped_script18: Fail
 ExtraneousModifier/part_wrapped_script19: Fail
-ExtraneousModifier/part_wrapped_script2: Fail
+ExtraneousModifier/part_wrapped_script1: Fail
 ExtraneousModifier/part_wrapped_script20: Fail
+ExtraneousModifier/part_wrapped_script2: Fail
 ExtraneousModifier/part_wrapped_script3: Fail
 ExtraneousModifier/part_wrapped_script4: Fail
 ExtraneousModifier/part_wrapped_script5: Fail
@@ -289,7 +291,6 @@
 ExtraneousModifier/part_wrapped_script7: Fail
 ExtraneousModifier/part_wrapped_script8: Fail
 ExtraneousModifier/part_wrapped_script9: Fail
-ExtraneousModifier/script1: Fail
 ExtraneousModifier/script10: Fail
 ExtraneousModifier/script11: Fail
 ExtraneousModifier/script12: Fail
@@ -298,8 +299,9 @@
 ExtraneousModifier/script17: Fail
 ExtraneousModifier/script18: Fail
 ExtraneousModifier/script19: Fail
-ExtraneousModifier/script2: Fail
+ExtraneousModifier/script1: Fail
 ExtraneousModifier/script20: Fail
+ExtraneousModifier/script2: Fail
 ExtraneousModifier/script3: Fail
 ExtraneousModifier/script4: Fail
 ExtraneousModifier/script5: Fail
@@ -396,10 +398,10 @@
 IncompatibleRedirecteeFunctionType/part_wrapped_script6: Fail
 IncompatibleRedirecteeFunctionType/script6: Fail # Triggers multiple errors.
 IncompatibleRedirecteeFunctionTypeWarning/example: Fail
-IncorrectTypeArgumentInferredWarning/example: Fail
 IncorrectTypeArgumentInReturnTypeWarning/example: Fail
 IncorrectTypeArgumentInSupertypeInferredWarning/example: Fail
 IncorrectTypeArgumentInSupertypeWarning/example: Fail
+IncorrectTypeArgumentInferredWarning/example: Fail
 IncorrectTypeArgumentQualifiedInferredWarning/example: Fail
 IncorrectTypeArgumentQualifiedWarning/example: Fail
 IncorrectTypeArgumentWarning/example: Fail
@@ -564,6 +566,9 @@
 NeverValueWarning/analyzerCode: Fail
 NeverValueWarning/example: Fail
 NoFormals/example: Fail
+NoSuchNamedParameter/example: Fail
+NoUnnamedConstructorInObject/analyzerCode: Fail
+NoUnnamedConstructorInObject/example: Fail
 NonAgnosticConstant/analyzerCode: Fail
 NonAgnosticConstant/example: Fail
 NonAsciiIdentifier/expression: Fail
@@ -571,6 +576,7 @@
 NonConstConstructor/example: Fail
 NonConstFactory/example: Fail
 NonInstanceTypeVariableUse/example: Fail
+NonNullAwareSpreadIsNull/analyzerCode: Fail # There's no analyzer code for that error yet.
 NonNullableInNullAware/analyzerCode: Fail
 NonNullableInNullAware/example: Fail
 NonNullableNotAssignedError/analyzerCode: Fail
@@ -580,17 +586,14 @@
 NonNullableOptOutExplicit/example: Fail
 NonNullableOptOutImplicit/analyzerCode: Fail
 NonNullableOptOutImplicit/example: Fail
-NonNullAwareSpreadIsNull/analyzerCode: Fail # There's no analyzer code for that error yet.
 NonPartOfDirectiveInPart/part_wrapped_script1: Fail
 NonPartOfDirectiveInPart/script1: Fail
-NoSuchNamedParameter/example: Fail
 NotAConstantExpression/example: Fail
-NotAnLvalue/example: Fail
 NotAType/example: Fail
+NotAnLvalue/example: Fail
 NotBinaryOperator/analyzerCode: Fail
 NotConstantExpression/example: Fail
-NoUnnamedConstructorInObject/analyzerCode: Fail
-NoUnnamedConstructorInObject/example: Fail
+NullAwareCascadeOutOfOrder/example: Fail
 NullableExpressionCallError/analyzerCode: Fail
 NullableExpressionCallError/example: Fail
 NullableExpressionCallWarning/analyzerCode: Fail
@@ -625,7 +628,6 @@
 NullableTearoffError/example: Fail
 NullableTearoffWarning/analyzerCode: Fail
 NullableTearoffWarning/example: Fail
-NullAwareCascadeOutOfOrder/example: Fail
 OperatorMinusParameterMismatch/example: Fail
 OperatorParameterMismatch0/analyzerCode: Fail
 OperatorParameterMismatch0/example: Fail
@@ -723,13 +725,13 @@
 StrongWithWeakDillLibrary/spelling: Fail
 SuperAsExpression/example: Fail
 SuperAsIdentifier/example: Fail
+SuperNullAware/example: Fail
 SuperclassHasNoDefaultConstructor/example: Fail
 SuperclassHasNoGetter/example: Fail
 SuperclassHasNoMethod/example: Fail
 SuperclassHasNoSetter/example: Fail
 SuperclassMethodArgumentMismatch/analyzerCode: Fail
 SuperclassMethodArgumentMismatch/example: Fail
-SuperNullAware/example: Fail
 SupertypeIsFunction/analyzerCode: Fail
 SupertypeIsFunction/example: Fail
 SupertypeIsIllegal/example: Fail
@@ -754,14 +756,14 @@
 TypeArgumentMismatch/example: Fail
 TypeArgumentsOnTypeVariable/part_wrapped_script1: Fail
 TypeArgumentsOnTypeVariable/script1: Fail
+TypeNotFound/example: Fail
+TypeVariableDuplicatedName/example: Fail
+TypeVariableSameNameAsEnclosing/example: Fail
 TypedefNotFunction/example: Fail
 TypedefNotType/example: Fail # Feature not yet enabled by default.
 TypedefNullableType/analyzerCode: Fail
 TypedefTypeVariableNotConstructor/analyzerCode: Fail # Feature not yet enabled by default.
 TypedefTypeVariableNotConstructor/example: Fail # Feature not yet enabled by default.
-TypeNotFound/example: Fail
-TypeVariableDuplicatedName/example: Fail
-TypeVariableSameNameAsEnclosing/example: Fail
 UnexpectedToken/part_wrapped_script1: Fail
 UnexpectedToken/script1: Fail
 UnmatchedToken/part_wrapped_script1: Fail
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index 390424e..9bb52f9 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -4936,3 +4936,18 @@
   analyzerCode: UNEXPECTED_TOKEN
   script:
     - "late int x;"
+
+CompilingWithSoundNullSafety:
+  template: "Compiling with sound null safety"
+  configuration: nnbd-strong,compile
+  severity: INFO
+  script: |
+    main() {}
+
+CompilingWithUnsoundNullSafety:
+  template: "Compiling with unsound null safety"
+  configuration: nnbd-weak,compile
+  severity: INFO
+  script: |
+    // @dart=2.9
+    main() {}
diff --git a/pkg/front_end/test/fasta/messages_suite.dart b/pkg/front_end/test/fasta/messages_suite.dart
index 9a370f4..8e04b81 100644
--- a/pkg/front_end/test/fasta/messages_suite.dart
+++ b/pkg/front_end/test/fasta/messages_suite.dart
@@ -26,7 +26,7 @@
 import "package:yaml/yaml.dart" show YamlList, YamlMap, YamlNode, loadYamlNode;
 
 import 'package:front_end/src/api_prototype/compiler_options.dart'
-    show CompilerOptions;
+    show CompilerOptions, InvocationMode;
 
 import 'package:front_end/src/api_prototype/experimental_flags.dart'
     show ExperimentalFlag;
@@ -70,8 +70,9 @@
 
 class Configuration {
   final NnbdMode nnbdMode;
+  final Set<InvocationMode> invocationModes;
 
-  const Configuration(this.nnbdMode);
+  const Configuration(this.nnbdMode, this.invocationModes);
 
   CompilerOptions apply(CompilerOptions options) {
     if (nnbdMode != null) {
@@ -80,10 +81,12 @@
     } else {
       options.explicitExperimentalFlags[ExperimentalFlag.nonNullable] = false;
     }
+    options.invocationModes = invocationModes;
     return options;
   }
 
-  static const Configuration defaultConfiguration = const Configuration(null);
+  static const Configuration defaultConfiguration =
+      const Configuration(null, const {});
 }
 
 class MessageTestSuite extends ChainContext {
@@ -345,12 +348,25 @@
             break;
 
           case "configuration":
-            if (value == "nnbd-weak") {
-              configuration = const Configuration(NnbdMode.Weak);
-            } else if (value == "nnbd-strong") {
-              configuration = const Configuration(NnbdMode.Strong);
-            } else {
-              throw new ArgumentError("Unknown configuration '$value'.");
+            if (value is String) {
+              NnbdMode nnbdMode;
+              Set<InvocationMode> invocationModes = {};
+              for (String part in value.split(',')) {
+                if (part.isEmpty) continue;
+                if (part == "nnbd-weak") {
+                  nnbdMode = NnbdMode.Weak;
+                } else if (part == "nnbd-strong") {
+                  nnbdMode = NnbdMode.Strong;
+                } else {
+                  InvocationMode invocationMode = InvocationMode.fromName(part);
+                  if (invocationMode != null) {
+                    invocationModes.add(invocationMode);
+                  } else {
+                    throw new ArgumentError("Unknown configuration '$part'.");
+                  }
+                }
+              }
+              configuration = new Configuration(nnbdMode, invocationModes);
             }
             break;
 
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index cfb9146..f719e36e 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -1014,6 +1014,7 @@
 sdk's
 seconds
 sections
+selectively
 selectors
 semantically
 semver
diff --git a/pkg/front_end/tool/_fasta/command_line.dart b/pkg/front_end/tool/_fasta/command_line.dart
index ed79435..9c275d4 100644
--- a/pkg/front_end/tool/_fasta/command_line.dart
+++ b/pkg/front_end/tool/_fasta/command_line.dart
@@ -198,6 +198,7 @@
   Flags.warnOnReachabilityCheck: const BoolValue(false),
   Flags.linkDependencies: const UriListValue(),
   Flags.noDeps: const BoolValue(false),
+  Flags.invocationModes: const StringValue(),
   "-D": const DefineValue(),
   "-h": const AliasValue(Flags.help),
   "--out": const AliasValue(Flags.output),
@@ -303,6 +304,8 @@
 
   final List<Uri> linkDependencies = options[Flags.linkDependencies] ?? [];
 
+  final String invocationModes = options[Flags.invocationModes] ?? '';
+
   if (nnbdStrongMode && nnbdWeakMode) {
     return throw new CommandLineProblem.deprecated(
         "Can't specify both '${Flags.nnbdStrongMode}' and "
@@ -353,7 +356,8 @@
     ..nnbdMode = nnbdMode
     ..additionalDills = linkDependencies
     ..emitDeps = !noDeps
-    ..warnOnReachabilityCheck = warnOnReachabilityCheck;
+    ..warnOnReachabilityCheck = warnOnReachabilityCheck
+    ..invocationModes = InvocationMode.parseArguments(invocationModes);
 
   if (programName == "compile_platform") {
     if (arguments.length != 5) {
diff --git a/pkg/front_end/tool/_fasta/entry_points.dart b/pkg/front_end/tool/_fasta/entry_points.dart
index 1aa2265..b05374d 100644
--- a/pkg/front_end/tool/_fasta/entry_points.dart
+++ b/pkg/front_end/tool/_fasta/entry_points.dart
@@ -357,6 +357,7 @@
 
   Future<Uri> compile(
       {bool omitPlatform: false, bool supportAdditionalDills: true}) async {
+    c.options.reportNullSafetyCompilationModeInfo();
     KernelTarget kernelTarget =
         await buildOutline(supportAdditionalDills: supportAdditionalDills);
     Uri uri = c.options.output;
diff --git a/pkg/frontend_server/lib/frontend_server.dart b/pkg/frontend_server/lib/frontend_server.dart
index 5b8a4eb..136b9ab 100644
--- a/pkg/frontend_server/lib/frontend_server.dart
+++ b/pkg/frontend_server/lib/frontend_server.dart
@@ -36,6 +36,8 @@
 import 'src/javascript_bundle.dart';
 import 'src/strong_components.dart';
 
+export 'src/to_string_transformer.dart';
+
 ArgParser argParser = ArgParser(allowTrailingOptions: true)
   ..addFlag('train',
       help: 'Run through sample command line to produce snapshot',
@@ -359,6 +361,7 @@
         errors.addAll(message.plainTextFormatted);
         break;
       case Severity.warning:
+      case Severity.info:
         printMessage = true;
         break;
       case Severity.context:
diff --git a/pkg/vm/bin/kernel_service.dart b/pkg/vm/bin/kernel_service.dart
index 3a7d5f4..a0fc1de 100644
--- a/pkg/vm/bin/kernel_service.dart
+++ b/pkg/vm/bin/kernel_service.dart
@@ -126,6 +126,7 @@
           errors.addAll(message.plainTextFormatted);
           break;
         case Severity.warning:
+        case Severity.info:
           printMessage = !suppressWarnings;
           break;
         case Severity.context:
diff --git a/pkg/vm/lib/kernel_front_end.dart b/pkg/vm/lib/kernel_front_end.dart
index bd60735..a1fc45e 100644
--- a/pkg/vm/lib/kernel_front_end.dart
+++ b/pkg/vm/lib/kernel_front_end.dart
@@ -23,6 +23,7 @@
         CompilerContext,
         CompilerOptions,
         CompilerResult,
+        InvocationMode,
         DiagnosticMessage,
         DiagnosticMessageHandler,
         ExperimentalFlag,
@@ -127,6 +128,9 @@
   args.addFlag('track-widget-creation',
       help: 'Run a kernel transformer to track creation locations for widgets.',
       defaultsTo: false);
+  args.addOption('invocation-modes',
+      help: 'Provides information to the front end about how it is invoked.',
+      defaultsTo: '');
 }
 
 /// Create ArgParser and populate it with options consumed by [runCompiler].
@@ -226,7 +230,9 @@
     ..onDiagnostic = (DiagnosticMessage m) {
       errorDetector(m);
     }
-    ..embedSourceText = embedSources;
+    ..embedSourceText = embedSources
+    ..invocationModes =
+        InvocationMode.parseArguments(options['invocation-modes']);
 
   if (nullSafety == null &&
       compilerOptions.isExperimentEnabled(ExperimentalFlag.nonNullable)) {
@@ -496,7 +502,18 @@
 
   void printCompilationMessages() {
     final sortedUris = compilationMessages.keys.toList()
-      ..sort((a, b) => '$a'.compareTo('$b'));
+      ..sort((a, b) {
+        // Sort messages without a corresponding uri before the location based
+        // messages, since these related to the whole compilation.
+        if (a != null && b != null) {
+          return '$a'.compareTo('$b');
+        } else if (a != null) {
+          return 1;
+        } else if (b != null) {
+          return -1;
+        }
+        return 0;
+      });
     for (final Uri sourceUri in sortedUris) {
       for (final DiagnosticMessage message in compilationMessages[sourceUri]) {
         printDiagnosticMessage(message, print);
diff --git a/runtime/bin/ffi_test/ffi_test_functions_generated.cc b/runtime/bin/ffi_test/ffi_test_functions_generated.cc
index a77bff1..65d06a5 100644
--- a/runtime/bin/ffi_test/ffi_test_functions_generated.cc
+++ b/runtime/bin/ffi_test/ffi_test_functions_generated.cc
@@ -34,8 +34,6 @@
   CHECK(((EXPECTED * 0.99) <= (ACTUAL) && (EXPECTED * 1.01) >= (ACTUAL)) ||    \
         ((EXPECTED * 0.99) >= (ACTUAL) && (EXPECTED * 1.01) <= (ACTUAL)))
 
-struct Struct0Bytes {};
-
 struct Struct1ByteInt {
   int8_t a0;
 };
diff --git a/runtime/tests/vm/dart/isolates/dart_api_create_lightweight_isolate_test.dart b/runtime/tests/vm/dart/isolates/dart_api_create_lightweight_isolate_test.dart
index 25e0199..ecc33a1 100644
--- a/runtime/tests/vm/dart/isolates/dart_api_create_lightweight_isolate_test.dart
+++ b/runtime/tests/vm/dart/isolates/dart_api_create_lightweight_isolate_test.dart
@@ -22,7 +22,7 @@
     Platform.executableArguments.contains('--enable-isolate-groups');
 final sdkRoot = Platform.script.resolve('../../../../../');
 
-class Isolate extends Struct {}
+class Isolate extends Opaque {}
 
 abstract class FfiBindings {
   static final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
diff --git a/runtime/tests/vm/dart/isolates/thread_pool_test.dart b/runtime/tests/vm/dart/isolates/thread_pool_test.dart
index 7a301bc0..3d689ca 100644
--- a/runtime/tests/vm/dart/isolates/thread_pool_test.dart
+++ b/runtime/tests/vm/dart/isolates/thread_pool_test.dart
@@ -16,7 +16,7 @@
 // This should be larger than max-new-space-size/tlab-size.
 const int threadCount = 200;
 
-class Isolate extends Struct {}
+class Isolate extends Opaque {}
 
 typedef Dart_CurrentIsolateFT = Pointer<Isolate> Function();
 typedef Dart_CurrentIsolateNFT = Pointer<Isolate> Function();
diff --git a/runtime/tests/vm/dart_2/isolates/dart_api_create_lightweight_isolate_test.dart b/runtime/tests/vm/dart_2/isolates/dart_api_create_lightweight_isolate_test.dart
index d35474e..bcd6270 100644
--- a/runtime/tests/vm/dart_2/isolates/dart_api_create_lightweight_isolate_test.dart
+++ b/runtime/tests/vm/dart_2/isolates/dart_api_create_lightweight_isolate_test.dart
@@ -22,7 +22,7 @@
     Platform.executableArguments.contains('--enable-isolate-groups');
 final sdkRoot = Platform.script.resolve('../../../../../');
 
-class Isolate extends Struct {}
+class Isolate extends Opaque {}
 
 abstract class FfiBindings {
   static final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
diff --git a/runtime/tests/vm/dart_2/isolates/thread_pool_test.dart b/runtime/tests/vm/dart_2/isolates/thread_pool_test.dart
index ff8a7e6..695fda2 100644
--- a/runtime/tests/vm/dart_2/isolates/thread_pool_test.dart
+++ b/runtime/tests/vm/dart_2/isolates/thread_pool_test.dart
@@ -16,7 +16,7 @@
 // This should be larger than max-new-space-size/tlab-size.
 const int threadCount = 200;
 
-class Isolate extends Struct {}
+class Isolate extends Opaque {}
 
 typedef Dart_CurrentIsolateFT = Pointer<Isolate> Function();
 typedef Dart_CurrentIsolateNFT = Pointer<Isolate> Function();
diff --git a/samples/ffi/sqlite/lib/src/database.dart b/samples/ffi/sqlite/lib/src/database.dart
index 59da929..5031027 100644
--- a/samples/ffi/sqlite/lib/src/database.dart
+++ b/samples/ffi/sqlite/lib/src/database.dart
@@ -101,7 +101,7 @@
     int columnCount = bindings.sqlite3_column_count(statement);
     for (int i = 0; i < columnCount; i++) {
       String columnName =
-          bindings.sqlite3_column_name(statement, i).ref.toString();
+          Utf8.fromUtf8(bindings.sqlite3_column_name(statement, i));
       columnIndices[columnName] = i;
     }
 
@@ -109,9 +109,9 @@
   }
 
   SQLiteException _loadError(int errorCode) {
-    String errorMessage = bindings.sqlite3_errmsg(_database).ref.toString();
+    String errorMessage = Utf8.fromUtf8(bindings.sqlite3_errmsg(_database));
     String errorCodeExplanation =
-        bindings.sqlite3_errstr(errorCode).ref.toString();
+        Utf8.fromUtf8(bindings.sqlite3_errstr(errorCode));
     return SQLiteException(
         "$errorMessage (Code $errorCode: $errorCodeExplanation)");
   }
@@ -206,10 +206,8 @@
       dynamicType =
           _typeFromCode(bindings.sqlite3_column_type(_statement, columnIndex));
     } else {
-      dynamicType = _typeFromText(bindings
-          .sqlite3_column_decltype(_statement, columnIndex)
-          .ref
-          .toString());
+      dynamicType = _typeFromText(Utf8.fromUtf8(
+          bindings.sqlite3_column_decltype(_statement, columnIndex)));
     }
 
     switch (dynamicType) {
@@ -244,7 +242,7 @@
   /// Reads column [columnIndex] and converts to [Type.Text] if not text.
   String readColumnByIndexAsText(int columnIndex) {
     _checkIsCurrentRow();
-    return bindings.sqlite3_column_text(_statement, columnIndex).ref.toString();
+    return Utf8.fromUtf8(bindings.sqlite3_column_text(_statement, columnIndex));
   }
 
   void _checkIsCurrentRow() {
diff --git a/samples/ffi/sqlite/test/sqlite_test.dart b/samples/ffi/sqlite/test/sqlite_test.dart
index fedc9e5..ef7666d 100644
--- a/samples/ffi/sqlite/test/sqlite_test.dart
+++ b/samples/ffi/sqlite/test/sqlite_test.dart
@@ -167,7 +167,7 @@
   test("Utf8 unit test", () {
     final String test = 'Hasta Mañana';
     final medium = Utf8.toUtf8(test);
-    expect(test, medium.ref.toString());
+    expect(test, Utf8.fromUtf8(medium));
     calloc.free(medium);
   });
 }
diff --git a/samples_2/ffi/sqlite/lib/src/database.dart b/samples_2/ffi/sqlite/lib/src/database.dart
index 49f229f..687fea8 100644
--- a/samples_2/ffi/sqlite/lib/src/database.dart
+++ b/samples_2/ffi/sqlite/lib/src/database.dart
@@ -103,7 +103,7 @@
     int columnCount = bindings.sqlite3_column_count(statement);
     for (int i = 0; i < columnCount; i++) {
       String columnName =
-          bindings.sqlite3_column_name(statement, i).ref.toString();
+          Utf8.fromUtf8(bindings.sqlite3_column_name(statement, i));
       columnIndices[columnName] = i;
     }
 
@@ -111,12 +111,12 @@
   }
 
   SQLiteException _loadError([int errorCode]) {
-    String errorMessage = bindings.sqlite3_errmsg(_database).ref.toString();
+    String errorMessage = Utf8.fromUtf8(bindings.sqlite3_errmsg(_database));
     if (errorCode == null) {
       return SQLiteException(errorMessage);
     }
     String errorCodeExplanation =
-        bindings.sqlite3_errstr(errorCode).ref.toString();
+        Utf8.fromUtf8(bindings.sqlite3_errstr(errorCode));
     return SQLiteException(
         "$errorMessage (Code $errorCode: $errorCodeExplanation)");
   }
@@ -216,10 +216,8 @@
       dynamicType =
           _typeFromCode(bindings.sqlite3_column_type(_statement, columnIndex));
     } else {
-      dynamicType = _typeFromText(bindings
-          .sqlite3_column_decltype(_statement, columnIndex)
-          .ref
-          .toString());
+      dynamicType = _typeFromText(Utf8.fromUtf8(
+          bindings.sqlite3_column_decltype(_statement, columnIndex)));
     }
 
     switch (dynamicType) {
@@ -255,7 +253,7 @@
   /// Reads column [columnIndex] and converts to [Type.Text] if not text.
   String readColumnByIndexAsText(int columnIndex) {
     _checkIsCurrentRow();
-    return bindings.sqlite3_column_text(_statement, columnIndex).ref.toString();
+    return Utf8.fromUtf8(bindings.sqlite3_column_text(_statement, columnIndex));
   }
 
   void _checkIsCurrentRow() {
diff --git a/samples_2/ffi/sqlite/test/sqlite_test.dart b/samples_2/ffi/sqlite/test/sqlite_test.dart
index f27336bd..dffbe5b 100644
--- a/samples_2/ffi/sqlite/test/sqlite_test.dart
+++ b/samples_2/ffi/sqlite/test/sqlite_test.dart
@@ -171,7 +171,7 @@
   test("Utf8 unit test", () {
     final String test = 'Hasta Mañana';
     final medium = Utf8.toUtf8(test);
-    expect(test, medium.ref.toString());
+    expect(test, Utf8.fromUtf8(medium));
     calloc.free(medium);
   });
 }
diff --git a/tests/ffi/function_structs_by_value_generated_test.dart b/tests/ffi/function_structs_by_value_generated_test.dart
index c8b91cb..ba98841 100644
--- a/tests/ffi/function_structs_by_value_generated_test.dart
+++ b/tests/ffi/function_structs_by_value_generated_test.dart
@@ -106,10 +106,6 @@
   }
 }
 
-class Struct0Bytes extends Struct {
-  String toString() => "()";
-}
-
 class Struct1ByteInt extends Struct {
   @Int8()
   external int a0;
diff --git a/tests/ffi/generator/structs_by_value_tests_configuration.dart b/tests/ffi/generator/structs_by_value_tests_configuration.dart
index 718caea..bc1b8a1 100644
--- a/tests/ffi/generator/structs_by_value_tests_configuration.dart
+++ b/tests/ffi/generator/structs_by_value_tests_configuration.dart
@@ -421,7 +421,6 @@
 ];
 
 final structs = [
-  struct0bytes,
   struct1byteInt,
   struct3bytesInt,
   struct3bytesInt2,
@@ -462,9 +461,6 @@
   structNestedEvenBigger,
 ];
 
-/// Using empty structs is undefined behavior in C.
-final struct0bytes = StructType([]);
-
 final struct1byteInt = StructType([int8]);
 final struct3bytesInt = StructType(List.filled(3, uint8));
 final struct3bytesInt2 = StructType.disambiguate([int16, int8], "2ByteAligned");
diff --git a/tests/ffi/regress_43016_test.dart b/tests/ffi/regress_43016_test.dart
index f1489e0..db3d816 100644
--- a/tests/ffi/regress_43016_test.dart
+++ b/tests/ffi/regress_43016_test.dart
@@ -10,7 +10,10 @@
 
 import 'dylib_utils.dart';
 
-class MyStruct extends Struct {}
+class MyStruct extends Struct {
+  @Int8()
+  external int a;
+}
 
 typedef _c_pass_struct = Int32 Function(Pointer<MyStruct>);
 
diff --git a/tests/ffi/vmspecific_regress_37100_test.dart b/tests/ffi/vmspecific_regress_37100_test.dart
index a819503..ef4c614 100644
--- a/tests/ffi/vmspecific_regress_37100_test.dart
+++ b/tests/ffi/vmspecific_regress_37100_test.dart
@@ -10,7 +10,7 @@
 
 import 'dylib_utils.dart';
 
-class EVP_MD extends Struct {}
+class EVP_MD extends Opaque {}
 
 DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
 
diff --git a/tests/ffi/vmspecific_regress_38993_test.dart b/tests/ffi/vmspecific_regress_38993_test.dart
index 8c9972f..4391b00 100644
--- a/tests/ffi/vmspecific_regress_38993_test.dart
+++ b/tests/ffi/vmspecific_regress_38993_test.dart
@@ -8,6 +8,8 @@
 
 class C extends Struct {
   dynamic x; //# 1: compile-time error
+
+  external Pointer notEmpty;
 }
 
 main() {}
diff --git a/tests/ffi/vmspecific_static_checks_test.dart b/tests/ffi/vmspecific_static_checks_test.dart
index cbce8e6..ff194bd 100644
--- a/tests/ffi/vmspecific_static_checks_test.dart
+++ b/tests/ffi/vmspecific_static_checks_test.dart
@@ -358,24 +358,32 @@
 class TestStruct5 extends Struct {
   @Int64() //# 54: compile-time error
   external double z; //# 54: compile-time error
+
+  external Pointer notEmpty;
 }
 
 // error on annotation not matching up
 class TestStruct6 extends Struct {
   @Void() //# 55: compile-time error
   external double z; //# 55: compile-time error
+
+  external Pointer notEmpty;
 }
 
 // error on annotation not matching up
 class TestStruct7 extends Struct {
   @NativeType() //# 56: compile-time error
   external double z; //# 56: compile-time error
+
+  external Pointer notEmpty;
 }
 
 // error on field initializer on field
 class TestStruct8 extends Struct {
   @Double() //# 57: compile-time error
   double z = 10.0; //# 57: compile-time error
+
+  external Pointer notEmpty;
 }
 
 // error on field initializer in constructor
@@ -383,6 +391,8 @@
   @Double() //# 58: compile-time error
   double z; //# 58: compile-time error
 
+  external Pointer notEmpty;
+
   TestStruct9() : z = 0.0 {} //# 58: compile-time error
 }
 
@@ -395,6 +405,8 @@
 class TestStruct12 extends Struct {
   @Pointer //# 61: compile-time error
   external TestStruct9 struct; //# 61: compile-time error
+
+  external Pointer notEmpty;
 }
 
 class DummyAnnotation {
@@ -500,12 +512,16 @@
 }
 
 class TestStruct1001 extends Struct {
-  Handle handle; //# 1001: compile-time error
+  external Handle handle; //# 1001: compile-time error
+
+  external Pointer notEmpty;
 }
 
 class TestStruct1002 extends Struct {
   @Handle() //# 1002: compile-time error
-  Object handle; //# 1002: compile-time error
+  external Object handle; //# 1002: compile-time error
+
+  external Pointer notEmpty;
 }
 
 class EmptyStruct extends Struct {}
@@ -554,6 +570,8 @@
 
 class HasNestedEmptyStruct extends Struct {
   external EmptyStruct nestedEmptyStruct; //# 1106: compile-time error
+
+  external Pointer notEmpty;
 }
 
 void testAllocateGeneric() {
diff --git a/tests/ffi_2/function_structs_by_value_generated_test.dart b/tests/ffi_2/function_structs_by_value_generated_test.dart
index e00f3fa..6867406 100644
--- a/tests/ffi_2/function_structs_by_value_generated_test.dart
+++ b/tests/ffi_2/function_structs_by_value_generated_test.dart
@@ -106,10 +106,6 @@
   }
 }
 
-class Struct0Bytes extends Struct {
-  String toString() => "()";
-}
-
 class Struct1ByteInt extends Struct {
   @Int8()
   int a0;
diff --git a/tests/ffi_2/generator/structs_by_value_tests_configuration.dart b/tests/ffi_2/generator/structs_by_value_tests_configuration.dart
index 718caea..bc1b8a1 100644
--- a/tests/ffi_2/generator/structs_by_value_tests_configuration.dart
+++ b/tests/ffi_2/generator/structs_by_value_tests_configuration.dart
@@ -421,7 +421,6 @@
 ];
 
 final structs = [
-  struct0bytes,
   struct1byteInt,
   struct3bytesInt,
   struct3bytesInt2,
@@ -462,9 +461,6 @@
   structNestedEvenBigger,
 ];
 
-/// Using empty structs is undefined behavior in C.
-final struct0bytes = StructType([]);
-
 final struct1byteInt = StructType([int8]);
 final struct3bytesInt = StructType(List.filled(3, uint8));
 final struct3bytesInt2 = StructType.disambiguate([int16, int8], "2ByteAligned");
diff --git a/tests/ffi_2/regress_43016_test.dart b/tests/ffi_2/regress_43016_test.dart
index b0ebc0c..997e451 100644
--- a/tests/ffi_2/regress_43016_test.dart
+++ b/tests/ffi_2/regress_43016_test.dart
@@ -10,7 +10,10 @@
 
 import 'dylib_utils.dart';
 
-class MyStruct extends Struct {}
+class MyStruct extends Struct {
+  @Int8()
+  int a;
+}
 
 typedef _c_pass_struct = Int32 Function(Pointer<MyStruct> arg0);
 
diff --git a/tests/ffi_2/vmspecific_regress_37100_test.dart b/tests/ffi_2/vmspecific_regress_37100_test.dart
index a819503..ef4c614 100644
--- a/tests/ffi_2/vmspecific_regress_37100_test.dart
+++ b/tests/ffi_2/vmspecific_regress_37100_test.dart
@@ -10,7 +10,7 @@
 
 import 'dylib_utils.dart';
 
-class EVP_MD extends Struct {}
+class EVP_MD extends Opaque {}
 
 DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
 
diff --git a/tests/ffi_2/vmspecific_regress_38993_test.dart b/tests/ffi_2/vmspecific_regress_38993_test.dart
index 8c9972f..e7c4315 100644
--- a/tests/ffi_2/vmspecific_regress_38993_test.dart
+++ b/tests/ffi_2/vmspecific_regress_38993_test.dart
@@ -8,6 +8,8 @@
 
 class C extends Struct {
   dynamic x; //# 1: compile-time error
+
+  Pointer notEmpty;
 }
 
 main() {}
diff --git a/tests/ffi_2/vmspecific_static_checks_test.dart b/tests/ffi_2/vmspecific_static_checks_test.dart
index f1db86e..8883658 100644
--- a/tests/ffi_2/vmspecific_static_checks_test.dart
+++ b/tests/ffi_2/vmspecific_static_checks_test.dart
@@ -358,24 +358,32 @@
 class TestStruct5 extends Struct {
   @Int64() //# 54: compile-time error
   double z; //# 54: compile-time error
+
+  Pointer notEmpty;
 }
 
 // error on annotation not matching up
 class TestStruct6 extends Struct {
   @Void() //# 55: compile-time error
   double z; //# 55: compile-time error
+
+  Pointer notEmpty;
 }
 
 // error on annotation not matching up
 class TestStruct7 extends Struct {
   @NativeType() //# 56: compile-time error
   double z; //# 56: compile-time error
+
+  Pointer notEmpty;
 }
 
 // error on field initializer on field
 class TestStruct8 extends Struct {
   @Double() //# 57: compile-time error
   double z = 10.0; //# 57: compile-time error
+
+  Pointer notEmpty;
 }
 
 // error on field initializer in constructor
@@ -395,6 +403,8 @@
 class TestStruct12 extends Struct {
   @Pointer //# 61: compile-time error
   TestStruct9 struct; //# 61: compile-time error
+
+  Pointer notEmpty;
 }
 
 class DummyAnnotation {
@@ -501,11 +511,15 @@
 
 class TestStruct1001 extends Struct {
   Handle handle; //# 1001: compile-time error
+
+  Pointer notEmpty;
 }
 
 class TestStruct1002 extends Struct {
   @Handle() //# 1002: compile-time error
   Object handle; //# 1002: compile-time error
+
+  Pointer notEmpty;
 }
 
 class EmptyStruct extends Struct {}
@@ -554,6 +568,8 @@
 
 class HasNestedEmptyStruct extends Struct {
   EmptyStruct nestedEmptyStruct; //# 1106: compile-time error
+
+  Pointer notEmpty;
 }
 
 void testAllocateGeneric() {
diff --git a/tools/VERSION b/tools/VERSION
index 48aba74..e955a98 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 12
 PATCH 0
-PRERELEASE 229
+PRERELEASE 230
 PRERELEASE_PATCH 0
\ No newline at end of file