mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 10:33:28 +00:00
[vm] Runtime type check in await
'await e' should check that e is a Future<flatten(S)>, where S is a static type of e before awaiting e. If e is not a Future<flatten(S)>, then 'await e' should await Future.value(e) instead of e. So futures of incompatible type are not awaited and soundness is not violated. TEST=tests/language/async/await_type_check_test.dart (Based on https://dart-review.git.corp.google.com/c/sdk/+/267422.) Fixes https://github.com/dart-lang/sdk/issues/50529 Part of https://github.com/dart-lang/sdk/issues/49396 Change-Id: Ia418db1be6736710abc9be87d95584c50cbc677e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/273002 Reviewed-by: Erik Ernst <eernst@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
parent
38cab10852
commit
66f1dee48c
|
@ -255,6 +255,20 @@ If an argument of `await` is a Future, then `_SuspendState._await` attaches 'the
|
|||
callbacks to that Future. Otherwise it schedules a micro-task to continue execution of
|
||||
the suspended function later.
|
||||
|
||||
### AwaitWithTypeCheck stub
|
||||
|
||||
AwaitWithTypeCheck is a variant of Await stub which additionally passes type argument `T`
|
||||
and calls `_SuspendState._awaitWithTypeCheck` in order to test if the value has a
|
||||
correct `Future<T>` type before awaiting.
|
||||
|
||||
This runtime check is needed to maintain soundness in case value is a Future of an
|
||||
incompatible type, for example:
|
||||
|
||||
```
|
||||
final FutureOr<Object> f = Future<Object?>.value(null);
|
||||
Object x = await f; // x == f, not null.
|
||||
```
|
||||
|
||||
### ReturnAsync stub
|
||||
|
||||
ReturnAsync stub = Return stub which calls `_SuspendState._returnAsync`.
|
||||
|
|
|
@ -7678,12 +7678,24 @@ void Call1ArgStubInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
|||
locs(), deopt_id(), env());
|
||||
}
|
||||
|
||||
Definition* SuspendInstr::Canonicalize(FlowGraph* flow_graph) {
|
||||
if (stub_id() == StubId::kAwaitWithTypeCheck &&
|
||||
!operand()->Type()->CanBeFuture()) {
|
||||
type_args()->RemoveFromUseList();
|
||||
stub_id_ = StubId::kAwait;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
LocationSummary* SuspendInstr::MakeLocationSummary(Zone* zone, bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
const intptr_t kNumInputs = has_type_args() ? 2 : 1;
|
||||
const intptr_t kNumTemps = 0;
|
||||
LocationSummary* locs = new (zone)
|
||||
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall);
|
||||
locs->set_in(0, Location::RegisterLocation(SuspendStubABI::kArgumentReg));
|
||||
if (has_type_args()) {
|
||||
locs->set_in(1, Location::RegisterLocation(SuspendStubABI::kTypeArgsReg));
|
||||
}
|
||||
locs->set_out(0, Location::RegisterLocation(CallingConventions::kReturnReg));
|
||||
return locs;
|
||||
}
|
||||
|
@ -7698,6 +7710,9 @@ void SuspendInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
|||
case StubId::kAwait:
|
||||
stub = object_store->await_stub();
|
||||
break;
|
||||
case StubId::kAwaitWithTypeCheck:
|
||||
stub = object_store->await_with_type_check_stub();
|
||||
break;
|
||||
case StubId::kYieldAsyncStar:
|
||||
stub = object_store->yield_async_star_stub();
|
||||
break;
|
||||
|
|
|
@ -10623,10 +10623,11 @@ class Call1ArgStubInstr : public TemplateDefinition<1, Throws> {
|
|||
};
|
||||
|
||||
// Suspends execution using the suspend stub specified using [StubId].
|
||||
class SuspendInstr : public TemplateDefinition<1, Throws> {
|
||||
class SuspendInstr : public TemplateDefinition<2, Throws> {
|
||||
public:
|
||||
enum class StubId {
|
||||
kAwait,
|
||||
kAwaitWithTypeCheck,
|
||||
kYieldAsyncStar,
|
||||
kSuspendSyncStarAtStart,
|
||||
kSuspendSyncStarAtYield,
|
||||
|
@ -10635,6 +10636,7 @@ class SuspendInstr : public TemplateDefinition<1, Throws> {
|
|||
SuspendInstr(const InstructionSource& source,
|
||||
StubId stub_id,
|
||||
Value* operand,
|
||||
Value* type_args,
|
||||
intptr_t deopt_id,
|
||||
intptr_t resume_deopt_id)
|
||||
: TemplateDefinition(source, deopt_id),
|
||||
|
@ -10642,9 +10644,22 @@ class SuspendInstr : public TemplateDefinition<1, Throws> {
|
|||
resume_deopt_id_(resume_deopt_id),
|
||||
token_pos_(source.token_pos) {
|
||||
SetInputAt(0, operand);
|
||||
if (has_type_args()) {
|
||||
SetInputAt(1, type_args);
|
||||
} else {
|
||||
ASSERT(type_args == nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool has_type_args() const { return stub_id_ == StubId::kAwaitWithTypeCheck; }
|
||||
virtual intptr_t InputCount() const { return has_type_args() ? 2 : 1; }
|
||||
|
||||
Value* operand() const { return inputs_[0]; }
|
||||
Value* type_args() const {
|
||||
ASSERT(has_type_args());
|
||||
return inputs_[1];
|
||||
}
|
||||
|
||||
StubId stub_id() const { return stub_id_; }
|
||||
intptr_t resume_deopt_id() const { return resume_deopt_id_; }
|
||||
virtual TokenPosition token_pos() const { return token_pos_; }
|
||||
|
@ -10659,8 +10674,10 @@ class SuspendInstr : public TemplateDefinition<1, Throws> {
|
|||
DECLARE_INSTRUCTION(Suspend);
|
||||
PRINT_OPERANDS_TO_SUPPORT
|
||||
|
||||
virtual Definition* Canonicalize(FlowGraph* flow_graph);
|
||||
|
||||
#define FIELD_LIST(F) \
|
||||
F(const StubId, stub_id_) \
|
||||
F(StubId, stub_id_) \
|
||||
F(const intptr_t, resume_deopt_id_) \
|
||||
F(const TokenPosition, token_pos_)
|
||||
|
||||
|
|
|
@ -1385,6 +1385,9 @@ void SuspendInstr::PrintOperandsTo(BaseTextBuffer* f) const {
|
|||
case StubId::kAwait:
|
||||
name = "Await";
|
||||
break;
|
||||
case StubId::kAwaitWithTypeCheck:
|
||||
name = "AwaitWithTypeCheck";
|
||||
break;
|
||||
case StubId::kYieldAsyncStar:
|
||||
name = "YieldAsyncStar";
|
||||
break;
|
||||
|
@ -1396,7 +1399,7 @@ void SuspendInstr::PrintOperandsTo(BaseTextBuffer* f) const {
|
|||
break;
|
||||
}
|
||||
f->Printf("%s(", name);
|
||||
operand()->PrintTo(f);
|
||||
Definition::PrintOperandsTo(f);
|
||||
f->AddString(")");
|
||||
}
|
||||
|
||||
|
|
|
@ -4481,15 +4481,28 @@ Fragment StreamingFlowGraphBuilder::BuildAwaitExpression(
|
|||
|
||||
instructions += BuildExpression(); // read operand.
|
||||
|
||||
SuspendInstr::StubId stub_id = SuspendInstr::StubId::kAwait;
|
||||
if (ReadTag() == kSomething) {
|
||||
// TODO(50529): Use runtime check type when present.
|
||||
SkipDartType(); // read runtime check type.
|
||||
const AbstractType& type = T.BuildType(); // read runtime check type.
|
||||
if (!type.IsType() ||
|
||||
!Class::Handle(Z, type.type_class()).IsFutureClass()) {
|
||||
FATAL("Unexpected type for runtime check in await: %s", type.ToCString());
|
||||
}
|
||||
ASSERT(type.IsFinalized());
|
||||
const auto& type_args = TypeArguments::ZoneHandle(Z, type.arguments());
|
||||
if (!type_args.IsNull()) {
|
||||
const auto& type_arg = AbstractType::Handle(Z, type_args.TypeAt(0));
|
||||
if (!type_arg.IsTopTypeForSubtyping()) {
|
||||
instructions += TranslateInstantiatedTypeArguments(type_args);
|
||||
stub_id = SuspendInstr::StubId::kAwaitWithTypeCheck;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (NeedsDebugStepCheck(parsed_function()->function(), pos)) {
|
||||
instructions += DebugStepCheck(pos);
|
||||
}
|
||||
instructions += B->Suspend(pos, SuspendInstr::StubId::kAwait);
|
||||
instructions += B->Suspend(pos, stub_id);
|
||||
return instructions;
|
||||
}
|
||||
|
||||
|
|
|
@ -673,7 +673,7 @@ void KernelFingerprintHelper::CalculateExpressionFingerprint() {
|
|||
ReadPosition(); // read position.
|
||||
CalculateExpressionFingerprint(); // read operand.
|
||||
if (ReadTag() == kSomething) {
|
||||
SkipDartType(); // read runtime check type.
|
||||
CalculateDartTypeFingerprint(); // read runtime check type.
|
||||
}
|
||||
return;
|
||||
case kConstStaticInvocation:
|
||||
|
|
|
@ -4323,9 +4323,12 @@ Fragment FlowGraphBuilder::Call1ArgStub(TokenPosition position,
|
|||
|
||||
Fragment FlowGraphBuilder::Suspend(TokenPosition position,
|
||||
SuspendInstr::StubId stub_id) {
|
||||
Value* type_args =
|
||||
(stub_id == SuspendInstr::StubId::kAwaitWithTypeCheck) ? Pop() : nullptr;
|
||||
Value* operand = Pop();
|
||||
SuspendInstr* instr =
|
||||
new (Z) SuspendInstr(InstructionSource(position), stub_id, Pop(),
|
||||
GetNextDeoptId(), GetNextDeoptId());
|
||||
new (Z) SuspendInstr(InstructionSource(position), stub_id, operand,
|
||||
type_args, GetNextDeoptId(), GetNextDeoptId());
|
||||
Push(instr);
|
||||
return Fragment(instr);
|
||||
}
|
||||
|
|
|
@ -983,7 +983,7 @@ void ScopeBuilder::VisitExpression() {
|
|||
helper_.ReadPosition(); // read position.
|
||||
VisitExpression(); // read operand.
|
||||
if (helper_.ReadTag() == kSomething) {
|
||||
helper_.SkipDartType(); // read runtime check type.
|
||||
VisitDartType(); // read runtime check type.
|
||||
}
|
||||
return;
|
||||
case kConstStaticInvocation:
|
||||
|
|
|
@ -1274,6 +1274,7 @@ class Thread : public AllStatic {
|
|||
|
||||
static word suspend_state_init_async_entry_point_offset();
|
||||
static word suspend_state_await_entry_point_offset();
|
||||
static word suspend_state_await_with_type_check_entry_point_offset();
|
||||
static word suspend_state_return_async_entry_point_offset();
|
||||
static word suspend_state_return_async_not_future_entry_point_offset();
|
||||
|
||||
|
@ -1312,6 +1313,7 @@ class ObjectStore : public AllStatic {
|
|||
static word type_type_offset();
|
||||
|
||||
static word suspend_state_await_offset();
|
||||
static word suspend_state_await_with_type_check_offset();
|
||||
static word suspend_state_handle_exception_offset();
|
||||
static word suspend_state_init_async_offset();
|
||||
static word suspend_state_init_async_star_offset();
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -182,6 +182,7 @@
|
|||
FIELD(ObjectStore, string_type_offset) \
|
||||
FIELD(ObjectStore, type_type_offset) \
|
||||
FIELD(ObjectStore, suspend_state_await_offset) \
|
||||
FIELD(ObjectStore, suspend_state_await_with_type_check_offset) \
|
||||
FIELD(ObjectStore, suspend_state_handle_exception_offset) \
|
||||
FIELD(ObjectStore, suspend_state_init_async_offset) \
|
||||
FIELD(ObjectStore, suspend_state_init_async_star_offset) \
|
||||
|
@ -306,6 +307,7 @@
|
|||
FIELD(Thread, stack_overflow_shared_without_fpu_regs_stub_offset) \
|
||||
FIELD(Thread, store_buffer_block_offset) \
|
||||
FIELD(Thread, suspend_state_await_entry_point_offset) \
|
||||
FIELD(Thread, suspend_state_await_with_type_check_entry_point_offset) \
|
||||
FIELD(Thread, suspend_state_init_async_entry_point_offset) \
|
||||
FIELD(Thread, suspend_state_return_async_entry_point_offset) \
|
||||
FIELD(Thread, suspend_state_return_async_not_future_entry_point_offset) \
|
||||
|
|
|
@ -1892,9 +1892,11 @@ static void GenerateAllocateSuspendState(Assembler* assembler,
|
|||
void StubCodeCompiler::GenerateSuspendStub(
|
||||
Assembler* assembler,
|
||||
bool call_suspend_function,
|
||||
bool pass_type_arguments,
|
||||
intptr_t suspend_entry_point_offset_in_thread,
|
||||
intptr_t suspend_function_offset_in_object_store) {
|
||||
const Register kArgument = SuspendStubABI::kArgumentReg;
|
||||
const Register kTypeArgs = SuspendStubABI::kTypeArgsReg;
|
||||
const Register kTemp = SuspendStubABI::kTempReg;
|
||||
const Register kFrameSize = SuspendStubABI::kFrameSizeReg;
|
||||
const Register kSuspendState = SuspendStubABI::kSuspendStateReg;
|
||||
|
@ -1917,6 +1919,10 @@ void StubCodeCompiler::GenerateSuspendStub(
|
|||
|
||||
__ EnterStubFrame();
|
||||
|
||||
if (pass_type_arguments) {
|
||||
__ PushRegister(kTypeArgs);
|
||||
}
|
||||
|
||||
__ CompareClassId(kSuspendState, kSuspendStateCid, kTemp);
|
||||
|
||||
if (FLAG_precompiled_mode) {
|
||||
|
@ -2042,8 +2048,14 @@ void StubCodeCompiler::GenerateSuspendStub(
|
|||
__ Bind(&call_dart);
|
||||
if (call_suspend_function) {
|
||||
__ Comment("Call suspend Dart function");
|
||||
if (pass_type_arguments) {
|
||||
__ LoadObject(ARGS_DESC_REG,
|
||||
ArgumentsDescriptorBoxed(/*type_args_len=*/1,
|
||||
/*num_arguments=*/2));
|
||||
}
|
||||
CallDartCoreLibraryFunction(assembler, suspend_entry_point_offset_in_thread,
|
||||
suspend_function_offset_in_object_store);
|
||||
suspend_function_offset_in_object_store,
|
||||
/*uses_args_desc=*/pass_type_arguments);
|
||||
} else {
|
||||
// SuspendStub returns either the result of Dart callback,
|
||||
// or SuspendStub argument (if Dart callback is not used).
|
||||
|
@ -2137,14 +2149,25 @@ void StubCodeCompiler::GenerateSuspendStub(
|
|||
void StubCodeCompiler::GenerateAwaitStub(Assembler* assembler) {
|
||||
GenerateSuspendStub(assembler,
|
||||
/*call_suspend_function=*/true,
|
||||
/*pass_type_arguments=*/false,
|
||||
target::Thread::suspend_state_await_entry_point_offset(),
|
||||
target::ObjectStore::suspend_state_await_offset());
|
||||
}
|
||||
|
||||
void StubCodeCompiler::GenerateAwaitWithTypeCheckStub(Assembler* assembler) {
|
||||
GenerateSuspendStub(
|
||||
assembler,
|
||||
/*call_suspend_function=*/true,
|
||||
/*pass_type_arguments=*/true,
|
||||
target::Thread::suspend_state_await_with_type_check_entry_point_offset(),
|
||||
target::ObjectStore::suspend_state_await_with_type_check_offset());
|
||||
}
|
||||
|
||||
void StubCodeCompiler::GenerateYieldAsyncStarStub(Assembler* assembler) {
|
||||
GenerateSuspendStub(
|
||||
assembler,
|
||||
/*call_suspend_function=*/true,
|
||||
/*pass_type_arguments=*/false,
|
||||
target::Thread::suspend_state_yield_async_star_entry_point_offset(),
|
||||
target::ObjectStore::suspend_state_yield_async_star_offset());
|
||||
}
|
||||
|
@ -2154,6 +2177,7 @@ void StubCodeCompiler::GenerateSuspendSyncStarAtStartStub(
|
|||
GenerateSuspendStub(
|
||||
assembler,
|
||||
/*call_suspend_function=*/true,
|
||||
/*pass_type_arguments=*/false,
|
||||
target::Thread::
|
||||
suspend_state_suspend_sync_star_at_start_entry_point_offset(),
|
||||
target::ObjectStore::suspend_state_suspend_sync_star_at_start_offset());
|
||||
|
@ -2162,7 +2186,8 @@ void StubCodeCompiler::GenerateSuspendSyncStarAtStartStub(
|
|||
void StubCodeCompiler::GenerateSuspendSyncStarAtYieldStub(
|
||||
Assembler* assembler) {
|
||||
GenerateSuspendStub(assembler,
|
||||
/*call_suspend_function=*/false, -1, -1);
|
||||
/*call_suspend_function=*/false,
|
||||
/*pass_type_arguments=*/false, -1, -1);
|
||||
}
|
||||
|
||||
void StubCodeCompiler::GenerateInitSuspendableFunctionStub(
|
||||
|
|
|
@ -207,6 +207,7 @@ class StubCodeCompiler : public AllStatic {
|
|||
static void GenerateSuspendStub(
|
||||
Assembler* assembler,
|
||||
bool call_suspend_function,
|
||||
bool pass_type_arguments,
|
||||
intptr_t suspend_entry_point_offset_in_thread,
|
||||
intptr_t suspend_function_offset_in_object_store);
|
||||
static void GenerateInitSuspendableFunctionStub(
|
||||
|
|
|
@ -573,10 +573,11 @@ struct DoubleToIntegerStubABI {
|
|||
static const Register kResultReg = R0;
|
||||
};
|
||||
|
||||
// ABI for SuspendStub (AwaitStub, YieldAsyncStarStub,
|
||||
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
|
||||
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
|
||||
struct SuspendStubABI {
|
||||
static const Register kArgumentReg = R0;
|
||||
static const Register kTypeArgsReg = R1; // Can be the same as kTempReg
|
||||
static const Register kTempReg = R1;
|
||||
static const Register kFrameSizeReg = R2;
|
||||
static const Register kSuspendStateReg = R3;
|
||||
|
|
|
@ -403,10 +403,11 @@ struct DoubleToIntegerStubABI {
|
|||
static const Register kResultReg = R0;
|
||||
};
|
||||
|
||||
// ABI for SuspendStub (AwaitStub, YieldAsyncStarStub,
|
||||
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
|
||||
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
|
||||
struct SuspendStubABI {
|
||||
static const Register kArgumentReg = R0;
|
||||
static const Register kTypeArgsReg = R1; // Can be the same as kTempReg
|
||||
static const Register kTempReg = R1;
|
||||
static const Register kFrameSizeReg = R2;
|
||||
static const Register kSuspendStateReg = R3;
|
||||
|
|
|
@ -292,10 +292,11 @@ struct DoubleToIntegerStubABI {
|
|||
static const Register kResultReg = EAX;
|
||||
};
|
||||
|
||||
// ABI for SuspendStub (AwaitStub, YieldAsyncStarStub,
|
||||
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
|
||||
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
|
||||
struct SuspendStubABI {
|
||||
static const Register kArgumentReg = EAX;
|
||||
static const Register kTypeArgsReg = EDX; // Can be the same as kTempReg
|
||||
static const Register kTempReg = EDX;
|
||||
static const Register kFrameSizeReg = ECX;
|
||||
static const Register kSuspendStateReg = EBX;
|
||||
|
|
|
@ -412,10 +412,11 @@ struct DoubleToIntegerStubABI {
|
|||
static constexpr Register kResultReg = A0;
|
||||
};
|
||||
|
||||
// ABI for SuspendStub (AwaitStub, YieldAsyncStarStub,
|
||||
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
|
||||
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
|
||||
struct SuspendStubABI {
|
||||
static const Register kArgumentReg = A0;
|
||||
static const Register kTypeArgsReg = T0; // Can be the same as kTempReg
|
||||
static const Register kTempReg = T0;
|
||||
static const Register kFrameSizeReg = T1;
|
||||
static const Register kSuspendStateReg = T2;
|
||||
|
|
|
@ -373,10 +373,11 @@ struct DoubleToIntegerStubABI {
|
|||
static const Register kResultReg = RAX;
|
||||
};
|
||||
|
||||
// ABI for SuspendStub (AwaitStub, YieldAsyncStarStub,
|
||||
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
|
||||
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
|
||||
struct SuspendStubABI {
|
||||
static const Register kArgumentReg = RAX;
|
||||
static const Register kTypeArgsReg = RDX; // Can be the same as kTempReg
|
||||
static const Register kTempReg = RDX;
|
||||
static const Register kFrameSizeReg = RCX;
|
||||
static const Register kSuspendStateReg = RBX;
|
||||
|
|
|
@ -300,6 +300,10 @@ void ObjectStore::InitKnownObjects() {
|
|||
ASSERT(!function.IsNull());
|
||||
set_suspend_state_await(function);
|
||||
|
||||
function = cls.LookupFunctionAllowPrivate(Symbols::_awaitWithTypeCheck());
|
||||
ASSERT(!function.IsNull());
|
||||
set_suspend_state_await_with_type_check(function);
|
||||
|
||||
function = cls.LookupFunctionAllowPrivate(Symbols::_returnAsync());
|
||||
ASSERT(!function.IsNull());
|
||||
set_suspend_state_return_async(function);
|
||||
|
|
|
@ -177,6 +177,7 @@ class ObjectPointerVisitor;
|
|||
RW(Function, async_star_stream_controller_add_stream) \
|
||||
RW(Function, suspend_state_init_async) \
|
||||
RW(Function, suspend_state_await) \
|
||||
RW(Function, suspend_state_await_with_type_check) \
|
||||
RW(Function, suspend_state_return_async) \
|
||||
RW(Function, suspend_state_return_async_not_future) \
|
||||
RW(Function, suspend_state_init_async_star) \
|
||||
|
@ -272,6 +273,7 @@ class ObjectPointerVisitor;
|
|||
RW(Code, slow_tts_stub) \
|
||||
/* Roots for JIT/AOT snapshots are up until here (see to_snapshot() below)*/ \
|
||||
RW(Code, await_stub) \
|
||||
RW(Code, await_with_type_check_stub) \
|
||||
RW(Code, clone_suspend_state_stub) \
|
||||
RW(Code, init_async_stub) \
|
||||
RW(Code, resume_stub) \
|
||||
|
@ -366,6 +368,7 @@ class ObjectPointerVisitor;
|
|||
DO(init_late_instance_field_stub, InitLateInstanceField) \
|
||||
DO(init_late_final_instance_field_stub, InitLateFinalInstanceField) \
|
||||
DO(await_stub, Await) \
|
||||
DO(await_with_type_check_stub, AwaitWithTypeCheck) \
|
||||
DO(clone_suspend_state_stub, CloneSuspendState) \
|
||||
DO(init_async_stub, InitAsync) \
|
||||
DO(resume_stub, Resume) \
|
||||
|
|
|
@ -160,6 +160,7 @@ namespace dart {
|
|||
V(InstantiateTypeArgumentsMayShareFunctionTA) \
|
||||
V(NoSuchMethodDispatcher) \
|
||||
V(Await) \
|
||||
V(AwaitWithTypeCheck) \
|
||||
V(InitAsync) \
|
||||
V(Resume) \
|
||||
V(ReturnAsync) \
|
||||
|
|
|
@ -418,6 +418,7 @@ class ObjectPointerVisitor;
|
|||
V(_WeakProperty, "_WeakProperty") \
|
||||
V(_WeakReference, "_WeakReference") \
|
||||
V(_await, "_await") \
|
||||
V(_awaitWithTypeCheck, "_awaitWithTypeCheck") \
|
||||
V(_classRangeCheck, "_classRangeCheck") \
|
||||
V(_current, "_current") \
|
||||
V(_ensureScheduleImmediate, "_ensureScheduleImmediate") \
|
||||
|
|
|
@ -184,6 +184,7 @@ class Thread;
|
|||
#define CACHED_FUNCTION_ENTRY_POINTS_LIST(V) \
|
||||
V(suspend_state_init_async) \
|
||||
V(suspend_state_await) \
|
||||
V(suspend_state_await_with_type_check) \
|
||||
V(suspend_state_return_async) \
|
||||
V(suspend_state_return_async_not_future) \
|
||||
V(suspend_state_init_async_star) \
|
||||
|
|
|
@ -309,6 +309,33 @@ class _SuspendState {
|
|||
return _functionData;
|
||||
}
|
||||
|
||||
@pragma("vm:entry-point", "call")
|
||||
@pragma("vm:invisible")
|
||||
Object? _awaitWithTypeCheck<T>(Object? object) {
|
||||
if (_thenCallback == null) {
|
||||
_createAsyncCallbacks();
|
||||
}
|
||||
// Declare a new variable to avoid type promotion of 'object' to
|
||||
// 'Future<T>', as it would disable further type promotion to '_Future'.
|
||||
final obj = object;
|
||||
if (obj is Future<T>) {
|
||||
if (object is _Future) {
|
||||
if (object._isComplete) {
|
||||
_awaitCompletedFuture(object);
|
||||
} else {
|
||||
object._thenAwait<dynamic>(
|
||||
unsafeCast<dynamic Function(dynamic)>(_thenCallback),
|
||||
unsafeCast<dynamic Function(Object, StackTrace)>(_errorCallback));
|
||||
}
|
||||
} else {
|
||||
_awaitUserDefinedFuture(obj);
|
||||
}
|
||||
} else {
|
||||
_awaitNotFuture(object);
|
||||
}
|
||||
return _functionData;
|
||||
}
|
||||
|
||||
@pragma("vm:entry-point", "call")
|
||||
@pragma("vm:invisible")
|
||||
static Future _returnAsync(Object suspendState, Object? returnValue) {
|
||||
|
|
81
tests/language/async/await_type_check_test.dart
Normal file
81
tests/language/async/await_type_check_test.dart
Normal file
|
@ -0,0 +1,81 @@
|
|||
// 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.
|
||||
|
||||
// Verifies that 'await' should check if the awaited value has a correct
|
||||
// Future<T> type before awaiting it. A Future of incompatible type
|
||||
// should not be awaited.
|
||||
//
|
||||
// Regression test for https://github.com/dart-lang/sdk/issues/49396.
|
||||
|
||||
// Requirements=nnbd-strong
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:expect/expect.dart';
|
||||
import '../static_type_helper.dart';
|
||||
|
||||
class A {}
|
||||
|
||||
abstract class B<X> extends A implements Future<X> {
|
||||
Future<X> get fut;
|
||||
asStream() => fut.asStream();
|
||||
catchError(error, {test}) => fut.catchError(error, test: test);
|
||||
then<R>(onValue, {onError}) => fut.then(onValue, onError: onError);
|
||||
timeout(timeLimit, {onTimeout}) =>
|
||||
fut.timeout(timeLimit, onTimeout: onTimeout);
|
||||
whenComplete(action) => fut.whenComplete(action);
|
||||
}
|
||||
|
||||
class C extends B<Object?> {
|
||||
final Future<Object?> fut = Future.value(CompletelyUnrelated());
|
||||
}
|
||||
|
||||
class CompletelyUnrelated {}
|
||||
|
||||
class C1 extends A {}
|
||||
|
||||
class C2 extends B<C1> {
|
||||
final Future<C1> fut = Future.value(C1());
|
||||
}
|
||||
|
||||
void main() async {
|
||||
final Object o = Future<Object?>.value();
|
||||
var o2 = await o; // Remains a future.
|
||||
o2.expectStaticType<Exactly<Object>>();
|
||||
Expect.isTrue(o2 is Future<Object?>);
|
||||
Expect.identical(o2, o);
|
||||
|
||||
final FutureOr<Object> x = Future<Object?>.value();
|
||||
var x2 = await x; // Remains a future.
|
||||
x2.expectStaticType<Exactly<Object>>();
|
||||
Expect.isTrue(x2 is Future<Object?>);
|
||||
Expect.identical(x2, x);
|
||||
|
||||
final FutureOr<Future<int>> y = Future<int>.value(1);
|
||||
var y2 = await y; // Remains a `Future<int>`.
|
||||
y2.expectStaticType<Exactly<Future<int>>>();
|
||||
Expect.isTrue(y2 is Future<int>);
|
||||
Expect.identical(y2, y);
|
||||
|
||||
A a = C();
|
||||
var a2 = await a; // Remains an `A`.
|
||||
a2.expectStaticType<Exactly<A>>();
|
||||
Expect.isTrue(a2 is A);
|
||||
Expect.identical(a2, a);
|
||||
|
||||
Future<void> f<X extends Object>(X x) async {
|
||||
var x2 = await x; // Remains a `Future<Object?>`.
|
||||
Expect.isTrue(x2 is Future<Object?>);
|
||||
Expect.identical(x2, x);
|
||||
}
|
||||
|
||||
await f(Future<Object?>.value(null));
|
||||
|
||||
Future<void> g<X extends A>(X x) async {
|
||||
var x2 = await x; // The future is awaited.
|
||||
Expect.isTrue(x2 is C1);
|
||||
Expect.notIdentical(x2, x);
|
||||
}
|
||||
|
||||
await g<A>(C2());
|
||||
}
|
Loading…
Reference in a new issue