Skip to content

Commit 50a3bf6

Browse files
liamappelbeCommit Queue
authored andcommitted
[vm] Improve const constructor coverage
The way coverage collection was implemented for const constructors was a bit of a hack. After ordinary coverage collection was complete and all ordinary source ranges had been added to the report, SourceReport::CollectConstConstructorCoverageFromScripts would iterate through all the const constructors and add extra ranges to the source report that reported them as being hit. The old ranges were still in the report, reporting the constructors as missed, but the miss was overwritten by the hit when package:coverage turned the source report into a coverage report. That hacky approach didn't work for branch coverage. I've refactored it to gather all those const constructor hits before ordinary coverage gathering, and store the hits in the script table. Then during the ordinary coverage collection flow, when we see one of those const constructors, we mark everything inside the function as hit. This also fixes a related bug that we hadn't noticed before, where calls inside the const constructor were treated as missed. Note: If there are dozens of const constructors in a single script, it might be worth sorting ScriptTableEntry.const_constructor_hits and then binary searching it, but I don't think that's worth doing atm. Bug: dart-lang/tools#513 Fixes: dart-lang/tools#513 Change-Id: Ie66a153fbeaac5b3ecfc9a28a7f8d129ab61114a TEST=source_report_test.cc Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/421720 Reviewed-by: Ben Konyi <[email protected]> Commit-Queue: Liam Appelbe <[email protected]>
1 parent 4683a0c commit 50a3bf6

File tree

4 files changed

+162
-40
lines changed

4 files changed

+162
-40
lines changed

