Version 3.6.0-176.0.dev

Merge 298a92d49ba97a3faafcb2a8867587b4e31a4de6 into dev
diff --git a/pkg/dartdev/test/native_assets/test_environment_test.dart b/pkg/dartdev/test/native_assets/test_environment_test.dart
new file mode 100644
index 0000000..c2441a8
--- /dev/null
+++ b/pkg/dartdev/test/native_assets/test_environment_test.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2024, 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.
+
+import 'dart:io';
+
+import 'package:native_assets_cli/native_assets_cli_internal.dart'
+    show CCompilerConfigImpl;
+import 'package:test/test.dart';
+
+void main() async {
+  // Test pkg/test_runner/lib/src/configuration.dart
+  test('test environment', () async {
+    printOnFailure(Platform.environment.toString());
+
+    String unparseKey(String key) => key.replaceAll('.', '__').toUpperCase();
+
+    final arKey = unparseKey(CCompilerConfigImpl.arConfigKeyFull);
+    final ccKey = unparseKey(CCompilerConfigImpl.ccConfigKeyFull);
+    final ldKey = unparseKey(CCompilerConfigImpl.ldConfigKeyFull);
+    final envScriptKey = unparseKey(CCompilerConfigImpl.envScriptConfigKeyFull);
+    final envScriptArgsKey =
+        unparseKey(CCompilerConfigImpl.envScriptArgsConfigKeyFull);
+
+    if (Platform.isLinux || Platform.isWindows) {
+      expect(Platform.environment[arKey], isNotEmpty);
+      expect(await File(Platform.environment[arKey]!).exists(), true);
+      expect(Platform.environment[ccKey], isNotEmpty);
+      expect(await File(Platform.environment[ccKey]!).exists(), true);
+      expect(Platform.environment[ldKey], isNotEmpty);
+      expect(await File(Platform.environment[ldKey]!).exists(), true);
+    }
+    if (Platform.isWindows) {
+      expect(Platform.environment[envScriptKey], isNotEmpty);
+      expect(await File(Platform.environment[envScriptKey]!).exists(), true);
+      expect(Platform.environment[envScriptArgsKey], isNotEmpty);
+    }
+  });
+}
diff --git a/pkg/dds/lib/src/dds_cli_entrypoint.dart b/pkg/dds/lib/src/dds_cli_entrypoint.dart
index ce44c14..665ac27 100644
--- a/pkg/dds/lib/src/dds_cli_entrypoint.dart
+++ b/pkg/dds/lib/src/dds_cli_entrypoint.dart
@@ -55,16 +55,21 @@
     argResults[DartDevelopmentServiceOptions.vmServiceUriOption],
   );
 
+  // Prefer IPv4 addresses if we can't determine the address type from the
+  // VM service host.
+  final preferredProtocolType =
+      InternetAddress.tryParse(remoteVmServiceUri.host)?.type ??
+          InternetAddressType.IPv4;
+
   // Resolve the address which is potentially provided by the user.
   late InternetAddress address;
   final bindAddress =
       argResults[DartDevelopmentServiceOptions.bindAddressOption];
   try {
     final addresses = await InternetAddress.lookup(bindAddress);
-    // Prefer IPv4 addresses.
     for (int i = 0; i < addresses.length; i++) {
       address = addresses[i];
-      if (address.type == InternetAddressType.IPv4) break;
+      if (address.type == preferredProtocolType) break;
     }
   } on SocketException catch (e, st) {
     writeErrorResponse('Invalid bind address: $bindAddress', st);
diff --git a/pkg/linter/lib/src/rules/use_build_context_synchronously.dart b/pkg/linter/lib/src/rules/use_build_context_synchronously.dart
index 663b6b5..1126929 100644
--- a/pkg/linter/lib/src/rules/use_build_context_synchronously.dart
+++ b/pkg/linter/lib/src/rules/use_build_context_synchronously.dart
@@ -270,12 +270,14 @@
       return switch ((leftGuardState, rightGuardState)) {
         // Anything on the left followed by async on the right is async.
         (_, AsyncState.asynchronous) => AsyncState.asynchronous,
-        // Async on the left followed by anything on the right is async.
+        // Anything on the left followed by not-mounted on the right is a
+        // not-mounted check.
+        (_, AsyncState.notMountedCheck) => AsyncState.notMountedCheck,
+        // Async on the left followed by anything else on the right is async.
         (AsyncState.asynchronous, _) => AsyncState.asynchronous,
         // A mounted guard only applies if both sides are guarded.
         (AsyncState.mountedCheck, AsyncState.mountedCheck) =>
           AsyncState.mountedCheck,
-        (_, AsyncState.notMountedCheck) => AsyncState.notMountedCheck,
         (AsyncState.notMountedCheck, _) => AsyncState.notMountedCheck,
         // Otherwise it's just uninteresting.
         (_, _) => null,
diff --git a/pkg/linter/test/rules/use_build_context_synchronously_test.dart b/pkg/linter/test/rules/use_build_context_synchronously_test.dart
index accd7a1..8c3e1c7 100644
--- a/pkg/linter/test/rules/use_build_context_synchronously_test.dart
+++ b/pkg/linter/test/rules/use_build_context_synchronously_test.dart
@@ -705,6 +705,22 @@
         AsyncState.asynchronous);
   }
 
