Version 2.14.0-52.0.dev

Merge commit '1dedeffc4c68485763031487d9812f8517fe2846' into 'dev'
diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index 761daab..8af538d 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -11,7 +11,7 @@
     "constraint, update this by running tools/generate_package_config.dart."
   ],
   "configVersion": 2,
-  "generated": "2021-04-23T17:16:06.111697",
+  "generated": "2021-04-27T17:23:41.776651",
   "generator": "tools/generate_package_config.dart",
   "packages": [
     {
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8365209..842903d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,14 +38,14 @@
 
 #### Linter
 
-Updated the Linter to `1.3.0`, which includes:
+Updated the Linter to `1.4.0`, which includes:
 
-- updated `non_constant_identifier_names` to check local variables, for-loop
-  initializers and catch clauses.
-- updated error range of `lines_longer_than_80_chars` to start at 80 to make
-  splitting easier.
-- new lint: `require_trailing_commas`.
-- new lint: `prefer_null_aware_method_calls`.
+- `directives_ordering` now checks ordering of `package:` imports in code
+  outside pub packages.
+- simple reachability analysis added to `use_build_context_synchronously` to
+  short-circuit await-discovery in terminating blocks.
+- `use_build_context_synchronously` updated to recognize nullable types when
+  accessed from legacy libraries.
 
 ### Language
 
@@ -5738,4 +5738,4 @@
     compression.
 
 * `dart:isolate`: `Isolate.spawnUri` added the optional `packageRoot` argument,
-  which controls how it resolves `package:` URIs.
+  which controls how it resolves `package:` URIs.
\ No newline at end of file
diff --git a/DEPS b/DEPS
index da99e64..553bbaf 100644
--- a/DEPS
+++ b/DEPS
@@ -73,7 +73,7 @@
 
   # Revisions of /third_party/* dependencies.
   "args_rev": "d8fea36c10ef96797be02e3d132d572445cd86f4",
-  "async_rev": "376c0fe95fa6fe7a5a6c41ef7b4585085c826f89",
+  "async_rev": "06774f59a7cf9780e08148298d438c1043e2b063",
   "bazel_worker_rev": "0885637b037979afbf5bcd05fd748b309fd669c0",
   "benchmark_harness_rev": "c546dbd9f639f75cd2f75de8df2eb9f8ea15e8e7",
   "boolean_selector_rev": "665e6921ab246569420376f827bff4585dff0b14",
@@ -117,14 +117,14 @@
   "http_multi_server_rev" : "7aca9e87d4a68374b685334f20359320054b8d7b",
   "http_parser_rev": "7720bfd42a0c096734c5213478fdce92c62f0293",
   "http_retry_rev": "845771af7bb5ab38ab740ce4a31f3b0c7680302b",
-  "http_rev": "69d6064dd92470ed7ccd50a808fc789ee7716fe8",
+  "http_rev": "14e4fece54591436c1e3083df350b8e142067ae7",
   "http_throttle_tag" : "1.0.2",
   "icu_rev" : "81d656878ec611cb0b42d52c82e9dae93920d9ba",
   "idl_parser_rev": "5fb1ebf49d235b5a70c9f49047e83b0654031eb7",
   "intl_tag": "0.17.0-nullsafety",
   "jinja2_rev": "2222b31554f03e62600cd7e383376a7c187967a1",
   "json_rpc_2_rev": "5ec32a2e0e99dedcef5b3237f93167cd22c2da50",
-  "linter_tag": "1.3.0",
+  "linter_tag": "1.4.0",
   "logging_rev": "e2f633b543ef89c54688554b15ca3d7e425b86a2",
   "markupsafe_rev": "8f45f5cfa0009d2a70589bcda0349b8cb2b72783",
   "markdown_rev": "9c4beaac96d8f008078e00b027915f81b665d2de",
diff --git a/pkg/native_stack_traces/CHANGELOG.md b/pkg/native_stack_traces/CHANGELOG.md
index 4a9f873..ae7d875 100644
--- a/pkg/native_stack_traces/CHANGELOG.md
+++ b/pkg/native_stack_traces/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog
 
+## 0.4.2
+
+- When decoding a stack trace, frames corresponding to the functions
+  with DW_AT_artificial DWARF attribute are now omitted from the symbolized
+  stack traces. This is needed because Dart VM no longer omits invisible
+  functions from binary stack traces in certain cases.
+
 ## 0.4.1
 
 - Exported some ELF utilities in lib/elf.dart for use in Dart tests.
diff --git a/pkg/native_stack_traces/lib/src/dwarf.dart b/pkg/native_stack_traces/lib/src/dwarf.dart
index e8d7ef1..71605a0 100644
--- a/pkg/native_stack_traces/lib/src/dwarf.dart
+++ b/pkg/native_stack_traces/lib/src/dwarf.dart
@@ -40,6 +40,7 @@
 
 enum _AttributeName {
   abstractOrigin,
+  artificial,
   callColumn,
   callFile,
   callLine,
@@ -66,6 +67,7 @@
   0x20: _AttributeName.inline,
   0x25: _AttributeName.producer,
   0x31: _AttributeName.abstractOrigin,
+  0x34: _AttributeName.artificial,
   0x39: _AttributeName.declarationColumn,
   0x3a: _AttributeName.declarationFile,
   0x3b: _AttributeName.declarationLine,
@@ -84,6 +86,7 @@
   _AttributeName.inline: "DW_AT_inline",
   _AttributeName.producer: "DW_AT_producer",
   _AttributeName.abstractOrigin: "DW_AT_abstract_origin",
+  _AttributeName.artificial: "DW_AT_artificial",
   _AttributeName.declarationColumn: "DW_AT_decl_column",
   _AttributeName.declarationFile: "DW_AT_decl_file",
   _AttributeName.declarationLine: "DW_AT_decl_line",
@@ -95,6 +98,7 @@
 enum _AttributeForm {
   address,
   constant,
+  flag,
   reference4,
   sectionOffset,
   string,
@@ -103,6 +107,7 @@
 const _attributeForms = <int, _AttributeForm>{
   0x01: _AttributeForm.address,
   0x08: _AttributeForm.string,
+  0x0c: _AttributeForm.flag,
   0x0f: _AttributeForm.constant,
   0x13: _AttributeForm.reference4,
   0x17: _AttributeForm.sectionOffset,
@@ -111,6 +116,7 @@
 const _attributeFormStrings = <_AttributeForm, String>{
   _AttributeForm.address: "DW_FORM_addr",
   _AttributeForm.string: "DW_FORM_string",
+  _AttributeForm.flag: "DW_FORM_flag",
   _AttributeForm.constant: "DW_FORM_udata",
   _AttributeForm.reference4: "DW_FORM_ref4",
   _AttributeForm.sectionOffset: "DW_FORM_sec_offset",
@@ -139,6 +145,8 @@
     switch (form) {
       case _AttributeForm.string:
         return reader.readNullTerminatedString();
+      case _AttributeForm.flag:
+        return reader.readByte() != 0;
       case _AttributeForm.address:
         return reader.readBytes(header.addressSize);
       case _AttributeForm.sectionOffset:
@@ -154,6 +162,8 @@
     switch (form) {
       case _AttributeForm.string:
         return value as String;
+      case _AttributeForm.flag:
+        return value.toString();
       case _AttributeForm.address:
         return '0x' + paddedHex(value as int, unit?.header.addressSize ?? 0);
       case _AttributeForm.sectionOffset:
@@ -310,6 +320,8 @@
 
   int? get highPC => this[_AttributeName.highProgramCounter] as int?;
 
+  bool get isArtificial => (this[_AttributeName.artificial] ?? false) as bool;
+
   bool containsPC(int virtualAddress) =>
       (lowPC ?? 0) <= virtualAddress && virtualAddress < (highPC ?? -1);
 
@@ -339,6 +351,7 @@
         ..add(DartCallInfo(
             function: unit.nameOfOrigin(abstractOrigin ?? -1),
             inlined: inlined,
+            internal: isArtificial,
             filename: callFilename(child.callFileIndex ?? -1),
             line: child.callLine ?? 0,
             column: child.callColumn ?? 0));
@@ -353,6 +366,7 @@
       DartCallInfo(
           function: unit.nameOfOrigin(abstractOrigin ?? -1),
           inlined: inlined,
+          internal: isArtificial,
           filename: filename,
           line: line,
           column: column)
@@ -1057,6 +1071,7 @@
 /// Represents the information for a call site located in Dart source code.
 class DartCallInfo extends CallInfo {
   final bool inlined;
+  final bool internal;
   final String function;
   final String filename;
   final int line;
@@ -1064,28 +1079,32 @@
 
   DartCallInfo(
       {this.inlined = false,
+      this.internal = false,
       required this.function,
       required this.filename,
       required this.line,
       required this.column});
 
   @override
-  bool get isInternal => false;
+  bool get isInternal => internal;
 
   @override
-  int get hashCode => _hashFinish(_hashCombine(
-      _hashCombine(
-          _hashCombine(
-              _hashCombine(
-                  _hashCombine(0, inlined.hashCode), function.hashCode),
-              filename.hashCode),
-          line.hashCode),
-      column.hashCode));
+  int get hashCode {
+    int hash = 0;
+    hash = _hashCombine(hash, inlined.hashCode);
+    hash = _hashCombine(hash, internal.hashCode);
+    hash = _hashCombine(hash, function.hashCode);
+    hash = _hashCombine(hash, filename.hashCode);
+    hash = _hashCombine(hash, line.hashCode);
+    hash = _hashCombine(hash, column.hashCode);
+    return _hashFinish(hash);
+  }
 
   @override
   bool operator ==(Object other) {
     if (other is DartCallInfo) {
       return inlined == other.inlined &&
+          internal == other.internal &&
           function == other.function &&
           filename == other.filename &&
           line == other.line &&
diff --git a/pkg/native_stack_traces/pubspec.yaml b/pkg/native_stack_traces/pubspec.yaml
index f93e01b..d398708 100644
--- a/pkg/native_stack_traces/pubspec.yaml
+++ b/pkg/native_stack_traces/pubspec.yaml
@@ -1,6 +1,6 @@
 name: native_stack_traces
 description: Utilities for working with non-symbolic stack traces.
-version: 0.4.1
+version: 0.4.2
 
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/native_stack_traces
 
diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc
index eaee574..22a4b28 100644
--- a/runtime/vm/compiler/aot/precompiler.cc
+++ b/runtime/vm/compiler/aot/precompiler.cc
@@ -1809,6 +1809,7 @@
       cls = it.GetNextClass();
       functions = cls.current_functions();
       for (intptr_t j = 0; j < functions.Length(); j++) {
+        SafepointWriteRwLocker ml(T, T->isolate_group()->program_lock());
         function ^= functions.At(j);
         bool retain = possibly_retained_functions_.ContainsKey(function);
         if (!retain && function.HasImplicitClosureFunction()) {
@@ -2767,13 +2768,8 @@
       function_ = code.function();
       if (functions_to_retain_.ContainsKey(function_)) {
         // Retain Code objects corresponding to:
-        // * invisible functions (to filter them from stack traces);
         // * async/async* closures (to construct async stacks).
         // * native functions (to find native implementation).
-        if (!function_.is_visible()) {
-          ++codes_with_invisible_function_;
-          return;
-        }
         if (function_.is_native()) {
           ++codes_with_native_function_;
           return;
@@ -2833,8 +2829,6 @@
                 codes_with_exception_handlers_);
       THR_Print("    %8" Pd " Codes with pc descriptors\n",
                 codes_with_pc_descriptors_);
-      THR_Print("    %8" Pd " Codes with invisible functions\n",
-                codes_with_invisible_function_);
       THR_Print("    %8" Pd " Codes with native functions\n",
                 codes_with_native_function_);
       THR_Print("    %8" Pd " Codes with async closure functions\n",
@@ -2869,7 +2863,6 @@
     intptr_t non_function_codes_ = 0;
     intptr_t codes_with_exception_handlers_ = 0;
     intptr_t codes_with_pc_descriptors_ = 0;
-    intptr_t codes_with_invisible_function_ = 0;
     intptr_t codes_with_native_function_ = 0;
     intptr_t codes_with_async_closure_function_ = 0;
     intptr_t codes_with_dynamically_called_function_ = 0;
diff --git a/runtime/vm/dwarf.cc b/runtime/vm/dwarf.cc
index 59d7893..8fe8ea3 100644
--- a/runtime/vm/dwarf.cc
+++ b/runtime/vm/dwarf.cc
@@ -259,6 +259,8 @@
   stream->uleb128(DW_FORM_addr);
   stream->uleb128(DW_AT_high_pc);
   stream->uleb128(DW_FORM_addr);
+  stream->uleb128(DW_AT_artificial);
+  stream->uleb128(DW_FORM_flag);
   stream->uleb128(0);
   stream->uleb128(0);  // End of attributes.
 
@@ -388,6 +390,8 @@
     stream->OffsetFromSymbol(asm_name, 0);
     // DW_AT_high_pc
     stream->OffsetFromSymbol(asm_name, code.Size());
+    // DW_AT_artificial
+    stream->u1(function.is_visible() ? 0 : 1);
 
     InliningNode* node = ExpandInliningTree(code);
     if (node != NULL) {
diff --git a/runtime/vm/dwarf.h b/runtime/vm/dwarf.h
index 66851c4..ec214b6 100644
--- a/runtime/vm/dwarf.h
+++ b/runtime/vm/dwarf.h
@@ -269,6 +269,7 @@
   static const intptr_t DW_AT_inline = 0x20;
   static const intptr_t DW_AT_producer = 0x25;
   static const intptr_t DW_AT_abstract_origin = 0x31;
+  static const intptr_t DW_AT_artificial = 0x34;
   static const intptr_t DW_AT_decl_column = 0x39;
   static const intptr_t DW_AT_decl_file = 0x3a;
   static const intptr_t DW_AT_decl_line = 0x3b;
@@ -278,6 +279,7 @@
 
   static const intptr_t DW_FORM_addr = 0x01;
   static const intptr_t DW_FORM_string = 0x08;
+  static const intptr_t DW_FORM_flag = 0x0c;
   static const intptr_t DW_FORM_udata = 0x0f;
   static const intptr_t DW_FORM_ref4 = 0x13;
   static const intptr_t DW_FORM_ref_udata = 0x15;
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 1d9e3ce..3c3c21a 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -7282,22 +7282,26 @@
   }
   ASSERT(is_native());
   ASSERT(obj.IsArray());
-  const Object& res = Object::Handle(Array::Cast(obj).At(1));
+  const Object& res = Object::Handle(Array::Cast(obj).AtAcquire(1));
   return res.IsNull() ? Function::null() : Function::Cast(res).ptr();
 }
 
 void Function::set_implicit_closure_function(const Function& value) const {
+  DEBUG_ASSERT(
+      IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
   ASSERT(!IsClosureFunction());
   const Object& old_data = Object::Handle(data());
   if (is_native()) {
     ASSERT(old_data.IsArray());
-    ASSERT((Array::Cast(old_data).At(1) == Object::null()) || value.IsNull());
-    Array::Cast(old_data).SetAt(1, value);
+    ASSERT((Array::Cast(old_data).AtAcquire(1) == Object::null()) ||
+           value.IsNull());
+    Array::Cast(old_data).SetAtRelease(1, value);
   } else {
     // Maybe this function will turn into a native later on :-/
     if (old_data.IsArray()) {
-      ASSERT((Array::Cast(old_data).At(1) == Object::null()) || value.IsNull());
-      Array::Cast(old_data).SetAt(1, value);
+      ASSERT((Array::Cast(old_data).AtAcquire(1) == Object::null()) ||
+             value.IsNull());
+      Array::Cast(old_data).SetAtRelease(1, value);
     } else {
       ASSERT(old_data.IsNull() || value.IsNull());
       set_data(value);
@@ -7424,7 +7428,7 @@
 //   dyn inv forwarder:       Array[0] = Function target
 //                            Array[1] = TypeArguments default type args
 void Function::set_data(const Object& value) const {
-  untag()->set_data(value.ptr());
+  untag()->set_data<std::memory_order_release>(value.ptr());
 }
 
 void Function::set_name(const String& value) const {
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index c39a387..078af85 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -3763,7 +3763,7 @@
   void set_num_optional_parameters(intptr_t value) const;  // Encoded value.
   void set_kind_tag(uint32_t value) const;
 
-  ObjectPtr data() const { return untag()->data(); }
+  ObjectPtr data() const { return untag()->data<std::memory_order_acquire>(); }
   void set_data(const Object& value) const;
 
   static FunctionPtr New(Heap::Space space = Heap::kOld);
diff --git a/tests/standalone/dwarf_stack_trace_invisible_functions_test.dart b/tests/standalone/dwarf_stack_trace_invisible_functions_test.dart
new file mode 100644
index 0000000..b7247454
--- /dev/null
+++ b/tests/standalone/dwarf_stack_trace_invisible_functions_test.dart
@@ -0,0 +1,122 @@
+// Copyright (c) 2021, 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.
+
+/// VMOptions=--dwarf-stack-traces --save-debugging-info=dwarf_invisible_functions.so
+
+import 'dart:io';
+
+import 'package:native_stack_traces/native_stack_traces.dart';
+import 'package:path/path.dart' as path;
+
+import 'dwarf_stack_trace_test.dart' as dwarf_stack_trace_test;
+
+const int LINE_A = 23;
+const int LINE_B = 29;
+const int LINE_C = 36;
+const int LINE_D = 44;
+const int LINE_E = 56;
+
+@pragma("vm:prefer-inline")
+bar() {
+  // Keep the 'throw' and its argument on separate lines.
+  throw // force linebreak with dartfmt // LINE_A
+      "Hello, Dwarf!";
+}
+
+@pragma("vm:never-inline")
+foo() {
+  bar(); // LINE_B
+}
+
+@pragma("vm:never-inline")
+bazz(void Function() func) {
+  // Call through tear-off (implicit closure function) which should be
+  // omitted from the stack trace.
+  func(); // LINE_C
+}
+
+class A<T> {
+  A();
+
+  @pragma("vm:never-inline")
+  void add(T x) {
+    bazz(foo); // LINE_D
+  }
+}
+
+dynamic aa = int.parse('1') == 1 ? A() : [];
+
+Future<void> main() async {
+  String rawStack = "";
+  try {
+    // Dynamic call to a generic-covariant method goes through the
+    // dynamic invocation forwarder function, which should be
+    // omitted from the stack trace.
+    aa.add(42); // LINE_E
+  } catch (e, st) {
+    rawStack = st.toString();
+  }
+
+  if (path.basenameWithoutExtension(Platform.executable) !=
+      "dart_precompiled_runtime") {
+    return; // Not running from an AOT compiled snapshot.
+  }
+
+  if (Platform.isAndroid) {
+    return; // Generated dwarf.so not available on the test device.
+  }
+
+  final dwarf = Dwarf.fromFile("dwarf_invisible_functions.so")!;
+
+  await dwarf_stack_trace_test.checkStackTrace(
+      rawStack, dwarf, expectedCallsInfo);
+}
+
+final expectedCallsInfo = <List<DartCallInfo>>[
+  // Frame 1: the throw in bar, which was inlined
+  // into foo (so we'll get information for two calls for that PC address).
+  [
+    DartCallInfo(
+        function: "bar",
+        filename: "dwarf_stack_trace_invisible_functions_test.dart",
+        line: LINE_A,
+        column: 3,
+        inlined: true),
+    DartCallInfo(
+        function: "foo",
+        filename: "dwarf_stack_trace_invisible_functions_test.dart",
+        line: LINE_B,
+        column: 3,
+        inlined: false)
+  ],
+  // Frame 2: call to foo in bazz.
+  [
+    DartCallInfo(
+        function: "bazz",
+        filename: "dwarf_stack_trace_invisible_functions_test.dart",
+        line: LINE_C,
+        column: 3,
+        inlined: false)
+  ],
+  // Frame 3: call to bazz in A.method.
+  [
+    DartCallInfo(
+        function: "A.add",
+        filename: "dwarf_stack_trace_invisible_functions_test.dart",
+        line: LINE_D,
+        column: 5,
+        inlined: false)
+  ],
+  // Frame 4: the call to foo in main.
+  [
+    DartCallInfo(
+        function: "main",
+        filename: "dwarf_stack_trace_invisible_functions_test.dart",
+        line: LINE_E,
+        column: 8,
+        inlined: false)
+  ],
+  // Don't assume anything about any of the frames below the main,
+  // as this makes the test too brittle.
+];
diff --git a/tests/standalone/dwarf_stack_trace_test.dart b/tests/standalone/dwarf_stack_trace_test.dart
index 236a21e..a583d34 100644
--- a/tests/standalone/dwarf_stack_trace_test.dart
+++ b/tests/standalone/dwarf_stack_trace_test.dart
@@ -86,11 +86,19 @@
     Expect.isNotNull(externalCallInfo);
     final allCallInfo = dwarf.callInfoFor(addr, includeInternalFrames: true);
     Expect.isNotNull(allCallInfo);
-    for (final call in allCallInfo!) {
+    for (final call in externalCallInfo!) {
       Expect.isTrue(call is DartCallInfo, "got non-Dart call info ${call}");
+      Expect.isFalse(call.isInternal);
+      Expect.isTrue(allCallInfo!.contains(call),
+          "External call info ${call} is not among all calls");
     }
-    Expect.deepEquals(externalCallInfo, allCallInfo);
-    gotCallsInfo.add(allCallInfo.cast<DartCallInfo>().toList());
+    for (final call in allCallInfo!) {
+      if (!call.isInternal) {
+        Expect.isTrue(externalCallInfo.contains(call),
+            "External call info ${call} is not among external calls");
+      }
+    }
+    gotCallsInfo.add(externalCallInfo.cast<DartCallInfo>().toList());
   }
 
   print("");
@@ -101,10 +109,13 @@
     gotCallsInfo[i].forEach((frame) => print("    ${frame}"));
   }
 
+  // Remove empty entries which correspond to skipped internal frames.
+  gotCallsInfo.removeWhere((calls) => calls.isEmpty);
+
   checkFrames(gotCallsInfo, expectedCallsInfo);
 
   final gotSymbolizedLines = await Stream.fromIterable(rawLines)
-      .transform(DwarfStackTraceDecoder(dwarf, includeInternalFrames: true))
+      .transform(DwarfStackTraceDecoder(dwarf, includeInternalFrames: false))
       .toList();
 
   final gotSymbolizedCalls =
diff --git a/tests/standalone_2/dwarf_stack_trace_invisible_functions_test.dart b/tests/standalone_2/dwarf_stack_trace_invisible_functions_test.dart
new file mode 100644
index 0000000..a54f3eb
--- /dev/null
+++ b/tests/standalone_2/dwarf_stack_trace_invisible_functions_test.dart
@@ -0,0 +1,122 @@
+// Copyright (c) 2021, 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.
+
+/// VMOptions=--dwarf-stack-traces --save-debugging-info=dwarf_invisible_functions.so
+
+import 'dart:io';
+
+import 'package:native_stack_traces/native_stack_traces.dart';
+import 'package:path/path.dart' as path;
+
+import 'dwarf_stack_trace_test.dart' as dwarf_stack_trace_test;
+
+const int LINE_A = 23;
+const int LINE_B = 29;
+const int LINE_C = 36;
+const int LINE_D = 44;
+const int LINE_E = 56;
+
+@pragma("vm:prefer-inline")
+bar() {
+  // Keep the 'throw' and its argument on separate lines.
+  throw // force linebreak with dartfmt // LINE_A
+      "Hello, Dwarf!";
+}
+
+@pragma("vm:never-inline")
+foo() {
+  bar(); // LINE_B
+}
+
+@pragma("vm:never-inline")
+bazz(void Function() func) {
+  // Call through tear-off (implicit closure function) which should be
+  // omitted from the stack trace.
+  func(); // LINE_C
+}
+
+class A<T> {
+  A();
+
+  @pragma("vm:never-inline")
+  void add(T x) {
+    bazz(foo); // LINE_D
+  }
+}
+
+dynamic aa = int.parse('1') == 1 ? A() : [];
+
+Future<void> main() async {
+  String rawStack = "";
+  try {
+    // Dynamic call to a generic-covariant method goes through the
+    // dynamic invocation forwarder function, which should be
+    // omitted from the stack trace.
+    aa.add(42); // LINE_E
+  } catch (e, st) {
+    rawStack = st.toString();
+  }
+
+  if (path.basenameWithoutExtension(Platform.executable) !=
+      "dart_precompiled_runtime") {
+    return; // Not running from an AOT compiled snapshot.
+  }
+
+  if (Platform.isAndroid) {
+    return; // Generated dwarf.so not available on the test device.
+  }
+
+  final dwarf = Dwarf.fromFile("dwarf_invisible_functions.so");
+
+  await dwarf_stack_trace_test.checkStackTrace(
+      rawStack, dwarf, expectedCallsInfo);
+}
+
+final expectedCallsInfo = <List<DartCallInfo>>[
+  // Frame 1: the throw in bar, which was inlined
+  // into foo (so we'll get information for two calls for that PC address).
+  [
+    DartCallInfo(
+        function: "bar",
+        filename: "dwarf_stack_trace_invisible_functions_test.dart",
+        line: LINE_A,
+        column: 3,
+        inlined: true),
+    DartCallInfo(
+        function: "foo",
+        filename: "dwarf_stack_trace_invisible_functions_test.dart",
+        line: LINE_B,
+        column: 3,
+        inlined: false)
+  ],
+  // Frame 2: call to foo in bazz.
+  [
+    DartCallInfo(
+        function: "bazz",
+        filename: "dwarf_stack_trace_invisible_functions_test.dart",
+        line: LINE_C,
+        column: 3,
+        inlined: false)
+  ],
+  // Frame 3: call to bazz in A.method.
+  [
+    DartCallInfo(
+        function: "A.add",
+        filename: "dwarf_stack_trace_invisible_functions_test.dart",
+        line: LINE_D,
+        column: 5,
+        inlined: false)
+  ],
+  // Frame 4: the call to foo in main.
+  [
+    DartCallInfo(
+        function: "main",
+        filename: "dwarf_stack_trace_invisible_functions_test.dart",
+        line: LINE_E,
+        column: 8,
+        inlined: false)
+  ],
+  // Don't assume anything about any of the frames below the main,
+  // as this makes the test too brittle.
+];
diff --git a/tests/standalone_2/dwarf_stack_trace_test.dart b/tests/standalone_2/dwarf_stack_trace_test.dart
index 6624b62..75353ee 100644
--- a/tests/standalone_2/dwarf_stack_trace_test.dart
+++ b/tests/standalone_2/dwarf_stack_trace_test.dart
@@ -88,11 +88,19 @@
     Expect.isNotNull(externalCallInfo);
     final allCallInfo = dwarf.callInfoFor(addr, includeInternalFrames: true);
     Expect.isNotNull(allCallInfo);
-    for (final call in allCallInfo) {
+    for (final call in externalCallInfo) {
       Expect.isTrue(call is DartCallInfo, "got non-Dart call info ${call}");
+      Expect.isFalse(call.isInternal);
+      Expect.isTrue(allCallInfo.contains(call),
+          "External call info ${call} is not among all calls");
     }
-    Expect.deepEquals(externalCallInfo, allCallInfo);
-    gotCallsInfo.add(allCallInfo.cast<DartCallInfo>().toList());
+    for (final call in allCallInfo) {
+      if (!call.isInternal) {
+        Expect.isTrue(externalCallInfo.contains(call),
+            "External call info ${call} is not among external calls");
+      }
+    }
+    gotCallsInfo.add(externalCallInfo.cast<DartCallInfo>().toList());
   }
 
   print("");
@@ -103,10 +111,13 @@
     gotCallsInfo[i].forEach((frame) => print("    ${frame}"));
   }
 
+  // Remove empty entries which correspond to skipped internal frames.
+  gotCallsInfo.removeWhere((calls) => calls.isEmpty);
+
   checkFrames(gotCallsInfo, expectedCallsInfo);
 
   final gotSymbolizedLines = await Stream.fromIterable(rawLines)
-      .transform(DwarfStackTraceDecoder(dwarf, includeInternalFrames: true))
+      .transform(DwarfStackTraceDecoder(dwarf, includeInternalFrames: false))
       .toList();
 
   final gotSymbolizedCalls =
diff --git a/tools/VERSION b/tools/VERSION
index e11b2c9..f14e617 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 51
+PRERELEASE 52
 PRERELEASE_PATCH 0
\ No newline at end of file