Version 2.18.0-156.0.dev

Merge commit '83850ac5fa8cfd58a0dcbfe7103b6455a2a1d523' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6195c9e..2eab9df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -54,6 +54,52 @@
 - Add `connectionState` attribute and `connectionstatechange` listener to
   `RtcPeerConnection`.
 
+#### `dart:io`
+
+- **Breaking Change** [#45630][]: The Dart VM no longer automatically restores
+    the initial terminal settings upon exit. Programs that change the `Stdin`
+    settings `lineMode` and `echoMode` are now responsible for restoring the
+    settings upon program exit. E.g. a program disabling `echoMode` will now
+    need to restore the setting itself and handle exiting by the appropriate
+    signals if desired:
+
+    ```dart
+    import 'dart:io';
+    import 'dart:async';
+
+    main() {
+      bool echoWasEnabled = stdin.echoMode;
+      try {
+        late StreamSubscription subscription;
+        subscription = ProcessSignal.sigint.watch().listen((ProcessSignal signal) {
+          stdin.echoMode = echoWasEnabled;
+          subscription.cancel();
+          Process.killPid(pid, signal); /* Die by the signal. */
+        });
+        stdin.echoMode = false;
+      } finally {
+        stdin.echoMode = echoWasEnabled;
+      }
+    }
+    ```
+
+    This change is needed to fix [#36453][] where the dart programs not caring
+    about the terminal settings can inadverently corrupt the terminal settings
+    when e.g. piping into less.
+
+    Furthermore the `echoMode` setting now only controls the `echo` local mode
+    and no longer sets the `echonl` local mode on POSIX systems (which controls
+    whether newline are echoed even if the regular echo mode is disabled). The
+    `echonl` local mode is usually turned off in common shell environments.
+    Programs that wish to control the `echonl` local mode can use the new
+    `echoNewlineMode` setting.
+
+    The Windows console code pages (if not UTF-8) and ANSI escape code support
+    (if disabled) remain restored when the VM exits.
+
+[#45630]: https://github.com/dart-lang/sdk/issues/45630
+[#36453]: https://github.com/dart-lang/sdk/issues/36453
+
 #### `dart:js_util`
 
 - Added `dartify` and a number of minor helper functions.
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 35444e1..a7e9b00 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -3847,6 +3847,29 @@
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Template<Message Function(String name)>
+    templateExtensionInNullAwareReceiver =
+    const Template<Message Function(String name)>(
+        problemMessageTemplate: r"""The extension '#name' cannot be null.""",
+        correctionMessageTemplate: r"""Try replacing '?.' with '.'""",
+        withArguments: _withArgumentsExtensionInNullAwareReceiver);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(String name)> codeExtensionInNullAwareReceiver =
+    const Code<Message Function(String name)>("ExtensionInNullAwareReceiver",
+        severity: Severity.warning);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsExtensionInNullAwareReceiver(String name) {
+  if (name.isEmpty) throw 'No name provided';
+  name = demangleMixinApplicationName(name);
+  return new Message(codeExtensionInNullAwareReceiver,
+      problemMessage: """The extension '${name}' cannot be null.""",
+      correctionMessage: """Try replacing '?.' with '.'""",
+      arguments: {'name': name});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<Message Function(String name)>
     templateExtensionMemberConflictsWithObjectMember =
     const Template<Message Function(String name)>(
         problemMessageTemplate:
diff --git a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
index 02535d9..2946592 100644
--- a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
@@ -2947,7 +2947,7 @@
       assert(declaration.isStatic || declaration.isTopLevel);
       MemberBuilder memberBuilder = declaration as MemberBuilder;
       return new StaticAccessGenerator(
-          this, token, name, memberBuilder.member, null);
+          this, token, name, memberBuilder.parent, memberBuilder.member, null);
     } else if (declaration is PrefixBuilder) {
       assert(prefix == null);
       return new PrefixUseGenerator(this, token, declaration);
diff --git a/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart b/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart
index 4d0d98c..066a9c7 100644
--- a/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart
@@ -20,6 +20,7 @@
 import '../builder/declaration_builder.dart';
 import '../builder/extension_builder.dart';
 import '../builder/invalid_type_declaration_builder.dart';
+import '../builder/library_builder.dart';
 import '../builder/member_builder.dart';
 import '../builder/named_type_builder.dart';
 import '../builder/nullability_builder.dart';
@@ -1386,12 +1387,18 @@
   final int? typeOffset;
   final bool isNullAware;
 
+  /// The builder for the parent of [readTarget] and [writeTarget]. This is
+  /// either the builder for the enclosing library,  class, or extension.
+  final Builder? parentBuilder;
+
   StaticAccessGenerator(ExpressionGeneratorHelper helper, Token token,
-      this.targetName, this.readTarget, this.writeTarget,
+      this.targetName, this.parentBuilder, this.readTarget, this.writeTarget,
       {this.typeOffset, this.isNullAware: false})
       // ignore: unnecessary_null_comparison
       : assert(targetName != null),
         assert(readTarget != null || writeTarget != null),
+        assert(parentBuilder is DeclarationBuilder ||
+            parentBuilder is LibraryBuilder),
         super(helper, token);
 
   factory StaticAccessGenerator.fromBuilder(
@@ -1402,19 +1409,44 @@
       MemberBuilder? setterBuilder,
       {int? typeOffset,
       bool isNullAware: false}) {
-    return new StaticAccessGenerator(helper, token, targetName,
-        getterBuilder?.readTarget, setterBuilder?.writeTarget,
-        typeOffset: typeOffset, isNullAware: isNullAware);
+    // If both [getterBuilder] and [setterBuilder] exist, they must both be
+    // either top level (potentially from different libraries) or from the same
+    // class/extension.
+    assert(getterBuilder == null ||
+        setterBuilder == null ||
+        (getterBuilder.parent is LibraryBuilder &&
+            setterBuilder.parent is LibraryBuilder) ||
+        getterBuilder.parent == setterBuilder.parent);
+    return new StaticAccessGenerator(
+        helper,
+        token,
+        targetName,
+        getterBuilder?.parent ?? setterBuilder?.parent,
+        getterBuilder?.readTarget,
+        setterBuilder?.writeTarget,
+        typeOffset: typeOffset,
+        isNullAware: isNullAware);
   }
 
   void _reportNonNullableInNullAwareWarningIfNeeded() {
     if (isNullAware && _helper.libraryBuilder.isNonNullableByDefault) {
-      String className = (readTarget ?? writeTarget)!.enclosingClass!.name;
-      _helper.libraryBuilder.addProblem(
-          templateClassInNullAwareReceiver.withArguments(className),
-          typeOffset ?? fileOffset,
-          typeOffset != null ? className.length : noLength,
-          _helper.uri);
+      DeclarationBuilder declarationBuilder =
+          parentBuilder as DeclarationBuilder;
+      if (declarationBuilder.isExtension) {
+        String extensionName = declarationBuilder.name;
+        _helper.libraryBuilder.addProblem(
+            templateExtensionInNullAwareReceiver.withArguments(extensionName),
+            typeOffset ?? fileOffset,
+            typeOffset != null ? extensionName.length : noLength,
+            _helper.uri);
+      } else {
+        String className = declarationBuilder.name;
+        _helper.libraryBuilder.addProblem(
+            templateClassInNullAwareReceiver.withArguments(className),
+            typeOffset ?? fileOffset,
+            typeOffset != null ? className.length : noLength,
+            _helper.uri);
+      }
     }
   }
 
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index d7c85b5..7c33194 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -314,6 +314,7 @@
 ExtensionDeclaresAbstractMember/example: Fail
 ExtensionDeclaresConstructor/example: Fail
 ExtensionDeclaresInstanceField/example: Fail
+ExtensionInNullAwareReceiver/analyzerCode: Fail
 ExtensionMemberConflictsWithObjectMember/analyzerCode: Fail
 ExternalConstructorWithBody/part_wrapped_script1: Fail
 ExternalConstructorWithBody/script1: Fail
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index 7691707..0d6ce5d 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -5223,6 +5223,19 @@
       C?.field;
     }
 
+ExtensionInNullAwareReceiver:
+  problemMessage: "The extension '#name' cannot be null."
+  correctionMessage: "Try replacing '?.' with '.'"
+  severity: WARNING
+  configuration: nnbd-strong
+  script: |
+    extension E on int {
+      static var field;
+    }
+    method() {
+      E?.field;
+    }
+
 NonNullableNotAssignedError:
   problemMessage: "Non-nullable variable '#name' must be assigned before it can be used."
   configuration: nnbd-strong
diff --git a/pkg/front_end/test/fasta/generator_to_string_test.dart b/pkg/front_end/test/fasta/generator_to_string_test.dart
index c35e622..2d4703b 100644
--- a/pkg/front_end/test/fasta/generator_to_string_test.dart
+++ b/pkg/front_end/test/fasta/generator_to_string_test.dart
@@ -187,7 +187,8 @@
         "StaticAccessGenerator(offset: 4, targetName: foo,"
         " readTarget: $uri::myGetter,"
         " writeTarget: $uri::mySetter)",
-        new StaticAccessGenerator(helper, token, 'foo', getter, setter));
+        new StaticAccessGenerator(
+            helper, token, 'foo', libraryBuilder, getter, setter));
     check(
         "LoadLibraryGenerator(offset: 4,"
         " builder: Instance of 'LoadLibraryBuilder')",
diff --git a/pkg/front_end/testcases/general/issue49127.dart b/pkg/front_end/testcases/general/issue49127.dart
new file mode 100644
index 0000000..b475828
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue49127.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+extension E on int {
+  static String s = "Lily was here";
+}
+
+test() {
+  E?.s;
+}
diff --git a/pkg/front_end/testcases/general/issue49127.dart.textual_outline.expect b/pkg/front_end/testcases/general/issue49127.dart.textual_outline.expect
new file mode 100644
index 0000000..06deec8
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue49127.dart.textual_outline.expect
@@ -0,0 +1,5 @@
+extension E on int {
+  static String s = "Lily was here";
+}
+
+test() {}
diff --git a/pkg/front_end/testcases/general/issue49127.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/issue49127.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..06deec8
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue49127.dart.textual_outline_modelled.expect
@@ -0,0 +1,5 @@
+extension E on int {
+  static String s = "Lily was here";
+}
+
+test() {}
diff --git a/pkg/front_end/testcases/general/issue49127.dart.weak.expect b/pkg/front_end/testcases/general/issue49127.dart.weak.expect
new file mode 100644
index 0000000..efafa32
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue49127.dart.weak.expect
@@ -0,0 +1,19 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/issue49127.dart:10:3: Warning: The extension 'E' cannot be null.
+// Try replacing '?.' with '.'
+//   E?.s;
+//   ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension E on core::int {
+  static field s = self::E|s;
+}
+static field core::String E|s = "Lily was here";
+static method test() → dynamic {
+  self::E|s;
+}
diff --git a/pkg/front_end/testcases/general/issue49127.dart.weak.modular.expect b/pkg/front_end/testcases/general/issue49127.dart.weak.modular.expect
new file mode 100644
index 0000000..efafa32
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue49127.dart.weak.modular.expect
@@ -0,0 +1,19 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/issue49127.dart:10:3: Warning: The extension 'E' cannot be null.
+// Try replacing '?.' with '.'
+//   E?.s;
+//   ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension E on core::int {
+  static field s = self::E|s;
+}
+static field core::String E|s = "Lily was here";
+static method test() → dynamic {
+  self::E|s;
+}
diff --git a/pkg/front_end/testcases/general/issue49127.dart.weak.outline.expect b/pkg/front_end/testcases/general/issue49127.dart.weak.outline.expect
new file mode 100644
index 0000000..ca2e74f
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue49127.dart.weak.outline.expect
@@ -0,0 +1,10 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+extension E on core::int {
+  static field s = self::E|s;
+}
+static field core::String E|s;
+static method test() → dynamic
+  ;
diff --git a/pkg/front_end/testcases/general/issue49127.dart.weak.transformed.expect b/pkg/front_end/testcases/general/issue49127.dart.weak.transformed.expect
new file mode 100644
index 0000000..efafa32
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue49127.dart.weak.transformed.expect
@@ -0,0 +1,19 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/issue49127.dart:10:3: Warning: The extension 'E' cannot be null.
+// Try replacing '?.' with '.'
+//   E?.s;
+//   ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension E on core::int {
+  static field s = self::E|s;
+}
+static field core::String E|s = "Lily was here";
+static method test() → dynamic {
+  self::E|s;
+}
diff --git a/runtime/bin/console_posix.cc b/runtime/bin/console_posix.cc
index 17116da..f318ae1 100644
--- a/runtime/bin/console_posix.cc
+++ b/runtime/bin/console_posix.cc
@@ -18,71 +18,10 @@
 namespace dart {
 namespace bin {
 
-class PosixConsole {
- public:
-  static const tcflag_t kInvalidFlag = -1;
-
-  static void Initialize() {
-    SaveMode(STDOUT_FILENO, &stdout_initial_c_lflag_);
-    SaveMode(STDERR_FILENO, &stderr_initial_c_lflag_);
-    SaveMode(STDIN_FILENO, &stdin_initial_c_lflag_);
-  }
-
-  static void Cleanup() {
-    RestoreMode(STDOUT_FILENO, stdout_initial_c_lflag_);
-    RestoreMode(STDERR_FILENO, stderr_initial_c_lflag_);
-    RestoreMode(STDIN_FILENO, stdin_initial_c_lflag_);
-    ClearLFlags();
-  }
-
- private:
-  static tcflag_t stdout_initial_c_lflag_;
-  static tcflag_t stderr_initial_c_lflag_;
-  static tcflag_t stdin_initial_c_lflag_;
-
-  static void ClearLFlags() {
-    stdout_initial_c_lflag_ = kInvalidFlag;
-    stderr_initial_c_lflag_ = kInvalidFlag;
-    stdin_initial_c_lflag_ = kInvalidFlag;
-  }
-
-  static void SaveMode(intptr_t fd, tcflag_t* flag) {
-    ASSERT(flag != NULL);
-    struct termios term;
-    int status = TEMP_FAILURE_RETRY(tcgetattr(fd, &term));
-    if (status != 0) {
-      return;
-    }
-    *flag = term.c_lflag;
-  }
-
-  static void RestoreMode(intptr_t fd, tcflag_t flag) {
-    if (flag == kInvalidFlag) {
-      return;
-    }
-    struct termios term;
-    int status = TEMP_FAILURE_RETRY(tcgetattr(fd, &term));
-    if (status != 0) {
-      return;
-    }
-    term.c_lflag = flag;
-    VOID_TEMP_FAILURE_RETRY(tcsetattr(fd, TCSANOW, &term));
-  }
-
-  DISALLOW_ALLOCATION();
-  DISALLOW_IMPLICIT_CONSTRUCTORS(PosixConsole);
-};
-
-tcflag_t PosixConsole::stdout_initial_c_lflag_ = PosixConsole::kInvalidFlag;
-tcflag_t PosixConsole::stderr_initial_c_lflag_ = PosixConsole::kInvalidFlag;
-tcflag_t PosixConsole::stdin_initial_c_lflag_ = PosixConsole::kInvalidFlag;
-
 void Console::SaveConfig() {
-  PosixConsole::Initialize();
 }
 
 void Console::RestoreConfig() {
-  PosixConsole::Cleanup();
 }
 
 }  // namespace bin
diff --git a/runtime/bin/console_win.cc b/runtime/bin/console_win.cc
index 59e6603..d714aa5 100644
--- a/runtime/bin/console_win.cc
+++ b/runtime/bin/console_win.cc
@@ -105,6 +105,10 @@
     /// to reset the state when we cleanup.
     if ((h != INVALID_HANDLE_VALUE) && GetConsoleMode(h, &mode)) {
       old_mode = mode;
+      // No reason to restore the mode on exit if it was already desirable.
+      if ((mode & flags) == flags) {
+        return kInvalidFlag;
+      }
       if (flags != 0) {
         const DWORD request = mode | flags;
         SetConsoleMode(h, request);
diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc
index ea51935..002cd2c 100644
--- a/runtime/bin/io_natives.cc
+++ b/runtime/bin/io_natives.cc
@@ -176,6 +176,8 @@
   V(Stdin_ReadByte, 1)                                                         \
   V(Stdin_GetEchoMode, 1)                                                      \
   V(Stdin_SetEchoMode, 2)                                                      \
+  V(Stdin_GetEchoNewlineMode, 1)                                               \
+  V(Stdin_SetEchoNewlineMode, 2)                                               \
   V(Stdin_GetLineMode, 1)                                                      \
   V(Stdin_SetLineMode, 2)                                                      \
   V(Stdin_AnsiSupported, 1)                                                    \
diff --git a/runtime/bin/stdio.cc b/runtime/bin/stdio.cc
index d833ba4..07ea704 100644
--- a/runtime/bin/stdio.cc
+++ b/runtime/bin/stdio.cc
@@ -85,6 +85,39 @@
   }
 }
 
+void FUNCTION_NAME(Stdin_GetEchoNewlineMode)(Dart_NativeArguments args) {
+  bool enabled = false;
+  intptr_t fd;
+  if (!GetIntptrArgument(args, 0, &fd)) {
+    return;
+  }
+  if (Stdin::GetEchoNewlineMode(fd, &enabled)) {
+    Dart_SetBooleanReturnValue(args, enabled);
+  } else {
+    Dart_SetReturnValue(args, DartUtils::NewDartOSError());
+  }
+}
+
+void FUNCTION_NAME(Stdin_SetEchoNewlineMode)(Dart_NativeArguments args) {
+  intptr_t fd;
+  if (!GetIntptrArgument(args, 0, &fd)) {
+    return;
+  }
+  bool enabled;
+  Dart_Handle status = Dart_GetNativeBooleanArgument(args, 1, &enabled);
+  if (Dart_IsError(status)) {
+    // The caller is expecting an OSError if something goes wrong.
+    OSError os_error(-1, "Invalid argument", OSError::kUnknown);
+    Dart_SetReturnValue(args, DartUtils::NewDartOSError(&os_error));
+    return;
+  }
+  if (Stdin::SetEchoNewlineMode(fd, enabled)) {
+    Dart_SetReturnValue(args, Dart_True());
+  } else {
+    Dart_SetReturnValue(args, DartUtils::NewDartOSError());
+  }
+}
+
 void FUNCTION_NAME(Stdin_GetLineMode)(Dart_NativeArguments args) {
   bool enabled = false;
   intptr_t fd;
diff --git a/runtime/bin/stdio.h b/runtime/bin/stdio.h
index 3bbc019..e5796d5 100644
--- a/runtime/bin/stdio.h
+++ b/runtime/bin/stdio.h
@@ -20,6 +20,9 @@
   static bool GetEchoMode(intptr_t fd, bool* enabled);
   static bool SetEchoMode(intptr_t fd, bool enabled);
 
+  static bool GetEchoNewlineMode(intptr_t fd, bool* enabled);
+  static bool SetEchoNewlineMode(intptr_t fd, bool enabled);
+
   static bool GetLineMode(intptr_t fd, bool* enabled);
   static bool SetLineMode(intptr_t fd, bool enabled);
 
diff --git a/runtime/bin/stdio_android.cc b/runtime/bin/stdio_android.cc
index 1afb34f..e620e1f 100644
--- a/runtime/bin/stdio_android.cc
+++ b/runtime/bin/stdio_android.cc
@@ -44,9 +44,34 @@
     return false;
   }
   if (enabled) {
-    term.c_lflag |= (ECHO | ECHONL);
+    term.c_lflag |= ECHO;
   } else {
-    term.c_lflag &= ~(ECHO | ECHONL);
+    term.c_lflag &= ~(ECHO);
+  }
+  status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
+  return (status == 0);
+}
+
+bool Stdin::GetEchoNewlineMode(intptr_t fd, bool* enabled) {
+  struct termios term;
+  int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
+  if (status != 0) {
+    return false;
+  }
+  *enabled = ((term.c_lflag & ECHONL) != 0);
+  return true;
+}
+
+bool Stdin::SetEchoNewlineMode(intptr_t fd, bool enabled) {
+  struct termios term;
+  int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
+  if (status != 0) {
+    return false;
+  }
+  if (enabled) {
+    term.c_lflag |= ECHONL;
+  } else {
+    term.c_lflag &= ~(ECHONL);
   }
   status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
   return (status == 0);
diff --git a/runtime/bin/stdio_fuchsia.cc b/runtime/bin/stdio_fuchsia.cc
index 2f91a9c..4d84b48 100644
--- a/runtime/bin/stdio_fuchsia.cc
+++ b/runtime/bin/stdio_fuchsia.cc
@@ -34,6 +34,16 @@
   return false;
 }
 