pkg/vm_service/test/get_source_report_const_coverage_test.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ final tests = <IsolateTest>[
8787
final coverage = await service.getSourceReport(
8888
isolateId,
8989
[SourceReportKind.kCoverage],
90+
forceCompile: true,
9091
);
9192
hits = getHitsFor(coverage, filename);
9293
final lines = <int>{};
@@ -106,6 +107,7 @@ final tests = <IsolateTest>[
106107
isolateId,
107108
[SourceReportKind.kCoverage],
108109
scriptId: foundScript.id!,
110+
forceCompile: true,
109111
);
110112
final localHits = getHitsFor(coverage, filename);
111113
expect(localHits.length, hits.length);

runtime/vm/source_report.cc

Lines changed: 33 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ intptr_t SourceReport::GetTokenPosOrLine(const Script& script,
331331
}
332332

333333
void SourceReport::PrintCoverageData(JSONObject* jsobj,
334+
intptr_t script_index,
334335
const Function& function,
335336
const Code& code,
336337
bool report_branch_coverage) {
@@ -340,6 +341,17 @@ void SourceReport::PrintCoverageData(JSONObject* jsobj,
340341

341342
const Script& script = Script::Handle(zone(), function.script());
342343

344+
bool const_constructor_hit = false;
345+
if (function.IsConstructor() && function.is_const()) {
346+
for (TokenPosition hit :
347+
script_table_entries_[script_index]->const_constructor_hits) {
348+
if (hit == begin_pos) {
349+
const_constructor_hit = true;
350+
break;
351+
}
352+
}
353+
}
354+
343355
const int kCoverageNone = 0;
344356
const int kCoverageMiss = 1;
345357
const int kCoverageHit = 2;
@@ -351,7 +363,7 @@ void SourceReport::PrintCoverageData(JSONObject* jsobj,
351363
coverage[i] = kCoverageNone;
352364
}
353365

354-
if (function.WasExecuted()) {
366+
if (function.WasExecuted() || const_constructor_hit) {
355367
coverage[0] = kCoverageHit;
356368
} else {
357369
coverage[0] = kCoverageMiss;
@@ -382,7 +394,7 @@ void SourceReport::PrintCoverageData(JSONObject* jsobj,
382394
if (is_branch_coverage == report_branch_coverage) {
383395
const bool was_executed =
384396
Smi::Value(Smi::RawCast(coverage_array.At(i + 1))) != 0;
385-
update_coverage(token_pos, was_executed);
397+
update_coverage(token_pos, was_executed || const_constructor_hit);
386398
}
387399
}
388400
}
@@ -564,10 +576,12 @@ void SourceReport::VisitFunction(JSONArray* jsarr,
564576
PrintCallSitesData(&range, func, code);
565577
}
566578
if (IsReportRequested(kCoverage)) {
567-
PrintCoverageData(&range, func, code, /* report_branch_coverage */ false);
579+
PrintCoverageData(&range, script_index, func, code,
580+
/* report_branch_coverage */ false);
568581
}
569582
if (IsReportRequested(kBranchCoverage)) {
570-
PrintCoverageData(&range, func, code, /* report_branch_coverage */ true);
583+
PrintCoverageData(&range, script_index, func, code,
584+
/* report_branch_coverage */ true);
571585
}
572586
if (IsReportRequested(kPossibleBreakpoints)) {
573587
PrintPossibleBreakpointsData(&range, func, code);
@@ -678,6 +692,18 @@ void SourceReport::PrintJSON(JSONStream* js,
678692
{
679693
JSONArray ranges(&report, "ranges");
680694

695+
// Output constant coverage if coverage is requested.
696+
if (IsReportRequested(kCoverage) || IsReportRequested(kBranchCoverage)) {
697+
// Find all scripts. We need to go though all scripts because a script
698+
// (even one we don't want) can add coverage to another library (i.e.
699+
// potentially one we want).
700+
DirectChainedHashMap<ScriptTableTrait> local_script_table;
701+
GrowableArray<ScriptTableEntry*> local_script_table_entries;
702+
CollectAllScripts(&local_script_table, &local_script_table_entries);
703+
CollectConstConstructorCoverageFromScripts(&local_script_table_entries);
704+
CleanupCollectedScripts(&local_script_table, &local_script_table_entries);
705+
}
706+
681707
const GrowableObjectArray& libs = GrowableObjectArray::Handle(
682708
zone(), thread()->isolate_group()->object_store()->libraries());
683709

@@ -692,19 +718,6 @@ void SourceReport::PrintJSON(JSONStream* js,
692718

693719
// Visit all closures for this isolate.
694720
VisitClosures(&ranges);
695-
696-
// Output constant coverage if coverage is requested.
697-
if (IsReportRequested(kCoverage)) {
698-
// Find all scripts. We need to go though all scripts because a script
699-
// (even one we don't want) can add coverage to another library (i.e.
700-
// potentially one we want).
701-
DirectChainedHashMap<ScriptTableTrait> local_script_table;
702-
GrowableArray<ScriptTableEntry*> local_script_table_entries;
703-
CollectAllScripts(&local_script_table, &local_script_table_entries);
704-
CollectConstConstructorCoverageFromScripts(&local_script_table_entries,
705-
&ranges);
706-
CleanupCollectedScripts(&local_script_table, &local_script_table_entries);
707-
}
708721
}
709722

710723
// Print the script table.
@@ -756,8 +769,7 @@ void SourceReport::CleanupCollectedScripts(
756769
}
757770

758771
void SourceReport::CollectConstConstructorCoverageFromScripts(
759-
GrowableArray<ScriptTableEntry*>* local_script_table_entries,
760-
JSONArray* ranges) {
772+
GrowableArray<ScriptTableEntry*>* local_script_table_entries) {
761773
// Now output the wanted constant coverage.
762774
for (intptr_t i = 0; i < local_script_table_entries->length(); i++) {
763775
const Script* script = local_script_table_entries->At(i)->script;
@@ -770,7 +782,6 @@ void SourceReport::CollectConstConstructorCoverageFromScripts(
770782
Array::Handle(script->CollectConstConstructorCoverageFrom());
771783
intptr_t constructors_count = constructors.Length();
772784
Function& constructor = Function::Handle(zone());
773-
Code& code = Code::Handle(zone());
774785
for (intptr_t i = 0; i < constructors_count; i++) {
775786
constructor ^= constructors.At(i);
776787
// Check if we want coverage for this constructor.
@@ -782,25 +793,9 @@ void SourceReport::CollectConstConstructorCoverageFromScripts(
782793
if (script_index < 0) {
783794
continue;
784795
}
785-
code ^= constructor.unoptimized_code();
786-
const TokenPosition begin_pos = constructor.token_pos();
787-
const TokenPosition end_pos = constructor.end_token_pos();
788-
JSONObject range(ranges);
789-
range.AddProperty("scriptIndex", script_index);
790-
range.AddProperty("compiled",
791-
!code.IsNull()); // Does this make a difference?
792-
range.AddProperty("startPos", begin_pos);
793-
range.AddProperty("endPos", end_pos);
794796

795-
JSONObject cov(&range, "coverage");
796-
{
797-
JSONArray hits(&cov, "hits");
798-
hits.AddValue(GetTokenPosOrLine(scriptRef, begin_pos));
799-
}
800-
{
801-
JSONArray misses(&cov, "misses");
802-
// No misses
803-
}
797+
script_table_entries_[script_index]->const_constructor_hits.Add(
798+
constructor.token_pos());
804799
}
805800
}
806801
}

runtime/vm/source_report.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class SourceReport {
8888
const Function& func,
8989
const Code& code);
9090
void PrintCoverageData(JSONObject* jsobj,
91+
intptr_t script_index,
9192
const Function& func,
9293
const Code& code,
9394
bool report_branch_coverage);
@@ -108,13 +109,15 @@ class SourceReport {
108109
CompileMode compile_mode);
109110
void VisitLibrary(JSONArray* jsarr, const Library& lib);
110111
void VisitClosures(JSONArray* jsarr);
112+
111113
// An entry in the script table.
112114
struct ScriptTableEntry {
113115
ScriptTableEntry() : key(nullptr), index(-1), script(nullptr) {}
114116

115117
const String* key;
116118
intptr_t index;
117119
const Script* script;
120+
GrowableArray<TokenPosition> const_constructor_hits;
118121
};
119122

120123
// Needed for DirectChainedHashMap.
@@ -143,8 +146,7 @@ class SourceReport {
143146
GrowableArray<ScriptTableEntry*>* local_script_table_entries);
144147

145148
void CollectConstConstructorCoverageFromScripts(
146-
GrowableArray<ScriptTableEntry*>* local_script_table_entries,
147-
JSONArray* ranges);
149+
GrowableArray<ScriptTableEntry*>* local_script_table_entries);
148150

149151
intptr_t report_set_;
150152
CompileMode compile_mode_;

runtime/vm/source_report_test.cc

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1587,6 +1587,129 @@ main() {
15871587
buffer);
15881588
}
15891589

1590+
ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_constConstructor) {
1591+
// WARNING: This MUST be big enough for the serialized JSON string.
1592+
const int kBufferSize = 1024;
1593+
char buffer[kBufferSize];
1594+
const char* kScript = R"(
1595+
abstract class SuperClass {
1596+
const SuperClass();
1597+
}
1598+
1599+
class Class extends SuperClass {
1600+
const Class() :
1601+
super();
1602+
int method() => 123;
1603+
}
1604+
1605+
main() {
1606+
const Class().method();
1607+
}
1608+
)";
1609+
1610+
Library& lib = Library::Handle();
1611+
const bool old_branch_coverage = IsolateGroup::Current()->branch_coverage();
1612+
IsolateGroup::Current()->set_branch_coverage(true);
1613+
lib ^= ExecuteScript(kScript);
1614+
IsolateGroup::Current()->set_branch_coverage(old_branch_coverage);
1615+
ASSERT(!lib.IsNull());
1616+
const Script& script =
1617+
Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));
1618+
1619+
SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
1620+
JSONStream js;
1621+
report.PrintJSON(&js, script);
1622+
const char* json_str = js.ToCString();
1623+
ASSERT(strlen(json_str) < kBufferSize);
1624+
ElideJSONSubstring("classes", json_str, buffer);
1625+
ElideJSONSubstring("libraries", buffer, buffer);
1626+
EXPECT_STREQ(
1627+
"{\"type\":\"SourceReport\",\"ranges\":["
1628+
1629+
// The super class constructor is hit.
1630+
"{\"scriptIndex\":0,\"startPos\":31,\"endPos\":49,\"compiled\":true,"
1631+
"\"coverage\":{\"hits\":[31],\"misses\":[]}},"
1632+
1633+
// The class constructor is hit, as well as the super() call.
1634+
"{\"scriptIndex\":0,\"startPos\":89,\"endPos\":118,\"compiled\":true,"
1635+
"\"coverage\":{\"hits\":[89,111],\"misses\":[]}},"
1636+
1637+
// The method is called.
1638+
"{\"scriptIndex\":0,\"startPos\":122,\"endPos\":141,\"compiled\":true,"
1639+
"\"coverage\":{\"hits\":[122],\"misses\":[]}},"
1640+
1641+
// Main is hit.
1642+
"{\"scriptIndex\":0,\"startPos\":146,\"endPos\":181,\"compiled\":true,"
1643+
"\"coverage\":{\"hits\":[146,171],\"misses\":[]}}],"
1644+
1645+
// Only one script in the script table.
1646+
"\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,"
1647+
"\"id\":\"\",\"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
1648+
buffer);
1649+
}
1650+
1651+
ISOLATE_UNIT_TEST_CASE(SourceReport_BranchCoverage_constConstructor) {
1652+
// WARNING: This MUST be big enough for the serialized JSON string.
1653+
const int kBufferSize = 1024;
1654+
char buffer[kBufferSize];
1655+
const char* kScript = R"(
1656+
abstract class SuperClass {
1657+
const SuperClass();
1658+
}
1659+
1660+
class Class extends SuperClass {
1661+
const Class() :
1662+
super();
1663+
int method() => 123;
1664+
}
1665+
1666+
main() {
1667+
const Class().method();
1668+
}
1669+
)";
1670+
1671+
Library& lib = Library::Handle();
1672+
const bool old_branch_coverage = IsolateGroup::Current()->branch_coverage();
1673+
IsolateGroup::Current()->set_branch_coverage(true);
1674+
lib ^= ExecuteScript(kScript);
1675+
IsolateGroup::Current()->set_branch_coverage(old_branch_coverage);
1676+
ASSERT(!lib.IsNull());
1677+
const Script& script =
1678+
Script::Handle(lib.LookupScript(String::Handle(String::New("test-lib"))));
1679+
1680+
SourceReport report(SourceReport::kBranchCoverage,
1681+
SourceReport::kForceCompile);
1682+
JSONStream js;
1683+
report.PrintJSON(&js, script);
1684+
const char* json_str = js.ToCString();
1685+
ASSERT(strlen(json_str) < kBufferSize);
1686+
ElideJSONSubstring("classes", json_str, buffer);
1687+
ElideJSONSubstring("libraries", buffer, buffer);
1688+
EXPECT_STREQ(
1689+
"{\"type\":\"SourceReport\",\"ranges\":["
1690+
1691+
// The super class constructor is hit.
1692+
"{\"scriptIndex\":0,\"startPos\":31,\"endPos\":49,\"compiled\":true,"
1693+
"\"branchCoverage\":{\"hits\":[31],\"misses\":[]}},"
1694+
1695+
// The class constructor is hit.
1696+
"{\"scriptIndex\":0,\"startPos\":89,\"endPos\":118,\"compiled\":true,"
1697+
"\"branchCoverage\":{\"hits\":[89],\"misses\":[]}},"
1698+
1699+
// The method is called.
1700+
"{\"scriptIndex\":0,\"startPos\":122,\"endPos\":141,\"compiled\":true,"
1701+
"\"branchCoverage\":{\"hits\":[122],\"misses\":[]}},"
1702+
1703+
// Main is hit.
1704+
"{\"scriptIndex\":0,\"startPos\":146,\"endPos\":181,\"compiled\":true,"
1705+
"\"branchCoverage\":{\"hits\":[146],\"misses\":[]}}],"
1706+
1707+
// Only one script in the script table.
1708+
"\"scripts\":[{\"type\":\"@Script\",\"fixedId\":true,\"id\":\"\",\""
1709+
"uri\":\"file:\\/\\/\\/test-lib\",\"_kind\":\"kernel\"}]}",
1710+
buffer);
1711+
}
1712+
15901713
#endif // !PRODUCT
15911714

15921715
} // namespace dart

0 commit comments

Comments
 (0)