Commit 3b0f89d0 authored by Sigurd Schneider's avatar Sigurd Schneider Committed by Commit Bot

[debugger] Fix code coverage for async functions

Async functions were not correctly fixed up for code coverage, which
caused an additional uncovered range to be reported between a return
statement and the closing bracket.

This CL adds code that detects such ranges, and removes them, similarly
to how the ranges are removed for normal functions. The removal process
is different, because the parser rewrites async functions to contain a
try-catch handling promise rejection.

Change-Id: I73b08d64be74d26c32f2f9652d027430d4671251

Bug: chromium:981313, v8:8381
Change-Id: I82a7f0c54d3a48609ef5255a7659d9557e163566
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1782837Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Commit-Queue: Sigurd Schneider <sigurds@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63561}
parent 5c738033
......@@ -724,11 +724,14 @@ class BreakStatement final : public JumpStatement {
class ReturnStatement final : public JumpStatement {
public:
enum Type { kNormal, kAsyncReturn };
enum Type { kNormal, kAsyncReturn, kSyntheticAsyncReturn };
Expression* expression() const { return expression_; }
Type type() const { return TypeField::decode(bit_field_); }
bool is_async_return() const { return type() == kAsyncReturn; }
bool is_async_return() const { return type() != kNormal; }
bool is_synthetic_async_return() const {
return type() == kSyntheticAsyncReturn;
}
int end_position() const { return end_position_; }
......@@ -745,7 +748,7 @@ class ReturnStatement final : public JumpStatement {
Expression* expression_;
int end_position_;
using TypeField = JumpStatement::NextBitField<Type, 1>;
using TypeField = JumpStatement::NextBitField<Type, 2>;
};
......@@ -917,6 +920,10 @@ class TryCatchStatement final : public TryStatement {
outer_catch_prediction != HandlerTable::UNCAUGHT;
}
bool is_try_catch_for_async() {
return catch_prediction_ == HandlerTable::ASYNC_AWAIT;
}
private:
friend class AstNodeFactory;
......@@ -2885,6 +2892,12 @@ class AstNodeFactory final {
expression, ReturnStatement::kAsyncReturn, pos, end_position);
}
ReturnStatement* NewSyntheticAsyncReturnStatement(
Expression* expression, int pos, int end_position = kNoSourcePosition) {
return new (zone_) ReturnStatement(
expression, ReturnStatement::kSyntheticAsyncReturn, pos, end_position);
}
WithStatement* NewWithStatement(Scope* scope,
Expression* expression,
Statement* statement,
......
......@@ -31,6 +31,11 @@ void SourceRangeAstVisitor::VisitFunctionLiteral(FunctionLiteral* expr) {
MaybeRemoveLastContinuationRange(stmts);
}
void SourceRangeAstVisitor::VisitTryCatchStatement(TryCatchStatement* stmt) {
AstTraversalVisitor::VisitTryCatchStatement(stmt);
MaybeRemoveContinuationRangeOfAsyncReturn(stmt);
}
bool SourceRangeAstVisitor::VisitNode(AstNode* node) {
AstNodeSourceRanges* range = source_range_map_->Find(node);
......@@ -51,11 +56,8 @@ bool SourceRangeAstVisitor::VisitNode(AstNode* node) {
return true;
}
void SourceRangeAstVisitor::MaybeRemoveLastContinuationRange(
ZonePtrList<Statement>* statements) {
if (statements->is_empty()) return;
Statement* last_statement = statements->last();
void SourceRangeAstVisitor::MaybeRemoveContinuationRange(
Statement* last_statement) {
AstNodeSourceRanges* last_range = nullptr;
if (last_statement->IsExpressionStatement() &&
......@@ -75,5 +77,38 @@ void SourceRangeAstVisitor::MaybeRemoveLastContinuationRange(
}
}
void SourceRangeAstVisitor::MaybeRemoveLastContinuationRange(
ZonePtrList<Statement>* statements) {
if (statements->is_empty()) return;
MaybeRemoveContinuationRange(statements->last());
}
namespace {
Statement* FindLastNonSyntheticReturn(ZonePtrList<Statement>* statements) {
for (int i = statements->length() - 1; i >= 0; --i) {
Statement* stmt = statements->at(i);
if (!stmt->IsReturnStatement()) break;
if (stmt->AsReturnStatement()->is_synthetic_async_return()) continue;
return stmt;
}
return nullptr;
}
} // namespace
void SourceRangeAstVisitor::MaybeRemoveContinuationRangeOfAsyncReturn(
TryCatchStatement* try_catch_stmt) {
// Detect try-catch inserted by NewTryCatchStatementForAsyncAwait in the
// parser (issued for async functions, including async generators), and
// remove the continuation ranges of return statements corresponding to
// returns at function end in the untransformed source.
if (try_catch_stmt->is_try_catch_for_async()) {
Statement* last_non_synthetic =
FindLastNonSyntheticReturn(try_catch_stmt->try_block()->statements());
if (last_non_synthetic) {
MaybeRemoveContinuationRange(last_non_synthetic);
}
}
}
} // namespace internal
} // namespace v8
......@@ -36,8 +36,11 @@ class SourceRangeAstVisitor final
void VisitBlock(Block* stmt);
void VisitFunctionLiteral(FunctionLiteral* expr);
bool VisitNode(AstNode* node);
void VisitTryCatchStatement(TryCatchStatement* stmt);
void MaybeRemoveContinuationRange(Statement* last_statement);
void MaybeRemoveLastContinuationRange(ZonePtrList<Statement>* stmts);
void MaybeRemoveContinuationRangeOfAsyncReturn(TryCatchStatement* stmt);
SourceRangeMap* source_range_map_ = nullptr;
std::unordered_set<int> continuation_positions_;
......
......@@ -3250,7 +3250,7 @@ void Parser::RewriteAsyncFunctionBody(ScopedPtrList<Statement>* body,
// })
// }
block->statements()->Add(factory()->NewAsyncReturnStatement(
block->statements()->Add(factory()->NewSyntheticAsyncReturnStatement(
return_value, return_value->position()),
zone());
block = BuildRejectPromiseOnException(block);
......
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --no-always-opt --no-stress-flush-bytecode
// Flags: --no-stress-incremental-marking
// Files: test/mjsunit/code-coverage-utils.js
%DebugToggleBlockCoverage(true);
TestCoverage(
"await expressions",
`
async function f() { // 0000
await 42; // 0050
await 42; // 0100
}; // 0150
f(); // 0200
%PerformMicrotaskCheckpoint(); // 0250
`,
[{"start":0,"end":299,"count":1},
{"start":0,"end":151,"count":1}]
);
TestCoverage(
"for-await-of statements",
`
!async function() { // 0000
for await (var x of [0,1,2,3]) { // 0050
nop(); // 0100
} // 0150
}(); // 0200
%PerformMicrotaskCheckpoint(); // 0250
`,
[{"start":0,"end":299,"count":1},
{"start":1,"end":201,"count":1},
{"start":83,"end":153,"count":4}]
);
TestCoverage(
"https://crbug.com/981313",
`
class Foo { // 0000
async timeout() { // 0000
return new Promise( // 0100
(r) => setTimeout(r, 10)); // 0000
} // 0200
} // 0000
new Foo().timeout(); // 0300
`,
[ {"start":0, "end":349, "count":1},
{"start":52, "end":203, "count":1},
{"start":158,"end":182, "count":1}]);
TestCoverage(
"test async generator coverage",
`
class Foo { // 0000
async *timeout() { // 0000
return new Promise( // 0100
(r) => setTimeout(r, 10)); // 0000
} // 0200
} // 0000
new Foo().timeout(); // 0300
`,
[ {"start":0, "end":349, "count":1},
{"start":52, "end":203, "count":1},
{"start":158,"end":182, "count":0}]);
TestCoverage(
"test async generator coverage with next call",
`
class Foo { // 0000
async *timeout() { // 0000
return new Promise( // 0100
(r) => setTimeout(r, 10)); // 0000
} // 0200
} // 0000
new Foo().timeout().next(); // 0300
`,
[ {"start":0, "end":349, "count":1},
{"start":52, "end":203, "count":1},
{"start":158,"end":182, "count":1}]);
TestCoverage(
"test two consecutive returns",
`
class Foo { // 0000
timeout() { // 0000
return new Promise( // 0100
(r) => setTimeout(r, 10)); // 0000
return new Promise( // 0200
(r) => setTimeout(r, 10)); // 0000
} // 0300
} // 0000
new Foo().timeout(); // 0400
`,
[ {"start":0,"end":449,"count":1},
{"start":52,"end":303,"count":1},
{"start":184,"end":302,"count":0},
{"start":158,"end":182,"count":1}] );
TestCoverage(
"test async generator with two consecutive returns",
`
class Foo { // 0000
async *timeout() { // 0000
return new Promise( // 0100
(r) => setTimeout(r, 10)); // 0000
return new Promise( // 0200
(r) => setTimeout(r, 10)); // 0000
} // 0300
} // 0000
new Foo().timeout().next(); // 0400
`,
[ {"start":0,"end":449,"count":1},
{"start":52,"end":303,"count":1},
{"start":184,"end":302,"count":0},
{"start":158,"end":182,"count":1}] );
%DebugToggleBlockCoverage(false);
......@@ -205,21 +205,6 @@ TestCoverage(
{"start":381,"end":391,"count":2}]
);
TestCoverage(
"for-await-of statements",
`
!async function() { // 0000
for await (var x of [0,1,2,3]) { // 0050
nop(); // 0100
} // 0150
}(); // 0200
%PerformMicrotaskCheckpoint(); // 0250
`,
[{"start":0,"end":299,"count":1},
{"start":1,"end":201,"count":1},
{"start":83,"end":153,"count":4}]
);
TestCoverage(
"while and do-while statements",
`
......@@ -658,20 +643,6 @@ try { // 0200
{"start":317,"end":352,"count":0}]
);
TestCoverage(
"await expressions",
`
async function f() { // 0000
await 42; // 0050
await 42; // 0100
}; // 0150
f(); // 0200
%PerformMicrotaskCheckpoint(); // 0250
`,
[{"start":0,"end":299,"count":1},
{"start":0,"end":151,"count":1}]
);
TestCoverage(
"LogicalOrExpression assignment",
`
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment