// 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<ScriptTableTrait>::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<const ICData*>* ic_data_array =
      new (zone()) ZoneGrowableArray<const ICData*>();
  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<char> 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_DYNAMIC_MODULES)
    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<ScriptTableTrait> local_script_table;
      GrowableArray<ScriptTableEntry*> 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<ScriptTableTrait>* local_script_table,
    GrowableArray<ScriptTableEntry*>* 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<ScriptTableTrait>* local_script_table,
    GrowableArray<ScriptTableEntry*>* 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<ScriptTableEntry*>* 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)
