Skip to content

Commit 46e7d54

Browse files
mkustermannCommit Bot
authored and
Commit Bot
committed
[vm] Be more lenient when assumptions about _StreamIterator._stateData field is not a Future
Issue flutter/flutter#100441 Issue #48695 TEST=vm/dart{,_2}/flutter_regress_100441_test Change-Id: Ifdd331dc3b0a2b825a938132808b4021c0af5f71 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/239311 Reviewed-by: Slava Egorov <[email protected]> Reviewed-by: Alexander Markov <[email protected]> Commit-Queue: Martin Kustermann <[email protected]>
1 parent cfde2d4 commit 46e7d54

File tree

6 files changed

+95
-4
lines changed

6 files changed

+95
-4
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
import 'package:expect/expect.dart';
8+
9+
StackTrace? stackTrace;
10+
11+
main() async {
12+
final values = [];
13+
await for (final value in produce()) {
14+
values.add(value);
15+
}
16+
Expect.equals('foo', values.single);
17+
Expect.isNotNull(stackTrace!);
18+
}
19+
20+
Stream<String> produce() async* {
21+
await for (String response in produceInner()) {
22+
yield response;
23+
}
24+
}
25+
26+
Stream<String> produceInner() async* {
27+
yield 'foo';
28+
stackTrace = StackTrace.current;
29+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// @dart=2.9
6+
7+
import 'dart:async';
8+
9+
import 'package:expect/expect.dart';
10+
11+
StackTrace stackTrace;
12+
13+
main() async {
14+
final values = [];
15+
await for (final value in produce()) {
16+
values.add(value);
17+
}
18+
Expect.equals('foo', values.single);
19+
Expect.isNotNull(stackTrace);
20+
}
21+
22+
Stream<String> produce() async* {
23+
await for (String response in produceInner()) {
24+
yield response;
25+
}
26+
}
27+
28+
Stream<String> produceInner() async* {
29+
yield 'foo';
30+
stackTrace = StackTrace.current;
31+
}

runtime/vm/stack_trace.cc

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ CallerClosureFinder::CallerClosureFinder(Zone* zone)
8383
var_data_field(Field::Handle(zone)),
8484
state_field(Field::Handle(zone)),
8585
on_data_field(Field::Handle(zone)),
86-
state_data_field(Field::Handle(zone)) {
86+
state_data_field(Field::Handle(zone)),
87+
has_value_field(Field::Handle(zone)) {
8788
const auto& async_lib = Library::Handle(zone, Library::AsyncLibrary());
8889
// Look up classes:
8990
// - async:
@@ -143,6 +144,9 @@ CallerClosureFinder::CallerClosureFinder(Zone* zone)
143144
state_data_field =
144145
stream_iterator_class.LookupFieldAllowPrivate(Symbols::_stateData());
145146
ASSERT(!state_data_field.IsNull());
147+
has_value_field =
148+
stream_iterator_class.LookupFieldAllowPrivate(Symbols::_hasValue());
149+
ASSERT(!has_value_field.IsNull());
146150
}
147151

148152
ClosurePtr CallerClosureFinder::GetCallerInFutureImpl(const Object& future) {
@@ -201,9 +205,33 @@ ClosurePtr CallerClosureFinder::FindCallerInAsyncGenClosure(
201205

202206
// If the async* stream is await-for'd:
203207
if (callback_instance_.GetClassId() == stream_iterator_class.id()) {
204-
// _StreamIterator._stateData
205-
future_ = Instance::Cast(callback_instance_).GetField(state_data_field);
206-
return GetCallerInFutureImpl(future_);
208+
// If `_hasValue` is true then the `StreamIterator._stateData` field
209+
// contains the iterator's value. In that case we cannot unwind anymore.
210+
//
211+
// Notice: With correct async* semantics this may never be true: The async*
212+
// generator should only be invoked to produce a vaue if there's an
213+
// in-progress `await streamIterator.moveNext()` call. Once such call has
214+
// finished the async* generator should be paused/yielded until the next
215+
// such call - and being paused/yielded means it should not appear in stack
216+
// traces.
217+
//
218+
// See dartbug.com/48695.
219+
const auto& stream_iterator = Instance::Cast(callback_instance_);
220+
if (stream_iterator.GetField(has_value_field) ==
221+
Object::bool_true().ptr()) {
222+
return Closure::null();
223+
}
224+
225+
// If we have an await'er for `await streamIterator.moveNext()` we continue
226+
// unwinding there.
227+
//
228+
// Notice: With correct async* semantics this may always contain a Future
229+
// See also comment above as well as dartbug.com/48695.
230+
future_ = stream_iterator.GetField(state_data_field);
231+
if (future_.GetClassId() == future_impl_class.id()) {
232+
return GetCallerInFutureImpl(future_);
233+
}
234+
return Closure::null();
207235
}
208236

209237
UNREACHABLE(); // If no onData is found we have a bug.

runtime/vm/stack_trace.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class CallerClosureFinder {
9292
Field& state_field;
9393
Field& on_data_field;
9494
Field& state_data_field;
95+
Field& has_value_field;
9596

9697
DISALLOW_COPY_AND_ASSIGN(CallerClosureFinder);
9798
};

runtime/vm/symbols.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ class ObjectPointerVisitor;
414414
V(_handleMessage, "_handleMessage") \
415415
V(_handleFinalizerMessage, "_handleFinalizerMessage") \
416416
V(_handleNativeFinalizerMessage, "_handleNativeFinalizerMessage") \
417+
V(_hasValue, "_hasValue") \
417418
V(_instanceOf, "_instanceOf") \
418419
V(_listGetAt, "_listGetAt") \
419420
V(_listLength, "_listLength") \

sdk/lib/async/stream_impl.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,7 @@ class _StreamIterator<T> implements StreamIterator<T> {
978978
/// This will usually cause the [_subscription] to be paused, but as an
979979
/// optimization, we only pause after the [moveNext] future has been
980980
/// completed.
981+
@pragma("vm:entry-point")
981982
bool _hasValue = false;
982983

983984
_StreamIterator(final Stream<T> stream)

0 commit comments

Comments
 (0)