// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "vm/globals.h" #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) #include "vm/source_report.h" #include "vm/bit_vector.h" #include "vm/bytecode_reader.h" #include "vm/closure_functions_cache.h" #include "vm/compiler/jit/compiler.h" #include "vm/isolate.h" #include "vm/kernel_loader.h" #include "vm/object.h" #include "vm/object_store.h" #include "vm/profiler.h" #include "vm/profiler_service.h" namespace dart { const char* SourceReport::kCallSitesStr = "_CallSites"; const char* SourceReport::kCoverageStr = "Coverage"; const char* SourceReport::kPossibleBreakpointsStr = "PossibleBreakpoints"; const char* SourceReport::kProfileStr = "_Profile"; const char* SourceReport::kBranchCoverageStr = "BranchCoverage"; SourceReport::SourceReport(intptr_t report_set, CompileMode compile_mode, bool report_lines) : report_set_(report_set), compile_mode_(compile_mode), report_lines_(report_lines), library_filters_(GrowableObjectArray::Handle()), libraries_already_compiled_(nullptr), thread_(nullptr), script_(nullptr), start_pos_(TokenPosition::kMinSource), end_pos_(TokenPosition::kMaxSource), next_script_index_(0) {} SourceReport::SourceReport(intptr_t report_set, const GrowableObjectArray& library_filters, ZoneCStringSet* libraries_already_compiled, CompileMode compile_mode, bool report_lines) : report_set_(report_set), compile_mode_(compile_mode), report_lines_(report_lines), library_filters_(library_filters), libraries_already_compiled_(libraries_already_compiled), thread_(nullptr), script_(nullptr), start_pos_(TokenPosition::kMinSource), end_pos_(TokenPosition::kMaxSource), next_script_index_(0) {} SourceReport::~SourceReport() { ClearScriptTable(); } void SourceReport::ClearScriptTable() { // Delete entries from script_table_ as it has the unfiltered list. DirectChainedHashMap::Iterator iter = script_table_.GetIterator(); ScriptTableTrait::Pair* pair; while ((pair = iter.Next()) != nullptr) { delete ScriptTableTrait::ValueOf(*pair); } script_table_.Clear(); for (intptr_t i = 0; i < script_table_entries_.length(); i++) { script_table_entries_[i] = nullptr; } script_table_entries_.Clear(); next_script_index_ = 0; } void SourceReport::Init(Thread* thread, const Script* script, TokenPosition start_pos, TokenPosition end_pos) { thread_ = thread; script_ = script; start_pos_ = TokenPosition::Max(start_pos, TokenPosition::kMinSource); end_pos_ = TokenPosition::Min(end_pos, TokenPosition::kMaxSource); ClearScriptTable(); if (IsReportRequested(kProfile)) { // Build the profile. SampleFilter samplesForIsolate(thread_->isolate()->main_port(), Thread::kMutatorTask, -1, -1); profile_.Build(thread, thread->isolate(), &samplesForIsolate, Profiler::sample_block_buffer()); } } bool SourceReport::IsReportRequested(ReportKind report_kind) { return (report_set_ & report_kind) != 0; } bool SourceReport::ShouldSkipFunction(const Function& func) { // TODO(32315): Verify that the check is still needed after the issue is // resolved. if (!func.token_pos().IsReal() || !func.end_token_pos().IsReal()) { // At least one of the token positions is not known. return true; } if (script_ != nullptr && !script_->IsNull()) { if (func.script() != script_->ptr()) { // The function is from the wrong script. return true; } if ((func.end_token_pos() < start_pos_) || (func.token_pos() > end_pos_)) { // The function does not intersect with the requested token range. return true; } } // These don't have unoptimized code and are only used for synthetic stubs. if (func.ForceOptimize()) return true; switch (func.kind()) { case UntaggedFunction::kRegularFunction: case UntaggedFunction::kClosureFunction: case UntaggedFunction::kImplicitClosureFunction: case UntaggedFunction::kImplicitStaticGetter: case UntaggedFunction::kFieldInitializer: case UntaggedFunction::kGetterFunction: case UntaggedFunction::kSetterFunction: case UntaggedFunction::kConstructor: break; default: return true; } if (func.is_abstract() || func.IsImplicitConstructor() || func.is_synthetic() || func.IsRedirectingFactory()) { return true; } if (func.IsNonImplicitClosureFunction() && !func.HasBytecode() && (func.context_scope() == ContextScope::null())) { // TODO(iposva): This can arise if we attempt to compile an inner function // before we have compiled its enclosing function or if the enclosing // function failed to compile. return true; } // There is an idiom where static utility classes are given a private // constructor to prevent the class from being instantiated. Ignore these // constructors so that they don't lower the coverage rate. See #47021. SafepointReadRwLocker ml(thread_, thread_->isolate_group()->program_lock()); if (func.kind() == UntaggedFunction::kConstructor && func.NumParameters() == func.NumImplicitParameters() && func.IsPrivate()) { // Check that the class has no non-static members and no subclasses. Class& cls = Class::Handle(func.Owner()); GrowableObjectArray& subclasses = GrowableObjectArray::Handle(cls.direct_subclasses()); if (cls.is_abstract() && !cls.HasInstanceFields() && (subclasses.IsNull() || subclasses.Length() == 0)) { // Check that the constructor is the only non-static function. Array& clsFuncs = Array::Handle(cls.functions()); Function& otherFunc = Function::Handle(); intptr_t numNonStaticFunctions = 0; for (intptr_t i = 0; i < clsFuncs.Length(); ++i) { otherFunc ^= clsFuncs.At(i); if (!otherFunc.IsStaticFunction()) { ++numNonStaticFunctions; } } if (numNonStaticFunctions == 1) { return true; } } } // Enum constructors cannot be invoked by the user, so ignore them. if (func.IsGenerativeConstructor()) { Class& cls = Class::Handle(func.Owner()); if (cls.is_enum_class()) { return true; } } return false; } bool SourceReport::ShouldSkipField(const Field& field) { if (!field.token_pos().IsReal() || !field.end_token_pos().IsReal()) { // At least one of the token positions is not known. return true; } if (script_ != nullptr && !script_->IsNull()) { if (field.Script() != script_->ptr()) { // The field is from the wrong script. return true; } if ((field.end_token_pos() < start_pos_) || (field.token_pos() > end_pos_)) { // The field does not intersect with the requested token range. return true; } } // Static const fields from compiled code will not have an initializer // function, just an implicit static getter (which is skipped). // Static const fields in bytecode _do_ have initializer functions, // though, so skip them to ensure they aren't reported in the coverage // information so as to match coverage for compile code. if (field.is_static() && field.is_const()) { return true; } return false; } bool SourceReport::IsLibraryAlreadyCompiled(const Library& lib) { if (libraries_already_compiled_ == nullptr) return false; const char* url = String::ToCString(thread(), lib.url()); if (url == nullptr) return false; return libraries_already_compiled_->Lookup(url) != nullptr; } bool SourceReport::ShouldFiltersIncludeUrl(const String& url) { String& filter = String::Handle(zone()); const intptr_t num_filters = library_filters_.Length(); for (intptr_t i = 0; i < num_filters; ++i) { filter ^= library_filters_.At(i); if (url.StartsWith(filter)) { return true; } } return false; } bool SourceReport::ShouldFiltersIncludeScript(const Script& script) { if (library_filters_.IsNull()) return true; String& url = String::Handle(zone(), script.url()); if (ShouldFiltersIncludeUrl(url)) return true; const Library& lib = Library::Handle(zone(), script.FindLibrary()); url ^= lib.url(); return ShouldFiltersIncludeUrl(url); } intptr_t SourceReport::GetScriptIndex(const Script& script) { ScriptTableEntry wrapper; const String& url = String::Handle(zone(), script.url()); wrapper.key = &url; wrapper.script = &Script::Handle(zone(), script.ptr()); ScriptTableEntry* pair = script_table_.LookupValue(&wrapper); if (pair != nullptr) { return pair->index; } ScriptTableEntry* tmp = new ScriptTableEntry(); tmp->key = &url; tmp->script = wrapper.script; if (ShouldFiltersIncludeScript(script)) { tmp->index = next_script_index_++; script_table_entries_.Add(tmp); } else { tmp->index = -1; } script_table_.Insert(tmp); ASSERT(script_table_entries_.length() == next_script_index_); #if defined(DEBUG) VerifyScriptTable(); #endif return tmp->index; } #if defined(DEBUG) void SourceReport::VerifyScriptTable() { for (intptr_t i = 0; i < script_table_entries_.length(); i++) { const String* url = script_table_entries_[i]->key; const Script* script = script_table_entries_[i]->script; intptr_t index = script_table_entries_[i]->index; ASSERT(i == index); const String& url2 = String::Handle(zone(), script->url()); ASSERT(url2.Equals(*url)); ScriptTableEntry wrapper; wrapper.key = &url2; wrapper.script = &Script::Handle(zone(), script->ptr()); ScriptTableEntry* pair = script_table_.LookupValue(&wrapper); ASSERT(i == pair->index); } } #endif bool SourceReport::ScriptIsLoadedByLibrary(const Script& script, const Library& lib) { const Array& scripts = Array::Handle(zone(), lib.LoadedScripts()); for (intptr_t j = 0; j < scripts.Length(); j++) { if (scripts.At(j) == script.ptr()) { return true; } } return false; } void SourceReport::PrintCallSitesData(JSONObject* jsobj, const Function& function, const Code& code) { ASSERT(!code.IsNull()); const TokenPosition& begin_pos = function.token_pos(); const TokenPosition& end_pos = function.end_token_pos(); ZoneGrowableArray* ic_data_array = new (zone()) ZoneGrowableArray(); function.RestoreICDataMap(ic_data_array, false /* clone ic-data */); const PcDescriptors& descriptors = PcDescriptors::Handle(zone(), code.pc_descriptors()); JSONArray sites(jsobj, "callSites"); PcDescriptors::Iterator iter( descriptors, UntaggedPcDescriptors::kIcCall | UntaggedPcDescriptors::kUnoptStaticCall); while (iter.MoveNext()) { HANDLESCOPE(thread()); ASSERT(iter.DeoptId() < ic_data_array->length()); const ICData* ic_data = (*ic_data_array)[iter.DeoptId()]; if (ic_data != nullptr) { const TokenPosition& token_pos = iter.TokenPos(); if (!token_pos.IsWithin(begin_pos, end_pos)) { // Does not correspond to a valid source position. continue; } ic_data->PrintToJSONArray(sites, token_pos); } } } intptr_t SourceReport::GetTokenPosOrLine(const Script& script, const TokenPosition& token_pos) { if (!report_lines_) { return token_pos.Pos(); } intptr_t line = -1; const bool found = script.GetTokenLocation(token_pos, &line); ASSERT(found); return line; } void SourceReport::PrintCoverageData(JSONObject* jsobj, const Script& script, intptr_t script_index, const Function& function, bool report_branch_coverage) { const TokenPosition& begin_pos = function.token_pos(); const TokenPosition& end_pos = function.end_token_pos(); bool const_constructor_hit = false; if (function.IsFunction() && function.is_const()) { for (TokenPosition hit : script_table_entries_[script_index]->const_constructor_hits) { if (hit == begin_pos) { const_constructor_hit = true; break; } } } const int kCoverageNone = 0; const int kCoverageMiss = 1; const int kCoverageHit = 2; intptr_t func_length = function.SourceSize() + 1; GrowableArray coverage(func_length); coverage.SetLength(func_length); for (int i = 0; i < func_length; i++) { coverage[i] = kCoverageNone; } if (function.WasExecuted() || const_constructor_hit) { coverage[0] = kCoverageHit; } else { coverage[0] = kCoverageMiss; } auto update_coverage = [&](TokenPosition token_pos, bool was_executed) { if (!(token_pos.IsReal() && token_pos.IsWithin(begin_pos, end_pos))) { return; } const intptr_t token_offset = token_pos.Pos() - begin_pos.Pos(); if (was_executed) { coverage[token_offset] = kCoverageHit; } else { if (coverage[token_offset] == kCoverageNone) { coverage[token_offset] = kCoverageMiss; } } }; // Merge the coverage from coverage_array attached to the function. const auto& coverage_array = TypedData::Handle(function.GetCoverageArray()); if (!coverage_array.IsNull()) { for (intptr_t i = 0; i < coverage_array.Length(); i += 2) { bool is_branch_coverage; const TokenPosition token_pos = TokenPosition::DecodeCoveragePosition( coverage_array.GetUint32(i * kInt32Size), &is_branch_coverage); if (is_branch_coverage == report_branch_coverage) { const bool was_executed = coverage_array.GetUint32((i + 1) * kInt32Size) != 0; update_coverage(token_pos, was_executed || const_constructor_hit); } } } JSONObject cov(jsobj, report_branch_coverage ? "branchCoverage" : "coverage"); { JSONArray hits(&cov, "hits"); TokenPosition pos = begin_pos; for (int i = 0; i < func_length; i++) { if (coverage[i] == kCoverageHit) { // Add the token position or line number of the hit. hits.AddValue(GetTokenPosOrLine(script, pos)); } pos = pos.Next(); } } { JSONArray misses(&cov, "misses"); TokenPosition pos = begin_pos; for (int i = 0; i < func_length; i++) { if (coverage[i] == kCoverageMiss) { // Add the token position or line number of the miss. misses.AddValue(GetTokenPosOrLine(script, pos)); } pos = pos.Next(); } } } void SourceReport::PrintPossibleBreakpointsData(JSONObject* jsobj, const Script& script, const Function& func, const Code& code) { const TokenPosition& begin_pos = func.token_pos(); const TokenPosition& end_pos = func.end_token_pos(); intptr_t func_length = func.SourceSize() + 1; BitVector possible(zone(), func_length); if (func.HasBytecode()) { #if defined(DART_BYTECODE_INTERPRETER) const auto& bytecode = Bytecode::Handle(zone(), func.GetBytecode()); // Currently, every source position is a possible breakpoint. bytecode::BytecodeSourcePositionsIterator iter(zone(), bytecode); while (iter.MoveNext()) { const TokenPosition& token_pos = iter.TokenPos(); if (!token_pos.IsWithin(begin_pos, end_pos)) { // Does not correspond to a valid source position. continue; } intptr_t token_offset = token_pos.Pos() - begin_pos.Pos(); possible.Add(token_offset); } #else UNREACHABLE(); #endif } else { ASSERT(!code.IsNull()); const uint8_t kSafepointKind = (UntaggedPcDescriptors::kIcCall | UntaggedPcDescriptors::kUnoptStaticCall | UntaggedPcDescriptors::kRuntimeCall); const PcDescriptors& descriptors = PcDescriptors::Handle(zone(), code.pc_descriptors()); PcDescriptors::Iterator iter(descriptors, kSafepointKind); while (iter.MoveNext()) { const TokenPosition& token_pos = iter.TokenPos(); if (!token_pos.IsWithin(begin_pos, end_pos)) { // Does not correspond to a valid source position. continue; } intptr_t token_offset = token_pos.Pos() - begin_pos.Pos(); possible.Add(token_offset); } } JSONArray bpts(jsobj, "possibleBreakpoints"); TokenPosition pos = begin_pos; for (int i = 0; i < func_length; i++) { if (possible.Contains(i)) { // Add the token position or line number. bpts.AddValue(GetTokenPosOrLine(script, pos)); } pos = pos.Next(); } } void SourceReport::PrintProfileData(JSONObject* jsobj, ProfileFunction* profile_function) { ASSERT(profile_function != nullptr); ASSERT(profile_function->NumSourcePositions() > 0); { JSONObject profile(jsobj, "profile"); { JSONObject profileData(&profile, "metadata"); profileData.AddProperty("sampleCount", profile_.sample_count()); } // Positions. { JSONArray positions(&profile, "positions"); for (intptr_t i = 0; i < profile_function->NumSourcePositions(); i++) { const ProfileFunctionSourcePosition& position = profile_function->GetSourcePosition(i); if (position.token_pos().IsReal()) { // Add as an integer. positions.AddValue(position.token_pos().Pos()); } else { // Add as a string. positions.AddValue(position.token_pos().ToCString()); } } } // Exclusive ticks. { JSONArray exclusiveTicks(&profile, "exclusiveTicks"); for (intptr_t i = 0; i < profile_function->NumSourcePositions(); i++) { const ProfileFunctionSourcePosition& position = profile_function->GetSourcePosition(i); exclusiveTicks.AddValue(position.exclusive_ticks()); } } // Inclusive ticks. { JSONArray inclusiveTicks(&profile, "inclusiveTicks"); for (intptr_t i = 0; i < profile_function->NumSourcePositions(); i++) { const ProfileFunctionSourcePosition& position = profile_function->GetSourcePosition(i); inclusiveTicks.AddValue(position.inclusive_ticks()); } } } } void SourceReport::PrintScriptTable(JSONArray* scripts) { for (intptr_t i = 0; i < script_table_entries_.length(); i++) { const Script* script = script_table_entries_[i]->script; scripts->AddValue(*script); } } void SourceReport::VisitCodeOrBytecode(JSONObject* jsobj, const Script& script, intptr_t script_index, const Function& func, const Code& code, CompileMode compile_mode) { ASSERT(!code.IsNull() || func.HasBytecode()); // TODO(sstrickl): Handle call site data for bytecode. if (IsReportRequested(kCallSites) && !code.IsNull()) { PrintCallSitesData(jsobj, func, code); } if (IsReportRequested(kCoverage)) { PrintCoverageData(jsobj, script, script_index, func, /* report_branch_coverage */ false); } if (IsReportRequested(kBranchCoverage)) { PrintCoverageData(jsobj, script, script_index, func, /* report_branch_coverage */ true); } if (IsReportRequested(kPossibleBreakpoints)) { PrintPossibleBreakpointsData(jsobj, script, func, code); } if (IsReportRequested(kProfile)) { if (auto* const profile_function = profile_.FindFunction(func)) { if (profile_function->NumSourcePositions() > 0) { PrintProfileData(jsobj, profile_function); } } } } void SourceReport::VisitFunction(JSONArray* jsarr, const Function& func, CompileMode compile_mode) { if (ShouldSkipFunction(func)) { return; } const Script& script = Script::Handle(zone(), func.script()); const TokenPosition begin_pos = func.token_pos(); const TokenPosition end_pos = func.end_token_pos(); const intptr_t script_index = GetScriptIndex(script); if (script_index < 0) { return; } auto& code = Code::Handle(zone(), func.unoptimized_code()); auto& err = Error::Handle(zone()); bool is_compiled = !code.IsNull(); if (func.HasBytecode()) { ASSERT(code.IsNull()); // We treat unexecuted bytecode as "uncompiled" unless force compilation was // requested, to match the reports for compiled code. is_compiled = func.WasExecuted() || compile_mode == kForceCompile; } else if (code.IsNull() && (func.HasCode() || (compile_mode == kForceCompile))) { err = Compiler::EnsureUnoptimizedCode(thread(), func); if (err.IsNull()) { is_compiled = true; code = func.unoptimized_code(); } else { // Emit an uncompiled range for this function with error information. } } JSONObject range(jsarr); range.AddProperty("scriptIndex", script_index); range.AddProperty("startPos", begin_pos); range.AddProperty("endPos", end_pos); range.AddProperty("compiled", is_compiled); if (!err.IsNull()) { range.AddProperty("error", err); } if (is_compiled) { VisitCodeOrBytecode(&range, script, script_index, func, code, compile_mode); } } void SourceReport::VisitField(JSONArray* jsarr, const Field& field, CompileMode compile_mode) { if (ShouldSkipField(field) || !field.HasInitializerFunction()) return; const Function& func = Function::Handle(field.InitializerFunction()); VisitFunction(jsarr, func, compile_mode); } void SourceReport::VisitLibrary(JSONArray* jsarr, const Library& lib) { Class& cls = Class::Handle(zone()); Array& functions = Array::Handle(zone()); Array& fields = Array::Handle(zone()); Function& func = Function::Handle(zone()); Field& field = Field::Handle(zone()); Script& script = Script::Handle(zone()); ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate); CompileMode compile_mode = compile_mode_; if (compile_mode == kForceCompile && IsLibraryAlreadyCompiled(lib)) { compile_mode = kNoCompile; } while (it.HasNext()) { cls = it.GetNextClass(); if (!cls.is_finalized()) { if (compile_mode == kForceCompile) { Error& err = Error::Handle(cls.EnsureIsFinalized(thread())); if (!err.IsNull()) { // Emit an uncompiled range for this class with error information. script = cls.script(); const intptr_t script_index = GetScriptIndex(script); if (script_index < 0) { continue; } JSONObject range(jsarr); range.AddProperty("scriptIndex", script_index); range.AddProperty("startPos", cls.token_pos()); range.AddProperty("endPos", cls.end_token_pos()); range.AddProperty("compiled", false); range.AddProperty("error", err); continue; } ASSERT(cls.is_finalized()); } else { cls.EnsureDeclarationLoaded(); // Emit one range for the whole uncompiled class. script = cls.script(); const intptr_t script_index = GetScriptIndex(script); if (script_index < 0) { continue; } JSONObject range(jsarr); range.AddProperty("scriptIndex", script_index); range.AddProperty("startPos", cls.token_pos()); range.AddProperty("endPos", cls.end_token_pos()); range.AddProperty("compiled", false); continue; } } functions = cls.current_functions(); for (int i = 0; i < functions.Length(); i++) { func ^= functions.At(i); // Skip getter functions of static const field. if (func.kind() == UntaggedFunction::kImplicitStaticGetter) { field ^= func.accessor_field(); if (field.is_const() && field.is_static()) { continue; } } VisitFunction(jsarr, func, compile_mode); } fields = cls.fields(); for (intptr_t i = 0; i < fields.Length(); i++) { field ^= fields.At(i); VisitField(jsarr, field, compile_mode); } } } void SourceReport::VisitClosures(JSONArray* jsarr) { ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& func) { VisitFunction(jsarr, func, compile_mode_); return true; // Continue iteration. }); } void SourceReport::PrintJSON(JSONStream* js, const Script& script, TokenPosition start_pos, TokenPosition end_pos) { Init(Thread::Current(), &script, start_pos, end_pos); JSONObject report(js); report.AddProperty("type", "SourceReport"); { JSONArray ranges(&report, "ranges"); // Output constant coverage if coverage is requested. if (IsReportRequested(kCoverage) || IsReportRequested(kBranchCoverage)) { // Find all scripts. We need to go though all scripts because a script // (even one we don't want) can add coverage to another library (i.e. // potentially one we want). DirectChainedHashMap local_script_table; GrowableArray local_script_table_entries; CollectAllScripts(&local_script_table, &local_script_table_entries); CollectConstConstructorCoverageFromScripts(&local_script_table_entries); CleanupCollectedScripts(&local_script_table, &local_script_table_entries); } const GrowableObjectArray& libs = GrowableObjectArray::Handle( zone(), thread()->isolate_group()->object_store()->libraries()); // We only visit the libraries which actually load the specified script. Library& lib = Library::Handle(zone()); for (intptr_t i = 0; i < libs.Length(); i++) { lib ^= libs.At(i); if (script.IsNull() || ScriptIsLoadedByLibrary(script, lib)) { VisitLibrary(&ranges, lib); } } // Visit all closures for this isolate. VisitClosures(&ranges); } // Print the script table. JSONArray scripts(&report, "scripts"); PrintScriptTable(&scripts); } void SourceReport::CollectAllScripts( DirectChainedHashMap* local_script_table, GrowableArray* local_script_table_entries) { ScriptTableEntry wrapper; const GrowableObjectArray& libs = GrowableObjectArray::Handle( zone(), thread()->isolate_group()->object_store()->libraries()); Library& lib = Library::Handle(zone()); Script& scriptRef = Script::Handle(zone()); for (int i = 0; i < libs.Length(); i++) { lib ^= libs.At(i); const Array& scripts = Array::Handle(zone(), lib.LoadedScripts()); for (intptr_t j = 0; j < scripts.Length(); j++) { scriptRef ^= scripts.At(j); const String& url = String::Handle(zone(), scriptRef.url()); wrapper.key = &url; wrapper.script = &Script::Handle(zone(), scriptRef.ptr()); ScriptTableEntry* pair = local_script_table->LookupValue(&wrapper); if (pair != nullptr) { // Existing one. continue; } // New one. Insert. ScriptTableEntry* tmp = new ScriptTableEntry(); tmp->key = &url; tmp->index = -1; tmp->script = wrapper.script; local_script_table_entries->Add(tmp); local_script_table->Insert(tmp); } } } void SourceReport::CleanupCollectedScripts( DirectChainedHashMap* local_script_table, GrowableArray* local_script_table_entries) { for (intptr_t i = 0; i < local_script_table_entries->length(); i++) { delete local_script_table_entries->operator[](i); local_script_table_entries->operator[](i) = nullptr; } local_script_table_entries->Clear(); local_script_table->Clear(); } void SourceReport::CollectConstConstructorCoverageFromScripts( GrowableArray* local_script_table_entries) { // Now output the wanted constant coverage. for (intptr_t i = 0; i < local_script_table_entries->length(); i++) { const Script* script = local_script_table_entries->At(i)->script; // Whether we want *this* script or not we need to look at the constant // constructor coverage. Any of those could be in a script we *do* want. { Script& scriptRef = Script::Handle(zone()); const Array& constructors = Array::Handle(script->CollectConstConstructorCoverageFrom()); intptr_t constructors_count = constructors.Length(); Function& constructor = Function::Handle(zone()); for (intptr_t i = 0; i < constructors_count; i++) { constructor ^= constructors.At(i); // Check if we want coverage for this constructor. if (ShouldSkipFunction(constructor)) { continue; } scriptRef ^= constructor.script(); const intptr_t script_index = GetScriptIndex(scriptRef); if (script_index < 0) { continue; } script_table_entries_[script_index]->const_constructor_hits.Add( constructor.token_pos()); } } } } } // namespace dart #endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)