+bool Stdin::GetEchoNewlineMode(intptr_t fd, bool* enabled) {
+  errno = ENOSYS;
+  return false;
+}
+
+bool Stdin::SetEchoNewlineMode(intptr_t fd, bool enabled) {
+  errno = ENOSYS;
+  return false;
+}
+
 bool Stdin::GetLineMode(intptr_t fd, bool* enabled) {
   errno = ENOSYS;
   return false;
diff --git a/runtime/bin/stdio_linux.cc b/runtime/bin/stdio_linux.cc
index 17db9bd..4acddde 100644
--- a/runtime/bin/stdio_linux.cc
+++ b/runtime/bin/stdio_linux.cc
@@ -44,9 +44,34 @@
     return false;
   }
   if (enabled) {
-    term.c_lflag |= (ECHO | ECHONL);
+    term.c_lflag |= ECHO;
   } else {
-    term.c_lflag &= ~(ECHO | ECHONL);
+    term.c_lflag &= ~(ECHO);
+  }
+  status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
+  return (status == 0);
+}
+
+bool Stdin::GetEchoNewlineMode(intptr_t fd, bool* enabled) {
+  struct termios term;
+  int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
+  if (status != 0) {
+    return false;
+  }
+  *enabled = ((term.c_lflag & ECHONL) != 0);
+  return true;
+}
+
+bool Stdin::SetEchoNewlineMode(intptr_t fd, bool enabled) {
+  struct termios term;
+  int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
+  if (status != 0) {
+    return false;
+  }
+  if (enabled) {
+    term.c_lflag |= ECHONL;
+  } else {
+    term.c_lflag &= ~(ECHONL);
   }
   status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
   return (status == 0);
diff --git a/runtime/bin/stdio_macos.cc b/runtime/bin/stdio_macos.cc
index 969b75d..202b169 100644
--- a/runtime/bin/stdio_macos.cc
+++ b/runtime/bin/stdio_macos.cc
@@ -44,9 +44,34 @@
     return false;
   }
   if (enabled) {
-    term.c_lflag |= (ECHO | ECHONL);
+    term.c_lflag |= ECHO;
   } else {
-    term.c_lflag &= ~(ECHO | ECHONL);
+    term.c_lflag &= ~(ECHO);
+  }
+  status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
+  return (status == 0);
+}
+
+bool Stdin::GetEchoNewlineMode(intptr_t fd, bool* enabled) {
+  struct termios term;
+  int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
+  if (status != 0) {
+    return false;
+  }
+  *enabled = ((term.c_lflag & ECHONL) != 0);
+  return true;
+}
+
+bool Stdin::SetEchoNewlineMode(intptr_t fd, bool enabled) {
+  struct termios term;
+  int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
+  if (status != 0) {
+    return false;
+  }
+  if (enabled) {
+    term.c_lflag |= ECHONL;
+  } else {
+    term.c_lflag &= ~(ECHONL);
   }
   status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
   return (status == 0);
diff --git a/runtime/bin/stdio_win.cc b/runtime/bin/stdio_win.cc
index dc65cba..105b611 100644
--- a/runtime/bin/stdio_win.cc
+++ b/runtime/bin/stdio_win.cc
@@ -56,6 +56,19 @@
   return SetConsoleMode(h, mode);
 }
 
