Commit e65e2f87 authored by jgruber's avatar jgruber Committed by Commit Bot

[coverage] Add support for iteration (For,While,DoWhile)

This adds block coverage support for simple iteration. For-of and
for-in loops are not yet covered, and we don't yet keep execution counts
for init, cond, and next statements.

BUG=v8:6000

Change-Id: I30b468a2c93f0bb60e857b6632be92920f6857e0
Reviewed-on: https://chromium-review.googlesource.com/527113
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarMarja Hölttä <marja@chromium.org>
Reviewed-by: 's avatarRoss McIlroy <rmcilroy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#45779}
parent 8ff91ab7
......@@ -546,6 +546,8 @@ class IterationStatement : public BreakableStatement {
Statement* body() const { return body_; }
void set_body(Statement* s) { body_ = s; }
SourceRange body_range() const { return body_range_; }
int suspend_count() const { return suspend_count_; }
int first_suspend_id() const { return first_suspend_id_; }
void set_suspend_count(int suspend_count) { suspend_count_ = suspend_count; }
......@@ -567,7 +569,10 @@ class IterationStatement : public BreakableStatement {
suspend_count_(0),
first_suspend_id_(0) {}
static int parent_num_ids() { return BreakableStatement::num_ids(); }
void Initialize(Statement* body) { body_ = body; }
void Initialize(Statement* body, const SourceRange& body_range = {}) {
body_ = body;
body_range_ = body_range;
}
static const uint8_t kNextBitFieldIndex =
BreakableStatement::kNextBitFieldIndex;
......@@ -576,6 +581,7 @@ class IterationStatement : public BreakableStatement {
int local_id(int n) const { return base_id() + parent_num_ids() + n; }
Statement* body_;
SourceRange body_range_;
Label continue_target_;
int suspend_count_;
int first_suspend_id_;
......@@ -584,8 +590,9 @@ class IterationStatement : public BreakableStatement {
class DoWhileStatement final : public IterationStatement {
public:
void Initialize(Expression* cond, Statement* body) {
IterationStatement::Initialize(body);
void Initialize(Expression* cond, Statement* body,
const SourceRange& body_range = {}) {
IterationStatement::Initialize(body, body_range);
cond_ = cond;
}
......@@ -611,8 +618,9 @@ class DoWhileStatement final : public IterationStatement {
class WhileStatement final : public IterationStatement {
public:
void Initialize(Expression* cond, Statement* body) {
IterationStatement::Initialize(body);
void Initialize(Expression* cond, Statement* body,
const SourceRange& body_range = {}) {
IterationStatement::Initialize(body, body_range);
cond_ = cond;
}
......@@ -638,11 +646,9 @@ class WhileStatement final : public IterationStatement {
class ForStatement final : public IterationStatement {
public:
void Initialize(Statement* init,
Expression* cond,
Statement* next,
Statement* body) {
IterationStatement::Initialize(body);
void Initialize(Statement* init, Expression* cond, Statement* next,
Statement* body, const SourceRange& body_range = {}) {
IterationStatement::Initialize(body, body_range);
init_ = init;
cond_ = cond;
next_ = next;
......
......@@ -1368,15 +1368,20 @@ void BytecodeGenerator::VisitIterationBody(IterationStatement* stmt,
}
void BytecodeGenerator::VisitDoWhileStatement(DoWhileStatement* stmt) {
int body_slot = AllocateBlockCoverageSlotIfEnabled(stmt->body_range());
LoopBuilder loop_builder(builder());
if (stmt->cond()->ToBooleanIsFalse()) {
BuildIncrementBlockCoverageCounterIfEnabled(body_slot);
VisitIterationBody(stmt, &loop_builder);
} else if (stmt->cond()->ToBooleanIsTrue()) {
VisitIterationHeader(stmt, &loop_builder);
BuildIncrementBlockCoverageCounterIfEnabled(body_slot);
VisitIterationBody(stmt, &loop_builder);
loop_builder.JumpToHeader(loop_depth_);
} else {
VisitIterationHeader(stmt, &loop_builder);
BuildIncrementBlockCoverageCounterIfEnabled(body_slot);
VisitIterationBody(stmt, &loop_builder);
builder()->SetExpressionAsStatementPosition(stmt->cond());
BytecodeLabels loop_backbranch(zone());
......@@ -1388,6 +1393,8 @@ void BytecodeGenerator::VisitDoWhileStatement(DoWhileStatement* stmt) {
}
void BytecodeGenerator::VisitWhileStatement(WhileStatement* stmt) {
int body_slot = AllocateBlockCoverageSlotIfEnabled(stmt->body_range());
if (stmt->cond()->ToBooleanIsFalse()) {
// If the condition is false there is no need to generate the loop.
return;
......@@ -1402,11 +1409,14 @@ void BytecodeGenerator::VisitWhileStatement(WhileStatement* stmt) {
TestFallthrough::kThen);
loop_body.Bind(builder());
}
BuildIncrementBlockCoverageCounterIfEnabled(body_slot);
VisitIterationBody(stmt, &loop_builder);
loop_builder.JumpToHeader(loop_depth_);
}
void BytecodeGenerator::VisitForStatement(ForStatement* stmt) {
int body_slot = AllocateBlockCoverageSlotIfEnabled(stmt->body_range());
if (stmt->init() != nullptr) {
Visit(stmt->init());
}
......@@ -1425,6 +1435,7 @@ void BytecodeGenerator::VisitForStatement(ForStatement* stmt) {
TestFallthrough::kThen);
loop_body.Bind(builder());
}
BuildIncrementBlockCoverageCounterIfEnabled(body_slot);
VisitIterationBody(stmt, &loop_builder);
if (stmt->next() != nullptr) {
builder()->SetStatementPosition(stmt->next());
......
......@@ -96,9 +96,13 @@ class SourceRangeScope final {
PositionKind post_kind = POSITION)
: scanner_(scanner), range_(range), post_kind_(post_kind) {
range_->start = GetPosition(pre_kind);
DCHECK_NE(range_->start, kNoSourcePosition);
}
~SourceRangeScope() { range_->end = GetPosition(post_kind_); }
~SourceRangeScope() {
range_->end = GetPosition(post_kind_);
DCHECK_NE(range_->end, kNoSourcePosition);
}
private:
int32_t GetPosition(PositionKind kind) {
......@@ -5357,8 +5361,14 @@ typename ParserBase<Impl>::StatementT ParserBase<Impl>::ParseDoWhileStatement(
auto loop = factory()->NewDoWhileStatement(labels, peek_position());
typename Types::Target target(this, loop);
SourceRange body_range;
StatementT body = impl()->NullStatement();
Expect(Token::DO, CHECK_OK);
StatementT body = ParseStatement(nullptr, CHECK_OK);
{
SourceRangeScope range_scope(scanner(), &body_range);
body = ParseStatement(nullptr, CHECK_OK);
}
Expect(Token::WHILE, CHECK_OK);
Expect(Token::LPAREN, CHECK_OK);
......@@ -5371,7 +5381,7 @@ typename ParserBase<Impl>::StatementT ParserBase<Impl>::ParseDoWhileStatement(
// ExpectSemicolon() functionality here.
Check(Token::SEMICOLON);
loop->Initialize(cond, body);
loop->Initialize(cond, body, body_range);
return loop;
}
......@@ -5384,13 +5394,19 @@ typename ParserBase<Impl>::StatementT ParserBase<Impl>::ParseWhileStatement(
auto loop = factory()->NewWhileStatement(labels, peek_position());
typename Types::Target target(this, loop);
SourceRange body_range;
StatementT body = impl()->NullStatement();
Expect(Token::WHILE, CHECK_OK);
Expect(Token::LPAREN, CHECK_OK);
ExpressionT cond = ParseExpression(true, CHECK_OK);
Expect(Token::RPAREN, CHECK_OK);
StatementT body = ParseStatement(nullptr, CHECK_OK);
{
SourceRangeScope range_scope(scanner(), &body_range);
body = ParseStatement(nullptr, CHECK_OK);
}
loop->Initialize(cond, body);
loop->Initialize(cond, body, body_range);
return loop;
}
......@@ -5774,6 +5790,8 @@ typename ParserBase<Impl>::StatementT ParserBase<Impl>::ParseStandardForLoop(
StatementT next = impl()->NullStatement();
StatementT body = impl()->NullStatement();
SourceRange body_range;
// If there are let bindings, then condition and the next statement of the
// for loop must be parsed in a new scope.
Scope* inner_scope = scope();
......@@ -5795,6 +5813,7 @@ typename ParserBase<Impl>::StatementT ParserBase<Impl>::ParseStandardForLoop(
}
Expect(Token::RPAREN, CHECK_OK);
SourceRangeScope range_scope(scanner(), &body_range);
body = ParseStatement(nullptr, CHECK_OK);
}
......@@ -5804,7 +5823,8 @@ typename ParserBase<Impl>::StatementT ParserBase<Impl>::ParseStandardForLoop(
(is_resumable() || function_state_->contains_function_or_eval())) {
scope()->set_is_hidden();
return impl()->DesugarLexicalBindingsInForStatement(
loop, init, cond, next, body, inner_scope, *for_info, CHECK_OK);
loop, init, cond, next, body, body_range, inner_scope, *for_info,
CHECK_OK);
}
Scope* for_scope = scope()->FinalizeBlockScope();
......@@ -5834,11 +5854,11 @@ typename ParserBase<Impl>::StatementT ParserBase<Impl>::ParseStandardForLoop(
}
block->statements()->Add(loop, zone());
block->set_scope(for_scope);
loop->Initialize(init, cond, next, body);
loop->Initialize(init, cond, next, body, body_range);
return block;
}
loop->Initialize(init, cond, next, body);
loop->Initialize(init, cond, next, body, body_range);
return loop;
}
......
......@@ -2202,7 +2202,8 @@ Statement* Parser::InitializeForOfStatement(
Statement* Parser::DesugarLexicalBindingsInForStatement(
ForStatement* loop, Statement* init, Expression* cond, Statement* next,
Statement* body, Scope* inner_scope, const ForInfo& for_info, bool* ok) {
Statement* body, const SourceRange& body_range, Scope* inner_scope,
const ForInfo& for_info, bool* ok) {
// ES6 13.7.4.8 specifies that on each loop iteration the let variables are
// copied into a new environment. Moreover, the "next" statement must be
// evaluated not in the environment of the just completed iteration but in
......@@ -2432,7 +2433,7 @@ Statement* Parser::DesugarLexicalBindingsInForStatement(
inner_block->set_scope(inner_scope);
}
outer_loop->Initialize(NULL, NULL, NULL, inner_block);
outer_loop->Initialize(NULL, NULL, NULL, inner_block, body_range);
return outer_block;
}
......
......@@ -499,7 +499,8 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
Statement* DesugarLexicalBindingsInForStatement(
ForStatement* loop, Statement* init, Expression* cond, Statement* next,
Statement* body, Scope* inner_scope, const ForInfo& for_info, bool* ok);
Statement* body, const SourceRange& body_range, Scope* inner_scope,
const ForInfo& for_info, bool* ok);
Expression* RewriteDoExpression(Block* body, int pos, bool* ok);
......
......@@ -522,9 +522,11 @@ class PreParserStatement {
PreParserStatementList statements() { return PreParserStatementList(); }
void set_scope(Scope* scope) {}
void Initialize(PreParserExpression cond, PreParserStatement body) {}
void Initialize(PreParserExpression cond, PreParserStatement body,
const SourceRange& body_range = {}) {}
void Initialize(PreParserStatement init, PreParserExpression cond,
PreParserStatement next, PreParserStatement body) {}
PreParserStatement next, PreParserStatement body,
const SourceRange& body_range = {}) {}
private:
enum Type {
......@@ -1359,8 +1361,8 @@ class PreParser : public ParserBase<PreParser> {
V8_INLINE StatementT DesugarLexicalBindingsInForStatement(
PreParserStatement loop, PreParserStatement init,
PreParserExpression cond, PreParserStatement next,
PreParserStatement body, Scope* inner_scope, const ForInfo& for_info,
bool* ok) {
PreParserStatement body, const SourceRange& body_range,
Scope* inner_scope, const ForInfo& for_info, bool* ok) {
// See Parser::DesugarLexicalBindingsInForStatement.
if (track_unresolved_variables_) {
for (auto name : for_info.bound_names) {
......
......@@ -17,10 +17,13 @@ function TestCoverage(name, source, expectation) {
source = source.trim();
eval(source);
%CollectGarbage("collect dead objects");
var coverage = GetCoverage(source);
var result = JSON.stringify(coverage);
print(result);
assertEquals(JSON.stringify(expectation), result, name + " failed");
var covfefe = GetCoverage(source);
var stringified_result = JSON.stringify(covfefe);
var stringified_expectation = JSON.stringify(expectation);
if (stringified_result != stringified_expectation) {
print(JSON.stringify(covfefe, undefined, 1));
}
assertEquals(stringified_expectation, stringified_result, name + " failed");
}
%DebugToggleBlockCoverage(true);
......@@ -35,13 +38,8 @@ TestCoverage(
TestCoverage(
"call locally allocated function",
`
for (var i = 0; i < 10; i++) {
let f = () => 1;
i += f();
}
`,
[{"start":0,"end":63,"count":1},{"start":41,"end":48,"count":5}]
`let f = () => 1; f();`,
[{"start":0,"end":21,"count":1},{"start":8,"end":15,"count":1}]
);
TestCoverage(
......@@ -80,4 +78,83 @@ f(43);
{"start":236,"end":241,"count":2}]
);
TestCoverage(
"for statements",
`
function g() {}
!function() {
for (var i = 0; i < 12; i++) g();
for (var i = 0; i < 12; i++) {
g();
}
for (var i = 0; false; i++) g();
for (var i = 0; true; i++) break;
for (var i = 0; i < 12; i++) {
if (i % 3 == 0) g(); else g();
}
}();
`,
[{"start":0,"end":259,"count":1},
{"start":0,"end":15,"count":36},
{"start":17,"end":256,"count":1},
{"start":59,"end":64,"count":12},
{"start":95,"end":110,"count":12},
{"start":140,"end":145,"count":0},
{"start":174,"end":181,"count":1},
{"start":212,"end":253,"count":12},
{"start":234,"end":239,"count":4},
{"start":241,"end":249,"count":8}]
);
TestCoverage(
"for statements pt. 2",
`
function g() {}
!function() {
let j = 0;
for (let i = 0; i < 12; i++) g();
for (const i = 0; j < 12; j++) g();
for (j = 0; j < 12; j++) g();
for (;;) break;
}();
`,
[{"start":0,"end":171,"count":1},
{"start":0,"end":15,"count":36},
{"start":17,"end":168,"count":1},
{"start":72,"end":77,"count":12},
{"start":110,"end":115,"count":12},
{"start":142,"end":147,"count":12},
{"start":158,"end":165,"count":1}]
);
TestCoverage(
"while and do-while statements",
`
function g() {}
!function() {
var i;
i = 0; while (i < 12) i++;
i = 0; while (i < 12) { g(); i++; }
i = 0; while (false) g();
i = 0; while (true) break;
i = 0; do i++; while (i < 12);
i = 0; do { g(); i++; } while (i < 12);
i = 0; do { g(); } while (false);
i = 0; do { break; } while (true);
}();
`,
[{"start":0,"end":316,"count":1},
{"start":0,"end":15,"count":25},
{"start":17,"end":313,"count":1},
{"start":61,"end":66,"count":12},
{"start":90,"end":104,"count":12},
{"start":127,"end":132,"count":0},
{"start":154,"end":161,"count":1},
{"start":173,"end":179,"count":12},
{"start":206,"end":221,"count":12},
{"start":248,"end":258,"count":1},
{"start":284,"end":296,"count":1}]
);
%DebugToggleBlockCoverage(false);
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