| // Copyright 2017 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. |
| |
| #include "src/debug/debug-coverage.h" |
| |
| #include "src/base/hashmap.h" |
| #include "src/deoptimizer.h" |
| #include "src/frames-inl.h" |
| #include "src/isolate.h" |
| #include "src/objects.h" |
| #include "src/objects/debug-objects-inl.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| class SharedToCounterMap |
| : public base::TemplateHashMapImpl<SharedFunctionInfo*, uint32_t, |
| base::KeyEqualityMatcher<void*>, |
| base::DefaultAllocationPolicy> { |
| public: |
| typedef base::TemplateHashMapEntry<SharedFunctionInfo*, uint32_t> Entry; |
| inline void Add(SharedFunctionInfo* key, uint32_t count) { |
| Entry* entry = LookupOrInsert(key, Hash(key), []() { return 0; }); |
| uint32_t old_count = entry->value; |
| if (UINT32_MAX - count < old_count) { |
| entry->value = UINT32_MAX; |
| } else { |
| entry->value = old_count + count; |
| } |
| } |
| |
| inline uint32_t Get(SharedFunctionInfo* key) { |
| Entry* entry = Lookup(key, Hash(key)); |
| if (entry == nullptr) return 0; |
| return entry->value; |
| } |
| |
| private: |
| static uint32_t Hash(SharedFunctionInfo* key) { |
| return static_cast<uint32_t>(reinterpret_cast<intptr_t>(key)); |
| } |
| |
| DisallowHeapAllocation no_gc; |
| }; |
| |
| namespace { |
| int StartPosition(SharedFunctionInfo* info) { |
| int start = info->function_token_position(); |
| if (start == kNoSourcePosition) start = info->start_position(); |
| return start; |
| } |
| |
| bool CompareSharedFunctionInfo(SharedFunctionInfo* a, SharedFunctionInfo* b) { |
| int a_start = StartPosition(a); |
| int b_start = StartPosition(b); |
| if (a_start == b_start) return a->end_position() > b->end_position(); |
| return a_start < b_start; |
| } |
| |
| bool CompareCoverageBlock(const CoverageBlock& a, const CoverageBlock& b) { |
| DCHECK(a.start != kNoSourcePosition && a.end != kNoSourcePosition); |
| DCHECK(b.start != kNoSourcePosition && b.end != kNoSourcePosition); |
| if (a.start == b.start) return a.end > b.end; |
| return a.start < b.start; |
| } |
| |
| std::vector<CoverageBlock> GetSortedBlockData(Isolate* isolate, |
| SharedFunctionInfo* shared) { |
| DCHECK(FLAG_block_coverage); |
| DCHECK(shared->HasCoverageInfo()); |
| |
| std::vector<CoverageBlock> result; |
| |
| CoverageInfo* coverage_info = |
| CoverageInfo::cast(shared->GetDebugInfo()->coverage_info()); |
| |
| for (int i = 0; i < coverage_info->SlotCount(); i++) { |
| const int start_pos = coverage_info->StartSourcePosition(i); |
| const int until_pos = coverage_info->EndSourcePosition(i); |
| const int count = coverage_info->BlockCount(i); |
| |
| DCHECK(start_pos != kNoSourcePosition); |
| DCHECK(until_pos != kNoSourcePosition); |
| |
| result.emplace_back(start_pos, until_pos, count); |
| } |
| |
| // Sort according to the block nesting structure. |
| std::sort(result.begin(), result.end(), CompareCoverageBlock); |
| |
| return result; |
| } |
| } // anonymous namespace |
| |
| Coverage* Coverage::CollectPrecise(Isolate* isolate) { |
| DCHECK(!isolate->is_best_effort_code_coverage()); |
| Coverage* result = Collect(isolate, isolate->code_coverage_mode()); |
| if (isolate->is_precise_binary_code_coverage()) { |
| // We do not have to hold onto feedback vectors for invocations we already |
| // reported. So we can reset the list. |
| isolate->SetCodeCoverageList(*ArrayList::New(isolate, 0)); |
| } |
| return result; |
| } |
| |
| Coverage* Coverage::CollectBestEffort(Isolate* isolate) { |
| return Collect(isolate, v8::debug::Coverage::kBestEffort); |
| } |
| |
| Coverage* Coverage::Collect(Isolate* isolate, |
| v8::debug::Coverage::Mode collectionMode) { |
| SharedToCounterMap counter_map; |
| |
| switch (isolate->code_coverage_mode()) { |
| case v8::debug::Coverage::kBlockCount: |
| case v8::debug::Coverage::kPreciseBinary: |
| case v8::debug::Coverage::kPreciseCount: { |
| bool reset_count = collectionMode != v8::debug::Coverage::kBestEffort; |
| // Feedback vectors are already listed to prevent losing them to GC. |
| DCHECK(isolate->factory()->code_coverage_list()->IsArrayList()); |
| Handle<ArrayList> list = |
| Handle<ArrayList>::cast(isolate->factory()->code_coverage_list()); |
| for (int i = 0; i < list->Length(); i++) { |
| FeedbackVector* vector = FeedbackVector::cast(list->Get(i)); |
| SharedFunctionInfo* shared = vector->shared_function_info(); |
| DCHECK(shared->IsSubjectToDebugging()); |
| uint32_t count = static_cast<uint32_t>(vector->invocation_count()); |
| if (reset_count) vector->clear_invocation_count(); |
| counter_map.Add(shared, count); |
| } |
| break; |
| } |
| case v8::debug::Coverage::kBestEffort: { |
| DCHECK(!isolate->factory()->code_coverage_list()->IsArrayList()); |
| DCHECK_EQ(v8::debug::Coverage::kBestEffort, collectionMode); |
| HeapIterator heap_iterator(isolate->heap()); |
| while (HeapObject* current_obj = heap_iterator.next()) { |
| if (!current_obj->IsFeedbackVector()) continue; |
| FeedbackVector* vector = FeedbackVector::cast(current_obj); |
| SharedFunctionInfo* shared = vector->shared_function_info(); |
| if (!shared->IsSubjectToDebugging()) continue; |
| uint32_t count = static_cast<uint32_t>(vector->invocation_count()); |
| counter_map.Add(shared, count); |
| } |
| break; |
| } |
| } |
| |
| // Iterate shared function infos of every script and build a mapping |
| // between source ranges and invocation counts. |
| Coverage* result = new Coverage(); |
| Script::Iterator scripts(isolate); |
| while (Script* script = scripts.Next()) { |
| if (!script->IsUserJavaScript()) continue; |
| |
| // Create and add new script data. |
| Handle<Script> script_handle(script, isolate); |
| result->emplace_back(script_handle); |
| std::vector<CoverageFunction>* functions = &result->back().functions; |
| |
| std::vector<SharedFunctionInfo*> sorted; |
| |
| { |
| // Sort functions by start position, from outer to inner functions. |
| SharedFunctionInfo::ScriptIterator infos(script_handle); |
| while (SharedFunctionInfo* info = infos.Next()) { |
| sorted.push_back(info); |
| } |
| std::sort(sorted.begin(), sorted.end(), CompareSharedFunctionInfo); |
| } |
| |
| // Stack to track nested functions, referring function by index. |
| std::vector<size_t> nesting; |
| |
| // Use sorted list to reconstruct function nesting. |
| for (SharedFunctionInfo* info : sorted) { |
| int start = StartPosition(info); |
| int end = info->end_position(); |
| uint32_t count = counter_map.Get(info); |
| // Find the correct outer function based on start position. |
| while (!nesting.empty() && functions->at(nesting.back()).end <= start) { |
| nesting.pop_back(); |
| } |
| if (count != 0) { |
| switch (collectionMode) { |
| case v8::debug::Coverage::kBlockCount: |
| case v8::debug::Coverage::kPreciseCount: |
| break; |
| case v8::debug::Coverage::kPreciseBinary: |
| count = info->has_reported_binary_coverage() ? 0 : 1; |
| info->set_has_reported_binary_coverage(true); |
| break; |
| case v8::debug::Coverage::kBestEffort: |
| count = 1; |
| break; |
| } |
| } |
| // Only include a function range if it has a non-0 count, or |
| // if it is directly nested inside a function with non-0 count. |
| if (count != 0 || |
| (!nesting.empty() && functions->at(nesting.back()).count != 0)) { |
| Handle<String> name(info->DebugName(), isolate); |
| nesting.push_back(functions->size()); |
| functions->emplace_back(start, end, count, name); |
| |
| if (FLAG_block_coverage && info->HasCoverageInfo()) { |
| CoverageFunction* function = &functions->back(); |
| function->blocks = GetSortedBlockData(isolate, info); |
| } |
| } |
| } |
| |
| // Remove entries for scripts that have no coverage. |
| if (functions->empty()) result->pop_back(); |
| } |
| return result; |
| } |
| |
| void Coverage::SelectMode(Isolate* isolate, debug::Coverage::Mode mode) { |
| switch (mode) { |
| case debug::Coverage::kBestEffort: |
| if (FLAG_block_coverage) isolate->debug()->RemoveAllCoverageInfos(); |
| isolate->SetCodeCoverageList(isolate->heap()->undefined_value()); |
| break; |
| case debug::Coverage::kBlockCount: |
| case debug::Coverage::kPreciseBinary: |
| case debug::Coverage::kPreciseCount: { |
| HandleScope scope(isolate); |
| // Remove all optimized function. Optimized and inlined functions do not |
| // increment invocation count. |
| Deoptimizer::DeoptimizeAll(isolate); |
| // Collect existing feedback vectors. |
| std::vector<Handle<FeedbackVector>> vectors; |
| { |
| HeapIterator heap_iterator(isolate->heap()); |
| while (HeapObject* current_obj = heap_iterator.next()) { |
| if (current_obj->IsSharedFunctionInfo()) { |
| SharedFunctionInfo* shared = SharedFunctionInfo::cast(current_obj); |
| shared->set_has_reported_binary_coverage(false); |
| } else if (current_obj->IsFeedbackVector()) { |
| FeedbackVector* vector = FeedbackVector::cast(current_obj); |
| SharedFunctionInfo* shared = vector->shared_function_info(); |
| if (!shared->IsSubjectToDebugging()) continue; |
| vectors.emplace_back(vector, isolate); |
| } |
| } |
| } |
| // Add collected feedback vectors to the root list lest we lose them to |
| // GC. |
| Handle<ArrayList> list = |
| ArrayList::New(isolate, static_cast<int>(vectors.size())); |
| for (const auto& vector : vectors) list = ArrayList::Add(list, vector); |
| isolate->SetCodeCoverageList(*list); |
| break; |
| } |
| } |
| isolate->set_code_coverage_mode(mode); |
| } |
| |
| } // namespace internal |
| } // namespace v8 |