+bool Stdin::GetEchoNewlineMode(intptr_t fd, bool* enabled) {
+  *enabled = false;
+  return true;
+}
+
+bool Stdin::SetEchoNewlineMode(intptr_t fd, bool enabled) {
+  if (enabled) {
+    SetLastError(ERROR_NOT_CAPABLE);
+    return false;
+  }
+  return true;
+}
+
 bool Stdin::GetLineMode(intptr_t fd, bool* enabled) {
   HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
   DWORD mode;
diff --git a/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart b/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart
index a70800f..3be8a09 100644
--- a/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart
+++ b/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart
@@ -688,6 +688,16 @@
   }
 
   @patch
+  bool get echoNewlineMode {
+    throw UnsupportedError("Stdin.echoNewlineMode");
+  }
+
+  @patch
+  void set echoNewlineMode(bool enabled) {
+    throw UnsupportedError("Stdin.echoNewlineMode");
+  }
+
+  @patch
   bool get lineMode {
     throw UnsupportedError("Stdin.lineMode");
   }
diff --git a/sdk/lib/_internal/js_runtime/lib/io_patch.dart b/sdk/lib/_internal/js_runtime/lib/io_patch.dart
index e22aae2..f795f7c 100644
--- a/sdk/lib/_internal/js_runtime/lib/io_patch.dart
+++ b/sdk/lib/_internal/js_runtime/lib/io_patch.dart
@@ -688,6 +688,16 @@
   }
 
   @patch
