1// Copyright 2017 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "src/debug/debug-coverage.h" 6 7#include "src/ast/ast-source-ranges.h" 8#include "src/ast/ast.h" 9#include "src/base/hashmap.h" 10#include "src/common/assert-scope.h" 11#include "src/common/globals.h" 12#include "src/debug/debug.h" 13#include "src/deoptimizer/deoptimizer.h" 14#include "src/execution/frames-inl.h" 15#include "src/execution/isolate.h" 16#include "src/objects/debug-objects-inl.h" 17#include "src/objects/objects.h" 18 19namespace v8 { 20namespace internal { 21 22class SharedToCounterMap 23 : public base::TemplateHashMapImpl<SharedFunctionInfo, uint32_t, 24 base::KeyEqualityMatcher<Object>, 25 base::DefaultAllocationPolicy> { 26 public: 27 using Entry = base::TemplateHashMapEntry<SharedFunctionInfo, uint32_t>; 28 inline void Add(SharedFunctionInfo key, uint32_t count) { 29 Entry* entry = LookupOrInsert(key, Hash(key), []() { return 0; }); 30 uint32_t old_count = entry->value; 31 if (UINT32_MAX - count < old_count) { 32 entry->value = UINT32_MAX; 33 } else { 34 entry->value = old_count + count; 35 } 36 } 37 38 inline uint32_t Get(SharedFunctionInfo key) { 39 Entry* entry = Lookup(key, Hash(key)); 40 if (entry == nullptr) return 0; 41 return entry->value; 42 } 43 44 private: 45 static uint32_t Hash(SharedFunctionInfo key) { 46 return static_cast<uint32_t>(key.ptr()); 47 } 48 49 DISALLOW_GARBAGE_COLLECTION(no_gc) 50}; 51 52namespace { 53int StartPosition(SharedFunctionInfo info) { 54 int start = info.function_token_position(); 55 if (start == kNoSourcePosition) start = info.StartPosition(); 56 return start; 57} 58 59bool CompareCoverageBlock(const CoverageBlock& a, const CoverageBlock& b) { 60 DCHECK_NE(kNoSourcePosition, a.start); 61 DCHECK_NE(kNoSourcePosition, b.start); 62 if (a.start == b.start) return a.end > b.end; 63 return a.start < b.start; 64} 65 66void SortBlockData(std::vector<CoverageBlock>& v) { 67 // Sort according to the block nesting structure. 68 std::sort(v.begin(), v.end(), CompareCoverageBlock); 69} 70 71std::vector<CoverageBlock> GetSortedBlockData(SharedFunctionInfo shared) { 72 DCHECK(shared.HasCoverageInfo()); 73 74 CoverageInfo coverage_info = 75 CoverageInfo::cast(shared.GetDebugInfo().coverage_info()); 76 77 std::vector<CoverageBlock> result; 78 if (coverage_info.slot_count() == 0) return result; 79 80 for (int i = 0; i < coverage_info.slot_count(); i++) { 81 const int start_pos = coverage_info.slots_start_source_position(i); 82 const int until_pos = coverage_info.slots_end_source_position(i); 83 const int count = coverage_info.slots_block_count(i); 84 85 DCHECK_NE(kNoSourcePosition, start_pos); 86 result.emplace_back(start_pos, until_pos, count); 87 } 88 89 SortBlockData(result); 90 91 return result; 92} 93 94// A utility class to simplify logic for performing passes over block coverage 95// ranges. Provides access to the implicit tree structure of ranges (i.e. access 96// to parent and sibling blocks), and supports efficient in-place editing and 97// deletion. The underlying backing store is the array of CoverageBlocks stored 98// on the CoverageFunction. 99class CoverageBlockIterator final { 100 public: 101 explicit CoverageBlockIterator(CoverageFunction* function) 102 : function_(function) { 103 DCHECK(std::is_sorted(function_->blocks.begin(), function_->blocks.end(), 104 CompareCoverageBlock)); 105 } 106 107 ~CoverageBlockIterator() { 108 Finalize(); 109 DCHECK(std::is_sorted(function_->blocks.begin(), function_->blocks.end(), 110 CompareCoverageBlock)); 111 } 112 113 bool HasNext() const { 114 return read_index_ + 1 < static_cast<int>(function_->blocks.size()); 115 } 116 117 bool Next() { 118 if (!HasNext()) { 119 if (!ended_) MaybeWriteCurrent(); 120 ended_ = true; 121 return false; 122 } 123 124 // If a block has been deleted, subsequent iteration moves trailing blocks 125 // to their updated position within the array. 126 MaybeWriteCurrent(); 127 128 if (read_index_ == -1) { 129 // Initialize the nesting stack with the function range. 130 nesting_stack_.emplace_back(function_->start, function_->end, 131 function_->count); 132 } else if (!delete_current_) { 133 nesting_stack_.emplace_back(GetBlock()); 134 } 135 136 delete_current_ = false; 137 read_index_++; 138 139 DCHECK(IsActive()); 140 141 CoverageBlock& block = GetBlock(); 142 while (nesting_stack_.size() > 1 && 143 nesting_stack_.back().end <= block.start) { 144 nesting_stack_.pop_back(); 145 } 146 147 DCHECK_IMPLIES(block.start >= function_->end, 148 block.end == kNoSourcePosition); 149 DCHECK_NE(block.start, kNoSourcePosition); 150 DCHECK_LE(block.end, GetParent().end); 151 152 return true; 153 } 154 155 CoverageBlock& GetBlock() { 156 DCHECK(IsActive()); 157 return function_->blocks[read_index_]; 158 } 159 160 CoverageBlock& GetNextBlock() { 161 DCHECK(IsActive()); 162 DCHECK(HasNext()); 163 return function_->blocks[read_index_ + 1]; 164 } 165 166 CoverageBlock& GetPreviousBlock() { 167 DCHECK(IsActive()); 168 DCHECK_GT(read_index_, 0); 169 return function_->blocks[read_index_ - 1]; 170 } 171 172 CoverageBlock& GetParent() { 173 DCHECK(IsActive()); 174 return nesting_stack_.back(); 175 } 176 177 bool HasSiblingOrChild() { 178 DCHECK(IsActive()); 179 return HasNext() && GetNextBlock().start < GetParent().end; 180 } 181 182 CoverageBlock& GetSiblingOrChild() { 183 DCHECK(HasSiblingOrChild()); 184 DCHECK(IsActive()); 185 return GetNextBlock(); 186 } 187 188 // A range is considered to be at top level if its parent range is the 189 // function range. 190 bool IsTopLevel() const { return nesting_stack_.size() == 1; } 191 192 void DeleteBlock() { 193 DCHECK(!delete_current_); 194 DCHECK(IsActive()); 195 delete_current_ = true; 196 } 197 198 private: 199 void MaybeWriteCurrent() { 200 if (delete_current_) return; 201 if (read_index_ >= 0 && write_index_ != read_index_) { 202 function_->blocks[write_index_] = function_->blocks[read_index_]; 203 } 204 write_index_++; 205 } 206 207 void Finalize() { 208 while (Next()) { 209 // Just iterate to the end. 210 } 211 function_->blocks.resize(write_index_); 212 } 213 214 bool IsActive() const { return read_index_ >= 0 && !ended_; } 215 216 CoverageFunction* function_; 217 std::vector<CoverageBlock> nesting_stack_; 218 bool ended_ = false; 219 bool delete_current_ = false; 220 int read_index_ = -1; 221 int write_index_ = -1; 222}; 223 224bool HaveSameSourceRange(const CoverageBlock& lhs, const CoverageBlock& rhs) { 225 return lhs.start == rhs.start && lhs.end == rhs.end; 226} 227 228void MergeDuplicateRanges(CoverageFunction* function) { 229 CoverageBlockIterator iter(function); 230 231 while (iter.Next() && iter.HasNext()) { 232 CoverageBlock& block = iter.GetBlock(); 233 CoverageBlock& next_block = iter.GetNextBlock(); 234 235 if (!HaveSameSourceRange(block, next_block)) continue; 236 237 DCHECK_NE(kNoSourcePosition, block.end); // Non-singleton range. 238 next_block.count = std::max(block.count, next_block.count); 239 iter.DeleteBlock(); 240 } 241} 242 243// Rewrite position singletons (produced by unconditional control flow 244// like return statements, and by continuation counters) into source 245// ranges that end at the next sibling range or the end of the parent 246// range, whichever comes first. 247void RewritePositionSingletonsToRanges(CoverageFunction* function) { 248 CoverageBlockIterator iter(function); 249 250 while (iter.Next()) { 251 CoverageBlock& block = iter.GetBlock(); 252 CoverageBlock& parent = iter.GetParent(); 253 254 if (block.start >= function->end) { 255 DCHECK_EQ(block.end, kNoSourcePosition); 256 iter.DeleteBlock(); 257 } else if (block.end == kNoSourcePosition) { 258 // The current block ends at the next sibling block (if it exists) or the 259 // end of the parent block otherwise. 260 if (iter.HasSiblingOrChild()) { 261 block.end = iter.GetSiblingOrChild().start; 262 } else if (iter.IsTopLevel()) { 263 // See https://crbug.com/v8/6661. Functions are special-cased because 264 // we never want the closing brace to be uncovered. This is mainly to 265 // avoid a noisy UI. 266 block.end = parent.end - 1; 267 } else { 268 block.end = parent.end; 269 } 270 } 271 } 272} 273 274void MergeConsecutiveRanges(CoverageFunction* function) { 275 CoverageBlockIterator iter(function); 276 277 while (iter.Next()) { 278 CoverageBlock& block = iter.GetBlock(); 279 280 if (iter.HasSiblingOrChild()) { 281 CoverageBlock& sibling = iter.GetSiblingOrChild(); 282 if (sibling.start == block.end && sibling.count == block.count) { 283 // Best-effort: this pass may miss mergeable siblings in the presence of 284 // child blocks. 285 sibling.start = block.start; 286 iter.DeleteBlock(); 287 } 288 } 289 } 290} 291 292void MergeNestedRanges(CoverageFunction* function) { 293 CoverageBlockIterator iter(function); 294 295 while (iter.Next()) { 296 CoverageBlock& block = iter.GetBlock(); 297 CoverageBlock& parent = iter.GetParent(); 298 299 if (parent.count == block.count) { 300 // Transformation may not be valid if sibling blocks exist with a 301 // differing count. 302 iter.DeleteBlock(); 303 } 304 } 305} 306 307void RewriteFunctionScopeCounter(CoverageFunction* function) { 308 // Every function must have at least the top-level function counter. 309 DCHECK(!function->blocks.empty()); 310 311 CoverageBlockIterator iter(function); 312 if (iter.Next()) { 313 DCHECK(iter.IsTopLevel()); 314 315 CoverageBlock& block = iter.GetBlock(); 316 if (block.start == SourceRange::kFunctionLiteralSourcePosition && 317 block.end == SourceRange::kFunctionLiteralSourcePosition) { 318 // If a function-scope block exists, overwrite the function count. It has 319 // a more reliable count than what we get from the FeedbackVector (which 320 // is imprecise e.g. for generator functions and optimized code). 321 function->count = block.count; 322 323 // Then delete it; for compatibility with non-block coverage modes, the 324 // function-scope block is expected in CoverageFunction, not as a 325 // CoverageBlock. 326 iter.DeleteBlock(); 327 } 328 } 329} 330 331void FilterAliasedSingletons(CoverageFunction* function) { 332 CoverageBlockIterator iter(function); 333 334 iter.Next(); // Advance once since we reference the previous block later. 335 336 while (iter.Next()) { 337 CoverageBlock& previous_block = iter.GetPreviousBlock(); 338 CoverageBlock& block = iter.GetBlock(); 339 340 bool is_singleton = block.end == kNoSourcePosition; 341 bool aliases_start = block.start == previous_block.start; 342 343 if (is_singleton && aliases_start) { 344 // The previous block must have a full range since duplicate singletons 345 // have already been merged. 346 DCHECK_NE(previous_block.end, kNoSourcePosition); 347 // Likewise, the next block must have another start position since 348 // singletons are sorted to the end. 349 DCHECK_IMPLIES(iter.HasNext(), iter.GetNextBlock().start != block.start); 350 iter.DeleteBlock(); 351 } 352 } 353} 354 355void FilterUncoveredRanges(CoverageFunction* function) { 356 CoverageBlockIterator iter(function); 357 358 while (iter.Next()) { 359 CoverageBlock& block = iter.GetBlock(); 360 CoverageBlock& parent = iter.GetParent(); 361 if (block.count == 0 && parent.count == 0) iter.DeleteBlock(); 362 } 363} 364 365void FilterEmptyRanges(CoverageFunction* function) { 366 CoverageBlockIterator iter(function); 367 368 while (iter.Next()) { 369 CoverageBlock& block = iter.GetBlock(); 370 if (block.start == block.end) iter.DeleteBlock(); 371 } 372} 373 374void ClampToBinary(CoverageFunction* function) { 375 CoverageBlockIterator iter(function); 376 377 while (iter.Next()) { 378 CoverageBlock& block = iter.GetBlock(); 379 if (block.count > 0) block.count = 1; 380 } 381} 382 383void ResetAllBlockCounts(SharedFunctionInfo shared) { 384 DCHECK(shared.HasCoverageInfo()); 385 386 CoverageInfo coverage_info = 387 CoverageInfo::cast(shared.GetDebugInfo().coverage_info()); 388 389 for (int i = 0; i < coverage_info.slot_count(); i++) { 390 coverage_info.ResetBlockCount(i); 391 } 392} 393 394bool IsBlockMode(debug::CoverageMode mode) { 395 switch (mode) { 396 case debug::CoverageMode::kBlockBinary: 397 case debug::CoverageMode::kBlockCount: 398 return true; 399 default: 400 return false; 401 } 402} 403 404bool IsBinaryMode(debug::CoverageMode mode) { 405 switch (mode) { 406 case debug::CoverageMode::kBlockBinary: 407 case debug::CoverageMode::kPreciseBinary: 408 return true; 409 default: 410 return false; 411 } 412} 413 414void CollectBlockCoverageInternal(CoverageFunction* function, 415 SharedFunctionInfo info, 416 debug::CoverageMode mode) { 417 DCHECK(IsBlockMode(mode)); 418 419 // Functions with empty source ranges are not interesting to report. This can 420 // happen e.g. for internally-generated functions like class constructors. 421 if (!function->HasNonEmptySourceRange()) return; 422 423 function->has_block_coverage = true; 424 function->blocks = GetSortedBlockData(info); 425 426 // If in binary mode, only report counts of 0/1. 427 if (mode == debug::CoverageMode::kBlockBinary) ClampToBinary(function); 428 429 // To stay compatible with non-block coverage modes, the function-scope count 430 // is expected to be in the CoverageFunction, not as part of its blocks. 431 // This finds the function-scope counter, overwrites CoverageFunction::count, 432 // and removes it from the block list. 433 // 434 // Important: Must be called before other transformation passes. 435 RewriteFunctionScopeCounter(function); 436 437 // Functions without blocks don't need to be processed further. 438 if (!function->HasBlocks()) return; 439 440 // Remove singleton ranges with the same start position as a full range and 441 // throw away their counts. 442 // Singleton ranges are only intended to split existing full ranges and should 443 // never expand into a full range. Consider 'if (cond) { ... } else { ... }' 444 // as a problematic example; if the then-block produces a continuation 445 // singleton, it would incorrectly expand into the else range. 446 // For more context, see https://crbug.com/v8/8237. 447 FilterAliasedSingletons(function); 448 449 // Rewrite all singletons (created e.g. by continuations and unconditional 450 // control flow) to ranges. 451 RewritePositionSingletonsToRanges(function); 452 453 // Merge nested and consecutive ranges with identical counts. 454 // Note that it's necessary to merge duplicate ranges prior to merging nested 455 // changes in order to avoid invalid transformations. See crbug.com/827530. 456 MergeConsecutiveRanges(function); 457 458 SortBlockData(function->blocks); 459 MergeDuplicateRanges(function); 460 MergeNestedRanges(function); 461 462 MergeConsecutiveRanges(function); 463 464 // Filter out ranges with count == 0 unless the immediate parent range has 465 // a count != 0. 466 FilterUncoveredRanges(function); 467 468 // Filter out ranges of zero length. 469 FilterEmptyRanges(function); 470} 471 472void CollectBlockCoverage(CoverageFunction* function, SharedFunctionInfo info, 473 debug::CoverageMode mode) { 474 CollectBlockCoverageInternal(function, info, mode); 475 476 // Reset all counters on the DebugInfo to zero. 477 ResetAllBlockCounts(info); 478} 479 480void PrintBlockCoverage(const CoverageFunction* function, 481 SharedFunctionInfo info, bool has_nonempty_source_range, 482 bool function_is_relevant) { 483 DCHECK(FLAG_trace_block_coverage); 484 std::unique_ptr<char[]> function_name = 485 function->name->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL); 486 i::PrintF( 487 "Coverage for function='%s', SFI=%p, has_nonempty_source_range=%d, " 488 "function_is_relevant=%d\n", 489 function_name.get(), reinterpret_cast<void*>(info.ptr()), 490 has_nonempty_source_range, function_is_relevant); 491 i::PrintF("{start: %d, end: %d, count: %d}\n", function->start, function->end, 492 function->count); 493 for (const auto& block : function->blocks) { 494 i::PrintF("{start: %d, end: %d, count: %d}\n", block.start, block.end, 495 block.count); 496 } 497} 498 499void CollectAndMaybeResetCounts(Isolate* isolate, 500 SharedToCounterMap* counter_map, 501 v8::debug::CoverageMode coverage_mode) { 502 const bool reset_count = 503 coverage_mode != v8::debug::CoverageMode::kBestEffort; 504 505 switch (isolate->code_coverage_mode()) { 506 case v8::debug::CoverageMode::kBlockBinary: 507 case v8::debug::CoverageMode::kBlockCount: 508 case v8::debug::CoverageMode::kPreciseBinary: 509 case v8::debug::CoverageMode::kPreciseCount: { 510 // Feedback vectors are already listed to prevent losing them to GC. 511 DCHECK(isolate->factory() 512 ->feedback_vectors_for_profiling_tools() 513 ->IsArrayList()); 514 Handle<ArrayList> list = Handle<ArrayList>::cast( 515 isolate->factory()->feedback_vectors_for_profiling_tools()); 516 for (int i = 0; i < list->Length(); i++) { 517 FeedbackVector vector = FeedbackVector::cast(list->Get(i)); 518 SharedFunctionInfo shared = vector.shared_function_info(); 519 DCHECK(shared.IsSubjectToDebugging()); 520 uint32_t count = static_cast<uint32_t>(vector.invocation_count()); 521 if (reset_count) vector.clear_invocation_count(kRelaxedStore); 522 counter_map->Add(shared, count); 523 } 524 break; 525 } 526 case v8::debug::CoverageMode::kBestEffort: { 527 DCHECK(!isolate->factory() 528 ->feedback_vectors_for_profiling_tools() 529 ->IsArrayList()); 530 DCHECK_EQ(v8::debug::CoverageMode::kBestEffort, coverage_mode); 531 AllowGarbageCollection allow_gc; 532 HeapObjectIterator heap_iterator(isolate->heap()); 533 for (HeapObject current_obj = heap_iterator.Next(); 534 !current_obj.is_null(); current_obj = heap_iterator.Next()) { 535 if (!current_obj.IsJSFunction()) continue; 536 JSFunction func = JSFunction::cast(current_obj); 537 SharedFunctionInfo shared = func.shared(); 538 if (!shared.IsSubjectToDebugging()) continue; 539 if (!(func.has_feedback_vector() || 540 func.has_closure_feedback_cell_array())) { 541 continue; 542 } 543 uint32_t count = 0; 544 if (func.has_feedback_vector()) { 545 count = 546 static_cast<uint32_t>(func.feedback_vector().invocation_count()); 547 } else if (func.raw_feedback_cell().interrupt_budget() < 548 FLAG_interrupt_budget_for_feedback_allocation) { 549 // TODO(jgruber): The condition above is no longer precise since we 550 // may use either the fixed interrupt_budget or 551 // FLAG_interrupt_budget_factor_for_feedback_allocation. If the 552 // latter, we may incorrectly set a count of 1. 553 // 554 // We haven't allocated feedback vector, but executed the function 555 // atleast once. We don't have precise invocation count here. 556 count = 1; 557 } 558 counter_map->Add(shared, count); 559 } 560 561 // Also check functions on the stack to collect the count map. With lazy 562 // feedback allocation we may miss counting functions if the feedback 563 // vector wasn't allocated yet and the function's interrupt budget wasn't 564 // updated (i.e. it didn't execute return / jump). 565 for (JavaScriptFrameIterator it(isolate); !it.done(); it.Advance()) { 566 SharedFunctionInfo shared = it.frame()->function().shared(); 567 if (counter_map->Get(shared) != 0) continue; 568 counter_map->Add(shared, 1); 569 } 570 break; 571 } 572 } 573} 574 575// A {SFI, count} tuple is used to sort by source range (stored on 576// the SFI) and call count (in the counter map). 577struct SharedFunctionInfoAndCount { 578 SharedFunctionInfoAndCount(Handle<SharedFunctionInfo> info, uint32_t count) 579 : info(info), 580 count(count), 581 start(StartPosition(*info)), 582 end(info->EndPosition()) {} 583 584 // Sort by: 585 // - start, ascending. 586 // - end, descending. 587 // - info.is_toplevel() first 588 // - count, descending. 589 bool operator<(const SharedFunctionInfoAndCount& that) const { 590 if (this->start != that.start) return this->start < that.start; 591 if (this->end != that.end) return this->end > that.end; 592 if (this->info->is_toplevel() != that.info->is_toplevel()) { 593 return this->info->is_toplevel(); 594 } 595 return this->count > that.count; 596 } 597 598 Handle<SharedFunctionInfo> info; 599 uint32_t count; 600 int start; 601 int end; 602}; 603 604} // anonymous namespace 605 606std::unique_ptr<Coverage> Coverage::CollectPrecise(Isolate* isolate) { 607 DCHECK(!isolate->is_best_effort_code_coverage()); 608 std::unique_ptr<Coverage> result = 609 Collect(isolate, isolate->code_coverage_mode()); 610 if (!isolate->is_collecting_type_profile() && 611 (isolate->is_precise_binary_code_coverage() || 612 isolate->is_block_binary_code_coverage())) { 613 // We do not have to hold onto feedback vectors for invocations we already 614 // reported. So we can reset the list. 615 isolate->SetFeedbackVectorsForProfilingTools( 616 ReadOnlyRoots(isolate).empty_array_list()); 617 } 618 return result; 619} 620 621std::unique_ptr<Coverage> Coverage::CollectBestEffort(Isolate* isolate) { 622 return Collect(isolate, v8::debug::CoverageMode::kBestEffort); 623} 624 625std::unique_ptr<Coverage> Coverage::Collect( 626 Isolate* isolate, v8::debug::CoverageMode collectionMode) { 627 // Collect call counts for all functions. 628 SharedToCounterMap counter_map; 629 CollectAndMaybeResetCounts(isolate, &counter_map, collectionMode); 630 631 // Iterate shared function infos of every script and build a mapping 632 // between source ranges and invocation counts. 633 std::unique_ptr<Coverage> result(new Coverage()); 634 635 std::vector<Handle<Script>> scripts; 636 Script::Iterator scriptIt(isolate); 637 for (Script script = scriptIt.Next(); !script.is_null(); 638 script = scriptIt.Next()) { 639 if (script.IsUserJavaScript()) scripts.push_back(handle(script, isolate)); 640 } 641 642 for (Handle<Script> script : scripts) { 643 // Create and add new script data. 644 result->emplace_back(script); 645 std::vector<CoverageFunction>* functions = &result->back().functions; 646 647 std::vector<SharedFunctionInfoAndCount> sorted; 648 649 { 650 // Sort functions by start position, from outer to inner functions. 651 SharedFunctionInfo::ScriptIterator infos(isolate, *script); 652 for (SharedFunctionInfo info = infos.Next(); !info.is_null(); 653 info = infos.Next()) { 654 sorted.emplace_back(handle(info, isolate), counter_map.Get(info)); 655 } 656 std::sort(sorted.begin(), sorted.end()); 657 } 658 659 // Stack to track nested functions, referring function by index. 660 std::vector<size_t> nesting; 661 662 // Use sorted list to reconstruct function nesting. 663 for (const SharedFunctionInfoAndCount& v : sorted) { 664 Handle<SharedFunctionInfo> info = v.info; 665 int start = v.start; 666 int end = v.end; 667 uint32_t count = v.count; 668 669 // Find the correct outer function based on start position. 670 // 671 // This is, in general, not robust when considering two functions with 672 // identical source ranges; then the notion of inner and outer is unclear. 673 // Identical source ranges arise when the source range of top-most entity 674 // (e.g. function) in the script is identical to the whole script, e.g. 675 // <script>function foo() {}<script>. The script has its own shared 676 // function info, which has the same source range as the SFI for `foo`. 677 // Node.js creates an additional wrapper for scripts (again with identical 678 // source range) and those wrappers will have a call count of zero even if 679 // the wrapped script was executed (see v8:9212). We mitigate this issue 680 // by sorting top-level SFIs first among SFIs with the same source range: 681 // This ensures top-level SFIs are processed first. If a top-level SFI has 682 // a non-zero call count, it gets recorded due to `function_is_relevant` 683 // below (e.g. script wrappers), while top-level SFIs with zero call count 684 // do not get reported (this ensures node's extra wrappers do not get 685 // reported). If two SFIs with identical source ranges get reported, we 686 // report them in decreasing order of call count, as in all known cases 687 // this corresponds to the nesting order. In the case of the script tag 688 // example above, we report the zero call count of `foo` last. As it turns 689 // out, embedders started to rely on functions being reported in nesting 690 // order. 691 // TODO(jgruber): Investigate whether it is possible to remove node's 692 // extra top-level wrapper script, or change its source range, or ensure 693 // that it follows the invariant that nesting order is descending count 694 // order for SFIs with identical source ranges. 695 while (!nesting.empty() && functions->at(nesting.back()).end <= start) { 696 nesting.pop_back(); 697 } 698 699 if (count != 0) { 700 switch (collectionMode) { 701 case v8::debug::CoverageMode::kBlockCount: 702 case v8::debug::CoverageMode::kPreciseCount: 703 break; 704 case v8::debug::CoverageMode::kBlockBinary: 705 case v8::debug::CoverageMode::kPreciseBinary: 706 count = info->has_reported_binary_coverage() ? 0 : 1; 707 info->set_has_reported_binary_coverage(true); 708 break; 709 case v8::debug::CoverageMode::kBestEffort: 710 count = 1; 711 break; 712 } 713 } 714 715 Handle<String> name = SharedFunctionInfo::DebugName(info); 716 CoverageFunction function(start, end, count, name); 717 718 if (IsBlockMode(collectionMode) && info->HasCoverageInfo()) { 719 CollectBlockCoverage(&function, *info, collectionMode); 720 } 721 722 // Only include a function range if itself or its parent function is 723 // covered, or if it contains non-trivial block coverage. 724 bool is_covered = (count != 0); 725 bool parent_is_covered = 726 (!nesting.empty() && functions->at(nesting.back()).count != 0); 727 bool has_block_coverage = !function.blocks.empty(); 728 bool function_is_relevant = 729 (is_covered || parent_is_covered || has_block_coverage); 730 731 // It must also have a non-empty source range (otherwise it is not 732 // interesting to report). 733 bool has_nonempty_source_range = function.HasNonEmptySourceRange(); 734 735 if (has_nonempty_source_range && function_is_relevant) { 736 nesting.push_back(functions->size()); 737 functions->emplace_back(function); 738 } 739 740 if (FLAG_trace_block_coverage) { 741 PrintBlockCoverage(&function, *info, has_nonempty_source_range, 742 function_is_relevant); 743 } 744 } 745 746 // Remove entries for scripts that have no coverage. 747 if (functions->empty()) result->pop_back(); 748 } 749 return result; 750} 751 752void Coverage::SelectMode(Isolate* isolate, debug::CoverageMode mode) { 753 if (mode != isolate->code_coverage_mode()) { 754 // Changing the coverage mode can change the bytecode that would be 755 // generated for a function, which can interfere with lazy source positions, 756 // so just force source position collection whenever there's such a change. 757 isolate->CollectSourcePositionsForAllBytecodeArrays(); 758 // Changing the coverage mode changes the generated bytecode and hence it is 759 // not safe to flush bytecode. Set a flag here, so we can disable bytecode 760 // flushing. 761 isolate->set_disable_bytecode_flushing(true); 762 } 763 764 switch (mode) { 765 case debug::CoverageMode::kBestEffort: 766 // Note that DevTools switches back to best-effort coverage once the 767 // recording is stopped. Since we delete coverage infos at that point, any 768 // following coverage recording (without reloads) will be at function 769 // granularity. 770 isolate->debug()->RemoveAllCoverageInfos(); 771 if (!isolate->is_collecting_type_profile()) { 772 isolate->SetFeedbackVectorsForProfilingTools( 773 ReadOnlyRoots(isolate).undefined_value()); 774 } 775 break; 776 case debug::CoverageMode::kBlockBinary: 777 case debug::CoverageMode::kBlockCount: 778 case debug::CoverageMode::kPreciseBinary: 779 case debug::CoverageMode::kPreciseCount: { 780 HandleScope scope(isolate); 781 782 // Remove all optimized function. Optimized and inlined functions do not 783 // increment invocation count. 784 Deoptimizer::DeoptimizeAll(isolate); 785 786 std::vector<Handle<JSFunction>> funcs_needing_feedback_vector; 787 { 788 HeapObjectIterator heap_iterator(isolate->heap()); 789 for (HeapObject o = heap_iterator.Next(); !o.is_null(); 790 o = heap_iterator.Next()) { 791 if (o.IsJSFunction()) { 792 JSFunction func = JSFunction::cast(o); 793 if (func.has_closure_feedback_cell_array()) { 794 funcs_needing_feedback_vector.push_back( 795 Handle<JSFunction>(func, isolate)); 796 } 797 } else if (IsBinaryMode(mode) && o.IsSharedFunctionInfo()) { 798 // If collecting binary coverage, reset 799 // SFI::has_reported_binary_coverage to avoid optimizing / inlining 800 // functions before they have reported coverage. 801 SharedFunctionInfo shared = SharedFunctionInfo::cast(o); 802 shared.set_has_reported_binary_coverage(false); 803 } else if (o.IsFeedbackVector()) { 804 // In any case, clear any collected invocation counts. 805 FeedbackVector::cast(o).clear_invocation_count(kRelaxedStore); 806 } 807 } 808 } 809 810 for (Handle<JSFunction> func : funcs_needing_feedback_vector) { 811 IsCompiledScope is_compiled_scope( 812 func->shared().is_compiled_scope(isolate)); 813 CHECK(is_compiled_scope.is_compiled()); 814 JSFunction::EnsureFeedbackVector(isolate, func, &is_compiled_scope); 815 } 816 817 // Root all feedback vectors to avoid early collection. 818 isolate->MaybeInitializeVectorListFromHeap(); 819 820 break; 821 } 822 } 823 isolate->set_code_coverage_mode(mode); 824} 825 826} // namespace internal 827} // namespace v8 828