+  test_ifStatement_referenceAfter_asyncOrNotMountedInCondition() async {
+    await resolveCode(r'''
+import 'package:flutter/widgets.dart';
+void foo(BuildContext context) async {
+  if (await Future.value(true) || !context.mounted) return;
+  //} else {
+  context /* ref */;
+  //}
+}
+''');
+    var body = findNode.ifStatement('if ').parent!;
+    var reference = findNode.statement('context /* ref */');
+    expect(body.asyncStateFor(reference, contextElement),
+        AsyncState.notMountedCheck);
+  }
+
   test_ifStatement_referenceAfter_awaitThenExitInElse() async {
     await resolveCode(r'''
 import 'package:flutter/widgets.dart';
diff --git a/sdk/lib/_internal/wasm/lib/convert_patch.dart b/sdk/lib/_internal/wasm/lib/convert_patch.dart
index a67e3b2..0987af0 100644
--- a/sdk/lib/_internal/wasm/lib/convert_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/convert_patch.dart
@@ -245,6 +245,10 @@
 
   int get capacity => array.length;
 
+  void clear() {
+    length = 0;
+  }
+
   // Pick an initial capacity greater than the first part's size.
   // The typical use case has two parts, this is the attempt at
   // guessing the size of the second part without overdoing it.
@@ -473,12 +477,15 @@
   int partialState = NO_PARTIAL;
 
   /**
-   * Extra data stored while parsing a primitive value.
-   * May be set during parsing, always set at chunk end if a value is partial.
-   *
-   * May contain a string buffer while parsing strings.
+   * String parts stored while parsing a string.
    */
-  dynamic buffer = null;
+  late final StringBuffer stringBuffer = StringBuffer();
+
+  /**
+   * Number parts stored while parsing a number.
+   */
+  late final _NumberBuffer numberBuffer =
+      _NumberBuffer(_NumberBuffer.minCapacity);
 
   /**
    * Push the current parse [state] on a stack.
@@ -518,9 +525,7 @@
         // A partial number might be a valid number if we know it's done.
         // There is an unnecessary overhead if input is a single number,
         // but this is assumed to be rare.
-        _NumberBuffer buffer = this.buffer;
-        this.buffer = null;
-        finishChunkNumber(numState, 0, 0, buffer);
+        finishChunkNumber(numState, 0, 0);
       } else if (partialType == PARTIAL_STRING) {
         fail(chunkEnd, "Unterminated string");
       } else {
@@ -686,8 +691,6 @@
   int parsePartialNumber(int position, int state) {
     int start = position;
     // Primitive implementation, can be optimized.
-    _NumberBuffer buffer = this.buffer;
-    this.buffer = null;
     int end = chunkEnd;
     toBailout:
     {
@@ -721,7 +724,7 @@
           } else if ((char | 0x20) == CHAR_e) {
             state = NUM_E;
           } else {
-            finishChunkNumber(state, start, position, buffer);
+            finishChunkNumber(state, start, position);
             return position;
           }
         }
@@ -739,7 +742,7 @@
           if ((char | 0x20) == CHAR_e) {
             state = NUM_E;
           } else {
-            finishChunkNumber(state, start, position, buffer);
+            finishChunkNumber(state, start, position);
             return position;
           }
         }
@@ -765,12 +768,12 @@
         char = getChar(position);
         digit = char ^ CHAR_0;
       }
-      finishChunkNumber(state, start, position, buffer);
+      finishChunkNumber(state, start, position);
       return position;
     }
     // Bailout code in case the current chunk ends while parsing the numeral.
     assert(position == end);
-    continueChunkNumber(state, start, buffer);
+    continueChunkNumber(state, start);
     return chunkEnd;
   }
 
@@ -1289,50 +1292,49 @@
   int beginChunkNumber(int state, int start) {
     int end = chunkEnd;
     int length = end - start;
-    var buffer = new _NumberBuffer(length);
-    copyCharsToList(start, end, buffer.array, 0);
-    buffer.length = length;
-    this.buffer = buffer;
+    numberBuffer.ensureCapacity(length);
+    copyCharsToList(start, end, numberBuffer.array, 0);
+    numberBuffer.length = length;
     this.partialState = PARTIAL_NUMERAL | state;
     return end;
   }
 
-  void addNumberChunk(_NumberBuffer buffer, int start, int end, int overhead) {
+  void addNumberChunk(int start, int end, int overhead) {
     int length = end - start;
-    int count = buffer.length;
+    int count = numberBuffer.length;
     int newCount = count + length;
     int newCapacity = newCount + overhead;
-    buffer.ensureCapacity(newCapacity);
-    copyCharsToList(start, end, buffer.array, count);
-    buffer.length = newCount;
+    numberBuffer.ensureCapacity(newCapacity);
+    copyCharsToList(start, end, numberBuffer.array, count);
+    numberBuffer.length = newCount;
   }
 
   // Continues an already chunked number across an entire chunk.
-  int continueChunkNumber(int state, int start, _NumberBuffer buffer) {
+  int continueChunkNumber(int state, int start) {
     int end = chunkEnd;
-    addNumberChunk(buffer, start, end, _NumberBuffer.defaultOverhead);
-    this.buffer = buffer;
+    addNumberChunk(start, end, _NumberBuffer.defaultOverhead);
     this.partialState = PARTIAL_NUMERAL | state;
     return end;
   }
 
-  int finishChunkNumber(int state, int start, int end, _NumberBuffer buffer) {
+  void finishChunkNumber(int state, int start, int end) {
     if (state == NUM_ZERO) {
       listener.handleNumber(0);
-      return end;
+      numberBuffer.clear();
+      return;
     }
     if (end > start) {
-      addNumberChunk(buffer, start, end, 0);
+      addNumberChunk(start, end, 0);
     }
     if (state == NUM_DIGIT) {
-      num value = buffer.parseNum();
+      num value = numberBuffer.parseNum();
       listener.handleNumber(value);
     } else if (state == NUM_DOT_DIGIT || state == NUM_E_DIGIT) {
-      listener.handleNumber(buffer.parseDouble());
+      listener.handleNumber(numberBuffer.parseDouble());
     } else {
       fail(chunkEnd, "Unterminated number literal");
     }
-    return end;
+    numberBuffer.clear();
   }
 
   int parseNumber(int char, int position) {
@@ -1521,23 +1523,21 @@
   }
 
   void beginString() {
-    this.buffer = new StringBuffer();
+    assert(stringBuffer.isEmpty);
   }
 
   void addSliceToString(int start, int end) {
-    StringBuffer buffer = this.buffer;
-    buffer.write(chunk.substringUnchecked(start, end));
+    stringBuffer.write(chunk.substringUnchecked(start, end));
   }
 
   void addCharToString(int charCode) {
-    StringBuffer buffer = this.buffer;
-    buffer.writeCharCode(charCode);
+    stringBuffer.writeCharCode(charCode);
   }
 
   String endString() {
-    StringBuffer buffer = this.buffer;
-    this.buffer = null;
-    return buffer.toString();
+    final string = stringBuffer.toString();
+    stringBuffer.clear();
+    return string;
   }
 
   void copyCharsToList(
@@ -1656,25 +1656,23 @@
 
   void beginString() {
     decoder.reset();
-    this.buffer = new StringBuffer();
+    stringBuffer.clear();
   }
 
   void addSliceToString(int start, int end) {
-    final StringBuffer buffer = this.buffer;
-    buffer.write(decoder.convertChunked(chunk, start, end));
+    stringBuffer.write(decoder.convertChunked(chunk, start, end));
   }
 
   void addCharToString(int charCode) {
-    final StringBuffer buffer = this.buffer;
-    decoder.flush(buffer);
-    buffer.writeCharCode(charCode);
+    decoder.flush(stringBuffer);
+    stringBuffer.writeCharCode(charCode);
   }
 
   String endString() {
-    final StringBuffer buffer = this.buffer;
-    decoder.flush(buffer);
-    this.buffer = null;
-    return buffer.toString();
+    decoder.flush(stringBuffer);
+    final string = stringBuffer.toString();
+    stringBuffer.clear();
+    return string;
   }
 
   void copyCharsToList(
diff --git a/tools/VERSION b/tools/VERSION
index 1b15b16..81ccca3 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 3
 MINOR 6
 PATCH 0
-PRERELEASE 175
+PRERELEASE 176
 PRERELEASE_PATCH 0