50a3bf663b
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: https://github.com/dart-lang/tools/issues/513 Fixes: https://github.com/dart-lang/tools/issues/513 Change-Id: Ie66a153fbeaac5b3ecfc9a28a7f8d129ab61114a TEST=source_report_test.cc Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/421720 Reviewed-by: Ben Konyi <bkonyi@google.com> Commit-Queue: Liam Appelbe <liama@google.com>
171 lines
5.9 KiB
C++
171 lines
5.9 KiB
C++
// 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.
|
|
|
|
#ifndef RUNTIME_VM_SOURCE_REPORT_H_
|
|
#define RUNTIME_VM_SOURCE_REPORT_H_
|
|
|
|
#include "vm/globals.h"
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
#include "vm/allocation.h"
|
|
#include "vm/flags.h"
|
|
#include "vm/hash_map.h"
|
|
#include "vm/object.h"
|
|
#include "vm/profiler_service.h"
|
|
#include "vm/token_position.h"
|
|
|
|
namespace dart {
|
|
|
|
// A SourceReport object is used to generate reports about the program
|
|
// source code, with information associated with source token
|
|
// positions. There are multiple possible kinds of reports.
|
|
class SourceReport {
|
|
public:
|
|
enum ReportKind {
|
|
kCallSites = 0x1,
|
|
kCoverage = 0x2,
|
|
kPossibleBreakpoints = 0x4,
|
|
kProfile = 0x8,
|
|
kBranchCoverage = 0x10,
|
|
};
|
|
|
|
static const char* kCallSitesStr;
|
|
static const char* kCoverageStr;
|
|
static const char* kPossibleBreakpointsStr;
|
|
static const char* kProfileStr;
|
|
static const char* kBranchCoverageStr;
|
|
|
|
enum CompileMode { kNoCompile, kForceCompile };
|
|
|
|
// report_set is a bitvector indicating which reports to generate
|
|
// (e.g. kCallSites | kCoverage).
|
|
//
|
|
// If library_filters is not null, then the report will only include libraries
|
|
// whose URIs start with one of the filter strings.
|
|
explicit SourceReport(intptr_t report_set,
|
|
CompileMode compile = kNoCompile,
|
|
bool report_lines = false);
|
|
explicit SourceReport(intptr_t report_set,
|
|
const GrowableObjectArray& library_filters,
|
|
ZoneCStringSet* libraries_already_compiled = nullptr,
|
|
CompileMode compile = kNoCompile,
|
|
bool report_lines = false);
|
|
~SourceReport();
|
|
|
|
// Generate a source report for (some subrange of) a script.
|
|
//
|
|
// If script is null, then the report is generated for all scripts
|
|
// in the isolate.
|
|
void PrintJSON(JSONStream* js,
|
|
const Script& script,
|
|
TokenPosition start_pos = TokenPosition::kMinSource,
|
|
TokenPosition end_pos = TokenPosition::kMaxSource);
|
|
|
|
private:
|
|
void ClearScriptTable();
|
|
void Init(Thread* thread,
|
|
const Script* script,
|
|
TokenPosition start_pos,
|
|
TokenPosition end_pos);
|
|
|
|
Thread* thread() const { return thread_; }
|
|
Zone* zone() const { return thread_->zone(); }
|
|
Isolate* isolate() const { return thread_->isolate(); }
|
|
|
|
bool IsReportRequested(ReportKind report_kind);
|
|
bool ShouldSkipFunction(const Function& func);
|
|
bool ShouldSkipField(const Field& field);
|
|
intptr_t GetScriptIndex(const Script& script);
|
|
bool ScriptIsLoadedByLibrary(const Script& script, const Library& lib);
|
|
intptr_t GetTokenPosOrLine(const Script& script,
|
|
const TokenPosition& token_pos);
|
|
bool IsLibraryAlreadyCompiled(const Library& lib);
|
|
bool ShouldFiltersIncludeScript(const Script& script);
|
|
bool ShouldFiltersIncludeUrl(const String& url);
|
|
|
|
void PrintCallSitesData(JSONObject* jsobj,
|
|
const Function& func,
|
|
const Code& code);
|
|
void PrintCoverageData(JSONObject* jsobj,
|
|
intptr_t script_index,
|
|
const Function& func,
|
|
const Code& code,
|
|
bool report_branch_coverage);
|
|
void PrintPossibleBreakpointsData(JSONObject* jsobj,
|
|
const Function& func,
|
|
const Code& code);
|
|
void PrintProfileData(JSONObject* jsobj, ProfileFunction* profile_function);
|
|
#if defined(DEBUG)
|
|
void VerifyScriptTable();
|
|
#endif
|
|
void PrintScriptTable(JSONArray* jsarr);
|
|
|
|
void VisitFunction(JSONArray* jsarr,
|
|
const Function& func,
|
|
CompileMode compile_mode);
|
|
void VisitField(JSONArray* jsarr,
|
|
const Field& field,
|
|
CompileMode compile_mode);
|
|
void VisitLibrary(JSONArray* jsarr, const Library& lib);
|
|
void VisitClosures(JSONArray* jsarr);
|
|
|
|
// An entry in the script table.
|
|
struct ScriptTableEntry {
|
|
ScriptTableEntry() : key(nullptr), index(-1), script(nullptr) {}
|
|
|
|
const String* key;
|
|
intptr_t index;
|
|
const Script* script;
|
|
GrowableArray<TokenPosition> const_constructor_hits;
|
|
};
|
|
|
|
// Needed for DirectChainedHashMap.
|
|
struct ScriptTableTrait {
|
|
typedef ScriptTableEntry* Value;
|
|
typedef const ScriptTableEntry* Key;
|
|
typedef ScriptTableEntry* Pair;
|
|
|
|
static Key KeyOf(Pair kv) { return kv; }
|
|
|
|
static Value ValueOf(Pair kv) { return kv; }
|
|
|
|
static inline uword Hash(Key key) { return key->key->Hash(); }
|
|
|
|
static inline bool IsKeyEqual(Pair kv, Key key) {
|
|
return kv->script->ptr() == key->script->ptr();
|
|
}
|
|
};
|
|
|
|
void CollectAllScripts(
|
|
DirectChainedHashMap<ScriptTableTrait>* local_script_table,
|
|
GrowableArray<ScriptTableEntry*>* local_script_table_entries);
|
|
|
|
void CleanupCollectedScripts(
|
|
DirectChainedHashMap<ScriptTableTrait>* local_script_table,
|
|
GrowableArray<ScriptTableEntry*>* local_script_table_entries);
|
|
|
|
void CollectConstConstructorCoverageFromScripts(
|
|
GrowableArray<ScriptTableEntry*>* local_script_table_entries);
|
|
|
|
intptr_t report_set_;
|
|
CompileMode compile_mode_;
|
|
bool report_lines_;
|
|
const GrowableObjectArray& library_filters_;
|
|
ZoneCStringSet* libraries_already_compiled_;
|
|
Thread* thread_;
|
|
const Script* script_;
|
|
TokenPosition start_pos_;
|
|
TokenPosition end_pos_;
|
|
Profile profile_;
|
|
GrowableArray<ScriptTableEntry*> script_table_entries_;
|
|
DirectChainedHashMap<ScriptTableTrait> script_table_;
|
|
intptr_t next_script_index_;
|
|
intptr_t late_error_class_id_ = ClassId::kIllegalCid;
|
|
};
|
|
|
|
} // namespace dart
|
|
|
|
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
#endif // RUNTIME_VM_SOURCE_REPORT_H_
|