/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef vm_CodeCoverage_h
#define vm_CodeCoverage_h

#include "mozilla/Vector.h"

#include "ds/LifoAlloc.h"

#include "js/AllocPolicy.h"
#include "js/HashTable.h"
#include "js/Printer.h"
#include "js/TypeDecls.h"
#include "js/Utility.h"

namespace js {
namespace coverage {

class LCovSource {
 public:
  LCovSource(LifoAlloc* alloc, JS::UniqueChars name);

  // Whether the given script name matches this LCovSource.
  bool match(const char* name) const { return strcmp(name_.get(), name) == 0; }

  // Whether an OOM was seen recording coverage information. This indicates
  // that the resulting coverage information is incomplete.
  bool hadOutOfMemory() const { return hadOOM_; }

  // Whether the current source is complete and if it can be flushed.
  bool isComplete() const { return hasTopLevelScript_; }

  // Iterate over the bytecode and collect the lcov output based on the
  // ScriptCounts counters.
  void writeScript(JSScript* script, const char* scriptName);

  // Write the Lcov output in a buffer, such as the one associated with
  // the runtime code coverage trace file.
  void exportInto(GenericPrinter& out);

 private:
  // Name of the source file.
  JS::UniqueChars name_;

  // LifoAlloc strings which hold the filename of each function as
  // well as the number of hits for each function.
  LSprinter outFN_;
  LSprinter outFNDA_;
  size_t numFunctionsFound_;
  size_t numFunctionsHit_;

  // LifoAlloc string which hold branches statistics.
  LSprinter outBRDA_;
  size_t numBranchesFound_;
  size_t numBranchesHit_;

  // Holds lines statistics. When processing a line hit count, the hit count
  // is added to any hit count already in the hash map so that we handle
  // lines that belong to more than one JSScript or function in the same
  // source file.
  HashMap<size_t, uint64_t, DefaultHasher<size_t>, SystemAllocPolicy> linesHit_;
  size_t numLinesInstrumented_;
  size_t numLinesHit_;
  size_t maxLineHit_;

  // Status flags.
  bool hasTopLevelScript_ : 1;
  bool hadOOM_ : 1;
};

class LCovRealm {
 public:
  explicit LCovRealm(JS::Realm* realm);
  ~LCovRealm();

  // Write the Lcov output in a buffer, such as the one associated with
  // the runtime code coverage trace file.
  void exportInto(GenericPrinter& out, bool* isEmpty) const;

  friend bool InitScriptCoverage(JSContext* cx, JSScript* script);

 private:
  // Write the realm name in outTN_.
  void writeRealmName(JS::Realm* realm);

  // Return the LCovSource entry which matches the given ScriptSourceObject.
  LCovSource* lookupOrAdd(const char* name);

  // Generate escaped form of script atom and allocate inside our LifoAlloc if
  // necessary.
  const char* getScriptName(JSScript* script);

 private:
  using LCovSourceVector =
      mozilla::Vector<LCovSource*, 16, LifoAllocPolicy<Fallible>>;

  // LifoAlloc backend for all temporary allocations needed to stash the
  // strings to be written in the file.
  LifoAlloc alloc_;

  // LifoAlloc string which hold the name of the realm.
  LSprinter outTN_;

  // Vector of all sources which are used in this realm. The entries are
  // allocated within the LifoAlloc.
  LCovSourceVector sources_;
};

class LCovRuntime {
 public:
  LCovRuntime();
  ~LCovRuntime();

  // If the environment variable JS_CODE_COVERAGE_OUTPUT_DIR is set to a
  // directory, create a file inside this directory which uses the process
  // ID, the thread ID and a timestamp to ensure the uniqueness of the
  // file.
  //
  // At the end of the execution, this file should contains the LCOV output of
  // all the scripts executed in the current JSRuntime.
  void init();

  // Write the aggregated result of the code coverage of a realm
  // into a file.
  void writeLCovResult(LCovRealm& realm);

 private:
  // Fill an array with the name of the file. Return false if we are unable to
  // serialize the filename in this array.
  bool fillWithFilename(char* name, size_t length);

  // Finish the current opened file, and remove if it does not have any
  // content.
  void finishFile();

 private:
  // Output file which is created if code coverage is enabled.
  Fprinter out_;

  // The process' PID is used to watch for fork. When the process fork,
  // we want to close the current file and open a new one.
  uint32_t pid_;

  // Flag used to report if the generated file is empty or not. If it is empty
  // when the runtime is destroyed, then the file would be removed as an empty
  // file is not a valid LCov file.
  bool isEmpty_;
};

void InitLCov();

void EnableLCov();

inline bool IsLCovEnabled() {
  extern bool gLCovIsEnabled;
  return gLCovIsEnabled;
}

// Initialize coverage info to track code coverage for a JSScript.
bool InitScriptCoverage(JSContext* cx, JSScript* script);

// Collect the code-coverage data from a script into relevant LCovSource.
bool CollectScriptCoverage(JSScript* script, bool finalizing);

}  // namespace coverage
}  // namespace js

#endif  // vm_CodeCoverage_h
