Index: runtime/vm/parser.cc |
=================================================================== |
--- runtime/vm/parser.cc (revision 44105) |
+++ runtime/vm/parser.cc (working copy) |
@@ -3036,13 +3036,8 @@ |
intptr_t saved_try_index = last_used_try_index_; |
last_used_try_index_ = 0; |
- // In case of nested async functions we also need to save the currently saved |
- // try context, the corresponding stack variable, and the scope where |
+ // In case of nested async functions we also need to save the scope where |
// temporaries are added. |
- LocalVariable* saved_saved_try_ctx = parsed_function()->saved_try_ctx(); |
- const String& saved_async_saved_try_ctx_name = |
- String::Handle(Z, parsed_function()->async_saved_try_ctx_name()); |
- parsed_function()->reset_saved_try_ctx_vars(); |
LocalScope* saved_async_temp_scope = async_temp_scope_; |
if (func.IsGenerativeConstructor()) { |
@@ -3265,9 +3260,6 @@ |
innermost_function_ = saved_innermost_function.raw(); |
last_used_try_index_ = saved_try_index; |
async_temp_scope_ = saved_async_temp_scope; |
- parsed_function()->set_saved_try_ctx(saved_saved_try_ctx); |
- parsed_function()->set_async_saved_try_ctx_name( |
- saved_async_saved_try_ctx_name); |
return CloseBlock(); |
} |
@@ -5999,8 +5991,7 @@ |
new(Z) LoadLocalNode(Scanner::kNoSourcePos, stack_trace_var))); |
} |
- AddSavedExceptionAndStacktraceToScope( |
- exception_var, stack_trace_var, current_block_->scope); |
+ SaveExceptionAndStacktrace(exception_var, stack_trace_var); |
ASSERT(try_blocks_list_ != NULL); |
ASSERT(innermost_function().IsAsyncClosure() || |
@@ -6011,12 +6002,11 @@ |
current_block_->scope->function_level())) { |
// We need to unchain three scope levels: catch clause, catch |
// parameters, and the general try block. |
- RestoreSavedTryContext( |
- current_block_->scope->parent()->parent()->parent(), |
- try_blocks_list_->outer_try_block()->try_index(), |
- current_block_->statements); |
- } else { |
- parsed_function()->reset_saved_try_ctx_vars(); |
+ current_block_->statements->Add( |
+ AwaitTransformer::RestoreSavedTryContext( |
+ Z, |
+ current_block_->scope->parent()->parent()->parent(), |
+ try_blocks_list_->outer_try_block()->try_index())); |
} |
// Complete the async future with an error. |
@@ -6103,16 +6093,13 @@ |
current_block_->scope->AddVariable(stack_trace_var); |
} |
+ SetupSavedExceptionAndStacktrace(); |
+ |
// Open the try block. |
OpenBlock(); |
PushTryBlock(current_block_); |
- if (innermost_function().IsAsyncClosure() || |
- innermost_function().IsAsyncFunction() || |
- innermost_function().IsSyncGenClosure() || |
- innermost_function().IsSyncGenerator()) { |
- SetupSavedTryContext(context_var); |
- } |
+ SetupSavedTryContext(context_var); |
} |
@@ -6385,16 +6372,6 @@ |
} |
-static inline String& BuildAsyncSavedTryContextName(Zone* zone, |
- int16_t id) { |
- const char* async_saved_prefix = ":async_saved_try_ctx_var_"; |
- // Can be a regular handle since we only use it to build an actual symbol. |
- const String& cnt_str = String::Handle(zone, |
- String::NewFormatted("%s%d", async_saved_prefix, id)); |
- return String::ZoneHandle(zone, Symbols::New(cnt_str)); |
-} |
- |
- |
SequenceNode* Parser::CloseAsyncFunction(const Function& closure, |
SequenceNode* closure_body) { |
TRACE_PARSER("CloseAsyncFunction"); |
@@ -7777,6 +7754,61 @@ |
} |
+// If the await or yield being parsed is in a try block, the continuation code |
+// needs to restore the corresponding stack-based variable :saved_try_ctx_var, |
+// and possibly the stack-based variable :saved_try_ctx_var of the outer try |
+// block. |
+// The inner :saved_try_ctx_var is used by a finally clause handling an |
+// exception thrown by the continuation code in a catch clause. If no finally |
+// clause exists, the catch or finally clause of the outer try block, if any, |
+// uses the outer :saved_try_ctx_var to handle the exception. |
+// |
+// * Try blocks: Set the context variable for this try block. |
+// * Catch blocks: Set the context variable for this try block and for any outer |
+// try block (if existent). |
+// * Finally blocks: Set the context variable for any outer try block (if |
+// existent). Note that this try block is popped before |
+// parsing the finally clause, so the outer try block (if |
+// existent) is at the top of the try block list. |
+// |
+// TODO(regis): Could we return the variables instead of their containing |
+// scopes? Check if they are already setup at this point. |
+void Parser::CheckAsyncOpInTryBlock(LocalScope** try_scope, |
+ int16_t* try_index, |
+ LocalScope** outer_try_scope, |
+ int16_t* outer_try_index) const { |
+ *try_scope = NULL; |
+ *try_index = CatchClauseNode::kInvalidTryIndex; |
+ *outer_try_scope = NULL; |
+ *outer_try_index = CatchClauseNode::kInvalidTryIndex; |
+ if (try_blocks_list_ != NULL) { |
+ LocalScope* scope = try_blocks_list_->try_block()->scope; |
+ const int current_function_level = current_block_->scope->function_level(); |
+ if (scope->function_level() == current_function_level) { |
Ivan Posva
2015/02/27 22:15:25
We need to evaluate the need for this check.
|
+ // The block declaring :saved_try_ctx_var variable is the parent of the |
+ // pushed try block. |
+ *try_scope = scope->parent(); |
+ *try_index = try_blocks_list_->try_index(); |
+ if (try_blocks_list_->inside_catch() && |
+ (try_blocks_list_->outer_try_block() != NULL)) { |
+ scope = try_blocks_list_->outer_try_block()->try_block()->scope; |
+ if (scope->function_level() == current_function_level) { |
+ *outer_try_scope = scope->parent(); |
+ *outer_try_index = try_blocks_list_->outer_try_block()->try_index(); |
+ } |
+ } |
+ } |
+ } |
+ // An async or async* has an implicitly created try-catch around the |
+ // function body, so the await or yield inside the async closure should always |
+ // be created with a try scope. |
+ ASSERT((*try_scope != NULL) || |
+ innermost_function().IsAsyncFunction() || |
+ innermost_function().IsSyncGenClosure() || |
+ innermost_function().IsSyncGenerator()); |
+} |
+ |
+ |
AstNode* Parser::ParseAwaitForStatement(String* label_name) { |
TRACE_PARSER("ParseAwaitForStatement"); |
ASSERT(IsAwaitKeyword()); |
@@ -7844,6 +7876,13 @@ |
new(Z) StoreLocalNode(stream_pos, iterator_var, ctor_call); |
current_block_->statements->Add(iterator_init); |
+ LocalScope* try_scope; |
+ int16_t try_index; |
+ LocalScope* outer_try_scope; |
+ int16_t outer_try_index; |
+ CheckAsyncOpInTryBlock(&try_scope, &try_index, |
+ &outer_try_scope, &outer_try_index); |
+ |
// Build while loop condition. |
// while (await :for-in-iter.moveNext()) |
ArgumentListNode* no_args = new(Z) ArgumentListNode(stream_pos); |
@@ -7852,11 +7891,14 @@ |
new(Z) LoadLocalNode(stream_pos, iterator_var), |
Symbols::MoveNext(), |
no_args); |
- AstNode* await_moveNext = new (Z) AwaitNode(stream_pos, iterator_moveNext); |
+ AstNode* await_moveNext = new (Z) AwaitNode(stream_pos, |
+ iterator_moveNext, |
+ try_scope, |
+ try_index, |
+ outer_try_scope, |
+ outer_try_index); |
OpenBlock(); |
- AwaitTransformer at(current_block_->statements, |
- *parsed_function(), |
- async_temp_scope_); |
+ AwaitTransformer at(current_block_->statements, async_temp_scope_); |
AstNode* transformed_await = at.Transform(await_moveNext); |
SequenceNode* await_preamble = CloseBlock(); |
@@ -8218,34 +8260,48 @@ |
} |
-// Populate local scope of the catch block with the saved exception and saved |
+// Populate current scope of the try block with the saved exception and saved |
// stack trace. |
-void Parser::AddSavedExceptionAndStacktraceToScope( |
- LocalVariable* exception_var, |
- LocalVariable* stack_trace_var, |
- LocalScope* scope) { |
+void Parser::SetupSavedExceptionAndStacktrace() { |
ASSERT(innermost_function().IsAsyncClosure() || |
innermost_function().IsAsyncFunction() || |
innermost_function().IsSyncGenClosure() || |
innermost_function().IsSyncGenerator()); |
- // Add :saved_exception_var and :saved_stack_trace_var to scope. |
+ // Add :saved_exception_var and :saved_stack_trace_var to current scope. |
// They will automatically get captured. |
- LocalVariable* saved_exception_var = new (Z) LocalVariable( |
+ // Parallel try statements share the same set of variables. |
+ LocalVariable* saved_exception_var = |
+ current_block_->scope->LocalLookupVariable(Symbols::SavedExceptionVar()); |
+ if (saved_exception_var == NULL) { |
+ saved_exception_var = new (Z) LocalVariable( |
+ Scanner::kNoSourcePos, |
+ Symbols::SavedExceptionVar(), |
+ Type::ZoneHandle(Z, Type::DynamicType())); |
+ saved_exception_var->set_is_final(); |
+ current_block_->scope->AddVariable(saved_exception_var); |
+ } |
+ LocalVariable* saved_stack_trace_var = |
+ current_block_->scope->LocalLookupVariable(Symbols::SavedStackTraceVar()); |
+ if (saved_stack_trace_var == NULL) { |
+ saved_stack_trace_var = new (Z) LocalVariable( |
Scanner::kNoSourcePos, |
- Symbols::SavedExceptionVar(), |
- Type::ZoneHandle(Z, Type::DynamicType())); |
- saved_exception_var->set_is_final(); |
- scope->AddVariable(saved_exception_var); |
- LocalVariable* saved_stack_trace_var = new (Z) LocalVariable( |
- Scanner::kNoSourcePos, |
Symbols::SavedStackTraceVar(), |
Type::ZoneHandle(Z, Type::DynamicType())); |
- saved_exception_var->set_is_final(); |
- scope->AddVariable(saved_stack_trace_var); |
+ saved_exception_var->set_is_final(); |
+ current_block_->scope->AddVariable(saved_stack_trace_var); |
+ } |
+} |
- // Generate code to load the exception object (:exception_var) into |
- // the saved exception variable (:saved_exception_var) used to rethrow. |
- saved_exception_var = current_block_->scope->LookupVariable( |
+ |
+// Generate code to load the exception object (:exception_var) into |
+// the saved exception variable (:saved_exception_var) used to rethrow. |
+void Parser::SaveExceptionAndStacktrace(LocalVariable* exception_var, |
+ LocalVariable* stack_trace_var) { |
+ ASSERT(innermost_function().IsAsyncClosure() || |
+ innermost_function().IsAsyncFunction() || |
+ innermost_function().IsSyncGenClosure() || |
+ innermost_function().IsSyncGenerator()); |
+ LocalVariable* saved_exception_var = current_block_->scope->LookupVariable( |
Symbols::SavedExceptionVar(), false); |
ASSERT(saved_exception_var != NULL); |
ASSERT(exception_var != NULL); |
@@ -8256,7 +8312,7 @@ |
// Generate code to load the stack trace object (:stack_trace_var) into |
// the saved stacktrace variable (:saved_stack_trace_var) used to rethrow. |
- saved_stack_trace_var = current_block_->scope->LookupVariable( |
+ LocalVariable* saved_stack_trace_var = current_block_->scope->LookupVariable( |
Symbols::SavedStackTraceVar(), false); |
ASSERT(saved_stack_trace_var != NULL); |
ASSERT(stack_trace_var != NULL); |
@@ -8281,11 +8337,11 @@ |
innermost_function().IsSyncGenerator()) && |
(try_blocks_list_ != NULL)) { |
// We need two unchain two scopes: finally clause, and the try block level. |
- RestoreSavedTryContext(current_block_->scope->parent()->parent(), |
- try_blocks_list_->try_index(), |
- current_block_->statements); |
- } else { |
- parsed_function()->reset_saved_try_ctx_vars(); |
+ current_block_->statements->Add( |
+ AwaitTransformer::RestoreSavedTryContext( |
+ Z, |
+ current_block_->scope->parent()->parent(), |
+ try_blocks_list_->try_index())); |
} |
ParseStatementSequence(); |
@@ -8443,15 +8499,13 @@ |
current_block_->scope->function_level())) { |
// We need to unchain three scope levels: catch clause, catch |
// parameters, and the general try block. |
- RestoreSavedTryContext( |
- current_block_->scope->parent()->parent()->parent(), |
- try_blocks_list_->outer_try_block()->try_index(), |
- current_block_->statements); |
- } else { |
- parsed_function()->reset_saved_try_ctx_vars(); |
+ current_block_->statements->Add( |
+ AwaitTransformer::RestoreSavedTryContext( |
+ Z, |
+ current_block_->scope->parent()->parent()->parent(), |
+ try_blocks_list_->outer_try_block()->try_index())); |
} |
- AddSavedExceptionAndStacktraceToScope( |
- exception_var, stack_trace_var, current_block_->scope); |
+ SaveExceptionAndStacktrace(exception_var, stack_trace_var); |
} |
current_block_->statements->Add(ParseNestedStatement(false, NULL)); |
@@ -8519,6 +8573,9 @@ |
// There isn't a generic catch clause so create a clause body that |
// rethrows the exception. This includes the case that there were no |
// catch clauses. |
+ // An await cannot possibly be executed inbetween the catch entry and here, |
+ // therefore, it is safe to rethrow the stack-based :exception_var instead |
+ // of the captured copy :saved_exception_var. |
current = new(Z) SequenceNode(handler_pos, NULL); |
current->Add(new(Z) ThrowNode( |
handler_pos, |
@@ -8542,6 +8599,17 @@ |
AstNode* type_test = type_tests.RemoveLast(); |
SequenceNode* catch_block = catch_blocks.RemoveLast(); |
+ // TODO(regis): Understand the purpose of the following code restoring |
+ // :saved_try_context_var. This code was added as part of r39926. |
+ // In some cases, this code even crashed the compiler (debug mode assert), |
+ // because the scope unchaining was starting from the wrong block. |
+ // The catch clause(s) emitted below contain the same restoring code. |
+ // So why is it necessary? Could it be an attempt to handle the case where |
+ // the catch clause is replaced by a throw because of a bad type? It is not |
+ // necessary in this case either, because no await could have been executed |
+ // between the setup of :saved_try_context_var in the try clause and here |
+ // (it is the execution of an await that clears all stack-based variables). |
+ |
// In case of async closures we need to restore the saved try index of an |
// outer try block (if it exists). |
ASSERT(try_blocks_list_ != NULL); |
@@ -8553,14 +8621,14 @@ |
(try_blocks_list_->outer_try_block()->try_block() |
->scope->function_level() == |
current_block_->scope->function_level())) { |
- // We need to unchain three scope levels: catch clause, catch |
+ // We need to unchain three scope levels (from the catch block and not |
+ // from the current block): catch clause, catch |
// parameters, and the general try block. |
- RestoreSavedTryContext( |
- current_block_->scope->parent()->parent(), |
- try_blocks_list_->outer_try_block()->try_index(), |
- current_block_->statements); |
- } else { |
- parsed_function()->reset_saved_try_ctx_vars(); |
+ current_block_->statements->Add( |
+ AwaitTransformer::RestoreSavedTryContext( |
+ Z, |
+ catch_block->scope()->parent()->parent()->parent(), |
+ try_blocks_list_->outer_try_block()->try_index())); |
} |
} |
@@ -8573,8 +8641,11 @@ |
void Parser::SetupSavedTryContext(LocalVariable* saved_try_context) { |
- const String& async_saved_try_ctx_name = |
- BuildAsyncSavedTryContextName(Z, last_used_try_index_ - 1); |
+ const String& async_saved_try_ctx_name = String::ZoneHandle(Z, |
+ Symbols::New(String::Handle(Z, |
+ String::NewFormatted("%s%d", |
+ Symbols::AsyncSavedTryCtxVarPrefix().ToCString(), |
+ last_used_try_index_ - 1)))); |
LocalVariable* async_saved_try_ctx = new (Z) LocalVariable( |
Scanner::kNoSourcePos, |
async_saved_try_ctx_name, |
@@ -8590,41 +8661,9 @@ |
Scanner::kNoSourcePos, |
async_saved_try_ctx, |
new(Z) LoadLocalNode(Scanner::kNoSourcePos, saved_try_context))); |
- parsed_function()->set_saved_try_ctx(saved_try_context); |
- parsed_function()->set_async_saved_try_ctx_name(async_saved_try_ctx_name); |
} |
-// Restore the currently relevant :saved_try_context_var on the stack |
-// from the captured :async_saved_try_ctx_var_. |
-// * Try blocks: Set the context variable for this try block. |
-// * Catch/finally blocks: Set the context variable for any outer try block (if |
-// existent). |
-// |
-// Also save the captured variable and the stack variable to be able to set |
-// it after a function continues execution (await). |
-void Parser::RestoreSavedTryContext(LocalScope* saved_try_context_scope, |
- int16_t try_index, |
- SequenceNode* target) { |
- LocalVariable* saved_try_ctx = saved_try_context_scope->LookupVariable( |
- Symbols::SavedTryContextVar(), false); |
- ASSERT((saved_try_ctx != NULL) && !saved_try_ctx->is_captured()); |
- const String& async_saved_try_ctx_name = |
- BuildAsyncSavedTryContextName(Z, try_index); |
- LocalVariable* async_saved_try_ctx = |
- target->scope()->LookupVariable(async_saved_try_ctx_name, false); |
- ASSERT(async_saved_try_ctx != NULL); |
- ASSERT(async_saved_try_ctx->is_captured()); |
- target->Add(new (Z) StoreLocalNode( |
- Scanner::kNoSourcePos, |
- saved_try_ctx, |
- new (Z) LoadLocalNode(Scanner::kNoSourcePos, async_saved_try_ctx))); |
- |
- parsed_function()->set_saved_try_ctx(saved_try_ctx); |
- parsed_function()->set_async_saved_try_ctx_name(async_saved_try_ctx_name); |
-} |
- |
- |
AstNode* Parser::ParseTryStatement(String* label_name) { |
TRACE_PARSER("ParseTryStatement"); |
@@ -8641,6 +8680,7 @@ |
// :exception_var and :stack_trace_var get set with the exception object |
// and the stack trace object when an exception is thrown. These three |
// implicit variables can never be captured. |
+ // Parallel try statements share the same set of variables. |
LocalVariable* context_var = |
current_block_->scope->LocalLookupVariable(Symbols::SavedTryContextVar()); |
if (context_var == NULL) { |
@@ -8669,6 +8709,13 @@ |
current_block_->scope->AddVariable(stack_trace_var); |
} |
+ if (innermost_function().IsAsyncClosure() || |
+ innermost_function().IsAsyncFunction() || |
+ innermost_function().IsSyncGenClosure() || |
+ innermost_function().IsSyncGenerator()) { |
+ SetupSavedExceptionAndStacktrace(); |
+ } |
+ |
const intptr_t try_pos = TokenPos(); |
ConsumeToken(); // Consume the 'try'. |
@@ -8931,18 +8978,27 @@ |
yield->AddNode(return_true); |
// If this expression is part of a try block, also append the code for |
- // restoring the saved try context that lives on the stack. |
- const String& async_saved_try_ctx_name = |
- String::Handle(Z, parsed_function()->async_saved_try_ctx_name()); |
- if (!async_saved_try_ctx_name.IsNull()) { |
- LocalVariable* async_saved_try_ctx = |
- current_block_->scope->LookupVariable(async_saved_try_ctx_name, |
- false); |
- ASSERT(async_saved_try_ctx != NULL); |
- yield->AddNode(new (Z) StoreLocalNode( |
- Scanner::kNoSourcePos, |
- parsed_function()->saved_try_ctx(), |
- new (Z) LoadLocalNode(Scanner::kNoSourcePos, async_saved_try_ctx))); |
+ // restoring the saved try context that lives on the stack and possibly the |
+ // saved try context of the outer try block. |
+ LocalScope* try_scope; |
+ int16_t try_index; |
+ LocalScope* outer_try_scope; |
+ int16_t outer_try_index; |
+ CheckAsyncOpInTryBlock(&try_scope, &try_index, |
+ &outer_try_scope, &outer_try_index); |
+ if (try_scope != NULL) { |
+ yield->AddNode( |
+ AwaitTransformer::RestoreSavedTryContext(Z, |
+ try_scope, |
+ try_index)); |
+ if (outer_try_scope != NULL) { |
+ yield->AddNode( |
+ AwaitTransformer::RestoreSavedTryContext(Z, |
+ outer_try_scope, |
+ outer_try_index)); |
+ } |
+ } else { |
+ ASSERT(outer_try_scope == NULL); |
} |
statement = yield; |
@@ -8993,6 +9049,10 @@ |
// If in async code, use :saved_exception_var and :saved_stack_trace_var |
// instead of :exception_var and :stack_trace_var. |
+ // These variables are bound in the block containing the try. |
+ // Look in the try scope directly. |
+ LocalScope* scope = try_blocks_list_->try_block()->scope->parent(); |
+ ASSERT(scope != NULL); |
LocalVariable* excp_var; |
LocalVariable* trace_var; |
if (innermost_function().IsAsyncClosure() || |
@@ -8999,16 +9059,9 @@ |
innermost_function().IsAsyncFunction() || |
innermost_function().IsSyncGenClosure() || |
innermost_function().IsSyncGenerator()) { |
- // The saved exception and stack trace variables are bound in the block |
- // containing the catch. So start looking in the current scope. |
- LocalScope* scope = current_block_->scope; |
- excp_var = scope->LookupVariable(Symbols::SavedExceptionVar(), false); |
- trace_var = scope->LookupVariable(Symbols::SavedStackTraceVar(), false); |
+ excp_var = scope->LocalLookupVariable(Symbols::SavedExceptionVar()); |
+ trace_var = scope->LocalLookupVariable(Symbols::SavedStackTraceVar()); |
} else { |
- // The exception and stack trace variables are bound in the block |
- // containing the try. Look in the try scope directly. |
- LocalScope* scope = try_blocks_list_->try_block()->scope->parent(); |
- ASSERT(scope != NULL); |
excp_var = scope->LocalLookupVariable(Symbols::ExceptionVar()); |
trace_var = scope->LocalLookupVariable(Symbols::StackTraceVar()); |
} |
@@ -9698,9 +9751,7 @@ |
// See FlowGraphBuilder::VisitSequenceNode() for details on when contexts |
// are created. |
OpenBlock(); |
- AwaitTransformer at(current_block_->statements, |
- *parsed_function(), |
- async_temp_scope_); |
+ AwaitTransformer at(current_block_->statements, async_temp_scope_); |
AstNode* result = at.Transform(expr); |
SequenceNode* preamble = CloseBlock(); |
if (await_preamble == NULL) { |
@@ -9816,7 +9867,20 @@ |
} |
ConsumeToken(); |
parsed_function()->record_await(); |
- expr = new (Z) AwaitNode(op_pos, ParseUnaryExpr()); |
+ |
+ LocalScope* try_scope; |
+ int16_t try_index; |
+ LocalScope* outer_try_scope; |
+ int16_t outer_try_index; |
+ CheckAsyncOpInTryBlock(&try_scope, &try_index, |
+ &outer_try_scope, &outer_try_index); |
+ |
+ expr = new (Z) AwaitNode(op_pos, |
+ ParseUnaryExpr(), |
+ try_scope, |
+ try_index, |
+ outer_try_scope, |
+ outer_try_index); |
} else if (IsPrefixOperator(CurrentToken())) { |
Token::Kind unary_op = CurrentToken(); |
if (unary_op == Token::kSUB) { |
@@ -11089,7 +11153,7 @@ |
CheckToken(Token::kIDENT, "type name expected"); |
intptr_t ident_pos = TokenPos(); |
LibraryPrefix& prefix = LibraryPrefix::Handle(Z); |
- String& type_name = String::Handle(Z);; |
+ String& type_name = String::Handle(Z); |
if (finalization == ClassFinalizer::kIgnore) { |
if (!is_top_level_ && (current_block_ != NULL)) { |