+  bool get echoNewlineMode {
+    throw UnsupportedError("Stdin.echoNewlineMode");
+  }
+
+  @patch
+  void set echoNewlineMode(bool enabled) {
+    throw UnsupportedError("Stdin.echoNewlineMode");
+  }
+
+  @patch
   bool get lineMode {
     throw new UnsupportedError("Stdin.lineMode");
   }
diff --git a/sdk/lib/_internal/vm/bin/stdio_patch.dart b/sdk/lib/_internal/vm/bin/stdio_patch.dart
index 6a6bd2c..680bf44 100644
--- a/sdk/lib/_internal/vm/bin/stdio_patch.dart
+++ b/sdk/lib/_internal/vm/bin/stdio_patch.dart
@@ -87,6 +87,29 @@
   }
 
   @patch
+  bool get echoNewlineMode {
+    var result = _echoNewlineMode(_fd);
+    if (result is OSError) {
+      throw new StdinException(
+          "Error getting terminal echo newline mode", result);
+    }
+    return result;
+  }
+
+  @patch
+  void set echoNewlineMode(bool enabled) {
+    if (!_EmbedderConfig._maySetEchoNewlineMode) {
+      throw new UnsupportedError(
+          "This embedder disallows setting Stdin.echoNewlineMode");
+    }
+    var result = _setEchoNewlineMode(_fd, enabled);
+    if (result is OSError) {
+      throw new StdinException(
+          "Error setting terminal echo newline mode", result);
+    }
+  }
+
+  @patch
   bool get lineMode {
     var result = _lineMode(_fd);
     if (result is OSError) {
@@ -120,6 +143,10 @@
   external static _echoMode(int fd);
   @pragma("vm:external-name", "Stdin_SetEchoMode")
   external static _setEchoMode(int fd, bool enabled);
+  @pragma("vm:external-name", "Stdin_GetEchoNewlineMode")
+  external static _echoNewlineMode(int fd);
+  @pragma("vm:external-name", "Stdin_SetEchoNewlineMode")
+  external static _setEchoNewlineMode(int fd, bool enabled);
   @pragma("vm:external-name", "Stdin_GetLineMode")
   external static _lineMode(int fd);
   @pragma("vm:external-name", "Stdin_SetLineMode")
diff --git a/sdk/lib/io/embedder_config.dart b/sdk/lib/io/embedder_config.dart
index fd5bb24..9b934a6 100644
--- a/sdk/lib/io/embedder_config.dart
+++ b/sdk/lib/io/embedder_config.dart
@@ -24,6 +24,10 @@
   @pragma('vm:entry-point')
   static bool _maySetEchoMode = true;
 
+  // Whether the isolate may set [Stdin.echoNewlineMode].
+  @pragma('vm:entry-point')
+  static bool _maySetEchoNewlineMode = true;
+
   // Whether the isolate may set [Stdin.lineMode].
   @pragma('vm:entry-point')
   static bool _maySetLineMode = true;
diff --git a/sdk/lib/io/stdio.dart b/sdk/lib/io/stdio.dart
index dcaf1d8..3c8e355 100644
--- a/sdk/lib/io/stdio.dart
+++ b/sdk/lib/io/stdio.dart
@@ -112,14 +112,34 @@
 
   /// Whether echo mode is enabled on [stdin].
   ///
-  /// If disabled, input from to console will not be echoed.
+  /// If disabled, input from the console will not be echoed.
   ///
   /// Default depends on the parent process, but is usually enabled.
   ///
+  /// On POSIX systems this mode is the `echo` local terminal mode. Before
+  /// Dart 2.18, it also controlled the `echonl` mode, which is now controlled
+  /// by [echoNewlineMode].
+  ///
   /// On Windows this mode can only be enabled if [lineMode] is enabled as well.
   external bool get echoMode;
   external set echoMode(bool echoMode);
 
+  /// Whether echo newline mode is enabled on [stdin].
+  ///
+  /// If enabled, newlines from the terminal will be echoed even if the regular
+  /// [echoMode] is disabled. This mode may require `lineMode` to be turned on
+  /// to have an effect.
+  ///
+  /// Default depends on the parent process, but is usually disabled.
+  ///
+  /// On POSIX systems this mode is the `echonl` local terminal mode.
+  ///
+  /// On Windows this mode cannot be set.
+  @Since("2.18")
+  external bool get echoNewlineMode;
+  @Since("2.18")
+  external set echoNewlineMode(bool echoNewlineMode);
+
   /// Whether line mode is enabled on [stdin].
   ///
   /// If enabled, characters are delayed until a newline character is entered.
@@ -127,7 +147,10 @@
   ///
   /// Default depends on the parent process, but is usually enabled.
   ///
-  /// On Windows this mode can only be disabled if [echoMode] is disabled as well.
+  /// On POSIX systems this mode is the `icanon` local terminal mode.
+  ///
+  /// On Windows this mode can only be disabled if [echoMode] is disabled as
+  /// well.
   external bool get lineMode;
   external set lineMode(bool lineMode);
 
diff --git a/tests/standalone/io/io_override_test.dart b/tests/standalone/io/io_override_test.dart
index 4fe9c66..7acca81 100644
--- a/tests/standalone/io/io_override_test.dart
+++ b/tests/standalone/io/io_override_test.dart
@@ -177,6 +177,7 @@
 
 class StdinMock extends Stream<List<int>> implements Stdin {
   bool echoMode = false;
+  bool echoNewlineMode = false;
   bool lineMode = false;
   bool get hasTerminal => throw "";
   bool get supportsAnsiEscapes => throw "";
diff --git a/tests/standalone_2/io/io_override_test.dart b/tests/standalone_2/io/io_override_test.dart
index e7d9a81..e99f611 100644
--- a/tests/standalone_2/io/io_override_test.dart
+++ b/tests/standalone_2/io/io_override_test.dart
@@ -177,6 +177,7 @@
 
 class StdinMock extends Stream<List<int>> implements Stdin {
   bool echoMode = false;
+  bool echoNewlineMode = false;
   bool lineMode = false;
   bool get hasTerminal => throw "";
   bool get supportsAnsiEscapes => throw "";
diff --git a/tools/VERSION b/tools/VERSION
index f0ba236..3ea86a6 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 18
 PATCH 0
-PRERELEASE 155
+PRERELEASE 156
 PRERELEASE_PATCH 0
\ No newline at end of file