Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  CodeCoverage.cpp   Sprache: C

 
/* -*- 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/. */


#include "vm/CodeCoverage.h"

#include "mozilla/Atomics.h"
#include "mozilla/IntegerPrintfMacros.h"

#include <stdio.h>
#include <utility>

#include "frontend/SourceNotes.h"  // SrcNote, SrcNoteType, SrcNoteIterator
#include "gc/Zone.h"
#include "util/GetPidProvider.h"  // getpid()
#include "util/Text.h"
#include "vm/BytecodeUtil.h"
#include "vm/JSScript.h"
#include "vm/Realm.h"
#include "vm/Runtime.h"
#include "vm/Time.h"

// This file contains a few functions which are used to produce files understood
// by lcov tools. A detailed description of the format is available in the man
// page for "geninfo" [1].  To make it short, the following paraphrases what is
// commented in the man page by using curly braces prefixed by for-each to
// express repeated patterns.
//
//   TN:<compartment name>
//   for-each <source file> {
//     SF:<filename>
//     for-each <script> {
//       FN:<line>,<name>
//     }
//     for-each <script> {
//       FNDA:<hits>,<name>
//     }
//     FNF:<number of scripts>
//     FNH:<sum of scripts hits>
//     for-each <script> {
//       for-each <branch> {
//         BRDA:<line>,<block id>,<target id>,<taken>
//       }
//     }
//     BRF:<number of branches>
//     BRH:<sum of branches hits>
//     for-each <script> {
//       for-each <line> {
//         DA:<line>,<hits>
//       }
//     }
//     LF:<number of lines>
//     LH:<sum of lines hits>
//   }
//
// [1] http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php
//
namespace js {
namespace coverage {

LCovSource::LCovSource(LifoAlloc* alloc, UniqueChars name)
    : name_(std::move(name)),
      outFN_(alloc),
      outFNDA_(alloc),
      numFunctionsFound_(0),
      numFunctionsHit_(0),
      outBRDA_(alloc),
      numBranchesFound_(0),
      numBranchesHit_(0),
      numLinesInstrumented_(0),
      numLinesHit_(0),
      maxLineHit_(0),
      hasTopLevelScript_(false),
      hadOOM_(false) {}

void LCovSource::exportInto(GenericPrinter& out) {
  if (hadOutOfMemory()) {
    out.setPendingOutOfMemory();
  } else {
    out.printf("SF:%s\n", name_.get());

    outFN_.exportInto(out);
    outFNDA_.exportInto(out);
    out.printf("FNF:%zu\n", numFunctionsFound_);
    out.printf("FNH:%zu\n", numFunctionsHit_);

    outBRDA_.exportInto(out);
    out.printf("BRF:%zu\n", numBranchesFound_);
    out.printf("BRH:%zu\n", numBranchesHit_);

    if (!linesHit_.empty()) {
      for (size_t lineno = 1; lineno <= maxLineHit_; ++lineno) {
        if (auto p = linesHit_.lookup(lineno)) {
          out.printf("DA:%zu,%" PRIu64 "\n", lineno, p->value());
        }
      }
    }

    out.printf("LF:%zu\n", numLinesInstrumented_);
    out.printf("LH:%zu\n", numLinesHit_);

    out.put("end_of_record\n");
  }

  outFN_.clear();
  outFNDA_.clear();
  numFunctionsFound_ = 0;
  numFunctionsHit_ = 0;
  outBRDA_.clear();
  numBranchesFound_ = 0;
  numBranchesHit_ = 0;
  linesHit_.clear();
  numLinesInstrumented_ = 0;
  numLinesHit_ = 0;
  maxLineHit_ = 0;
}

void LCovSource::writeScript(JSScript* script, const char* scriptName) {
  if (hadOutOfMemory()) {
    return;
  }

  numFunctionsFound_++;
  outFN_.printf("FN:%u,%s\n", script->lineno(), scriptName);

  uint64_t hits = 0;
  ScriptCounts* sc = nullptr;
  if (script->hasScriptCounts()) {
    sc = &script->getScriptCounts();
    numFunctionsHit_++;
    const PCCounts* counts =
        sc->maybeGetPCCounts(script->pcToOffset(script->main()));
    outFNDA_.printf("FNDA:%" PRIu64 ",%s\n", counts->numExec(), scriptName);

    // Set the hit count of the pre-main code to 1, if the function ever got
    // visited.
    hits = 1;
  }

  jsbytecode* snpc = script->code();
  const SrcNote* sn = script->notes();
  const SrcNote* snEnd = script->notesEnd();
  if (sn < snEnd) {
    snpc += sn->delta();
  }

  size_t lineno = script->lineno();
  jsbytecode* end = script->codeEnd();
  size_t branchId = 0;
  bool firstLineHasBeenWritten = false;
  for (jsbytecode* pc = script->code(); pc != end; pc = GetNextPc(pc)) {
    MOZ_ASSERT(script->code() <= pc && pc < end);
    JSOp op = JSOp(*pc);
    bool jump = IsJumpOpcode(op) || op == JSOp::TableSwitch;
    bool fallsthrough = BytecodeFallsThrough(op);

    // If the current script & pc has a hit-count report, then update the
    // current number of hits.
    if (sc) {
      const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(pc));
      if (counts) {
        hits = counts->numExec();
      }
    }

    // If we have additional source notes, walk all the source notes of the
    // current pc.
    if (snpc <= pc || !firstLineHasBeenWritten) {
      size_t oldLine = lineno;
      SrcNoteIterator iter(sn, snEnd);
      while (!iter.atEnd() && snpc <= pc) {
        sn = *iter;
        SrcNoteType type = sn->type();
        if (type == SrcNoteType::SetLine) {
          lineno = SrcNote::SetLine::getLine(sn, script->lineno());
        } else if (type == SrcNoteType::SetLineColumn) {
          lineno = SrcNote::SetLineColumn::getLine(sn, script->lineno());
        } else if (type == SrcNoteType::NewLine ||
                   type == SrcNoteType::NewLineColumn) {
          lineno++;
        }
        ++iter;
        if (!iter.atEnd()) {
          snpc += (*iter)->delta();
        }
      }
      sn = *iter;

      if ((oldLine != lineno || !firstLineHasBeenWritten) &&
          pc >= script->main() && fallsthrough) {
        auto p = linesHit_.lookupForAdd(lineno);
        if (!p) {
          if (!linesHit_.add(p, lineno, hits)) {
            hadOOM_ = true;
            return;
          }
          numLinesInstrumented_++;
          if (hits != 0) {
            numLinesHit_++;
          }
          maxLineHit_ = std::max(lineno, maxLineHit_);
        } else {
          if (p->value() == 0 && hits != 0) {
            numLinesHit_++;
          }
          p->value() += hits;
        }

        firstLineHasBeenWritten = true;
      }
    }

    // If the current instruction has thrown, then decrement the hit counts
    // with the number of throws.
    if (sc) {
      const PCCounts* counts = sc->maybeGetThrowCounts(script->pcToOffset(pc));
      if (counts) {
        hits -= counts->numExec();
      }
    }

    // If the current pc corresponds to a conditional jump instruction, then
    // reports branch hits.
    if (jump && fallsthrough) {
      jsbytecode* fallthroughTarget = GetNextPc(pc);
      uint64_t fallthroughHits = 0;
      if (sc) {
        const PCCounts* counts =
            sc->maybeGetPCCounts(script->pcToOffset(fallthroughTarget));
        if (counts) {
          fallthroughHits = counts->numExec();
        }
      }

      uint64_t taken = hits - fallthroughHits;
      outBRDA_.printf("BRDA:%zu,%zu,0,", lineno, branchId);
      if (hits) {
        outBRDA_.printf("%" PRIu64 "\n", taken);
      } else {
        outBRDA_.put("-\n", 2);
      }

      outBRDA_.printf("BRDA:%zu,%zu,1,", lineno, branchId);
      if (hits) {
        outBRDA_.printf("%" PRIu64 "\n", fallthroughHits);
      } else {
        outBRDA_.put("-\n", 2);
      }

      // Count the number of branches, and the number of branches hit.
      numBranchesFound_ += 2;
      if (hits) {
        numBranchesHit_ += !!taken + !!fallthroughHits;
      }
      branchId++;
    }

    // If the current pc corresponds to a pre-computed switch case, then
    // reports branch hits for each case statement.
    if (jump && op == JSOp::TableSwitch) {
      // Get the default pc.
      jsbytecode* defaultpc = pc + GET_JUMP_OFFSET(pc);
      MOZ_ASSERT(script->code() <= defaultpc && defaultpc < end);
      MOZ_ASSERT(defaultpc > pc);

      // Get the low and high from the tableswitch
      int32_t low = GET_JUMP_OFFSET(pc + JUMP_OFFSET_LEN * 1);
      int32_t high = GET_JUMP_OFFSET(pc + JUMP_OFFSET_LEN * 2);
      MOZ_ASSERT(high - low + 1 >= 0);
      size_t numCases = high - low + 1;

      auto getCaseOrDefaultPc = [&](size_t index) {
        if (index < numCases) {
          return script->tableSwitchCasePC(pc, index);
        }
        MOZ_ASSERT(index == numCases);
        return defaultpc;
      };

      jsbytecode* firstCaseOrDefaultPc = end;
      for (size_t j = 0; j < numCases + 1; j++) {
        jsbytecode* testpc = getCaseOrDefaultPc(j);
        MOZ_ASSERT(script->code() <= testpc && testpc < end);
        if (testpc < firstCaseOrDefaultPc) {
          firstCaseOrDefaultPc = testpc;
        }
      }

      // Count the number of hits of the default branch, by subtracting
      // the number of hits of each cases.
      uint64_t defaultHits = hits;

      // Count the number of hits of the previous case entry.
      uint64_t fallsThroughHits = 0;

      // Record branches for each case and default.
      size_t caseId = 0;
      for (size_t i = 0; i < numCases + 1; i++) {
        jsbytecode* caseOrDefaultPc = getCaseOrDefaultPc(i);
        MOZ_ASSERT(script->code() <= caseOrDefaultPc && caseOrDefaultPc < end);

        // PCs might not be in increasing order of case indexes.
        jsbytecode* lastCaseOrDefaultPc = firstCaseOrDefaultPc - 1;
        bool foundLastCaseOrDefault = false;
        for (size_t j = 0; j < numCases + 1; j++) {
          jsbytecode* testpc = getCaseOrDefaultPc(j);
          MOZ_ASSERT(script->code() <= testpc && testpc < end);
          if (lastCaseOrDefaultPc < testpc &&
              (testpc < caseOrDefaultPc ||
               (j < i && testpc == caseOrDefaultPc))) {
            lastCaseOrDefaultPc = testpc;
            foundLastCaseOrDefault = true;
          }
        }

        // If multiple case instruction have the same code block, only
        // register the code coverage the first time we hit this case.
        if (!foundLastCaseOrDefault || caseOrDefaultPc != lastCaseOrDefaultPc) {
          uint64_t caseOrDefaultHits = 0;
          if (sc) {
            if (i < numCases) {
              // Case (i + low)
              const PCCounts* counts =
                  sc->maybeGetPCCounts(script->pcToOffset(caseOrDefaultPc));
              if (counts) {
                caseOrDefaultHits = counts->numExec();
              }

              // Remove fallthrough.
              fallsThroughHits = 0;
              if (foundLastCaseOrDefault) {
                // Walk from the previous case to the current one to
                // check if it fallthrough into the current block.
                MOZ_ASSERT(lastCaseOrDefaultPc != firstCaseOrDefaultPc - 1);
                jsbytecode* endpc = lastCaseOrDefaultPc;
                while (GetNextPc(endpc) < caseOrDefaultPc) {
                  endpc = GetNextPc(endpc);
                  MOZ_ASSERT(script->code() <= endpc && endpc < end);
                }

                if (BytecodeFallsThrough(JSOp(*endpc))) {
                  fallsThroughHits = script->getHitCount(endpc);
                }
              }
              caseOrDefaultHits -= fallsThroughHits;
            } else {
              caseOrDefaultHits = defaultHits;
            }
          }

          outBRDA_.printf("BRDA:%zu,%zu,%zu,", lineno, branchId, caseId);
          if (hits) {
            outBRDA_.printf("%" PRIu64 "\n", caseOrDefaultHits);
          } else {
            outBRDA_.put("-\n", 2);
          }

          numBranchesFound_++;
          numBranchesHit_ += !!caseOrDefaultHits;
          if (i < numCases) {
            defaultHits -= caseOrDefaultHits;
          }
          caseId++;
        }
      }
    }
  }

  if (outFN_.hadOutOfMemory() || outFNDA_.hadOutOfMemory() ||
      outBRDA_.hadOutOfMemory()) {
    hadOOM_ = true;
    return;
  }

  // If this script is the top-level script, then record it such that we can
  // assume that the code coverage report is complete, as this script has
  // references on all inner scripts.
  if (script->isTopLevel()) {
    hasTopLevelScript_ = true;
  }
}

LCovRealm::LCovRealm(JS::Realm* realm)
    : alloc_(4096, js::MallocArena), outTN_(&alloc_), sources_(alloc_) {
  // Record realm name. If we wait until finalization, the embedding may not be
  // able to provide us the name anymore.
  writeRealmName(realm);
}

LCovRealm::~LCovRealm() {
  // The LCovSource are in the LifoAlloc but we must still manually invoke
  // destructors to avoid leaks.
  while (!sources_.empty()) {
    LCovSource* source = sources_.popCopy();
    source->~LCovSource();
  }
}

LCovSource* LCovRealm::lookupOrAdd(const char* name) {
  // Find existing source if it exists.
  for (LCovSource* source : sources_) {
    if (source->match(name)) {
      return source;
    }
  }

  UniqueChars source_name = DuplicateString(name);
  if (!source_name) {
    return nullptr;
  }

  // Allocate a new LCovSource for the current top-level.
  LCovSource* source = alloc_.new_<LCovSource>(&alloc_, std::move(source_name));
  if (!source) {
    return nullptr;
  }

  if (!sources_.emplaceBack(source)) {
    return nullptr;
  }

  return source;
}

void LCovRealm::exportInto(GenericPrinter& out, bool* isEmpty) const {
  if (outTN_.hadOutOfMemory()) {
    return;
  }

  // If we only have cloned function, then do not serialize anything.
  bool someComplete = false;
  for (const LCovSource* sc : sources_) {
    if (sc->isComplete()) {
      someComplete = true;
      break;
    };
  }

  if (!someComplete) {
    return;
  }

  *isEmpty = false;
  outTN_.exportInto(out);
  for (LCovSource* sc : sources_) {
    // Only write if everything got recorded.
    if (sc->isComplete()) {
      sc->exportInto(out);
    }
  }
}

void LCovRealm::writeRealmName(JS::Realm* realm) {
  JSContext* cx = TlsContext.get();

  // lcov trace files are starting with an optional test case name, that we
  // recycle to be a realm name.
  //
  // Note: The test case name has some constraint in terms of valid character,
  // thus we escape invalid chracters with a "_" symbol in front of its
  // hexadecimal code.
  outTN_.put("TN:");
  if (cx->runtime()->realmNameCallback) {
    char name[1024];
    {
      // Hazard analysis cannot tell that the callback does not GC.
      JS::AutoSuppressGCAnalysis nogc;
      (*cx->runtime()->realmNameCallback)(cx, realm, name, sizeof(name), nogc);
    }
    for (char* s = name; s < name + sizeof(name) && *s; s++) {
      if (('a' <= *s && *s <= 'z') || ('A' <= *s && *s <= 'Z') ||
          ('0' <= *s && *s <= '9')) {
        outTN_.put(s, 1);
        continue;
      }
      outTN_.printf("_%p", (void*)size_t(*s));
    }
    outTN_.put("\n", 1);
  } else {
    outTN_.printf("Realm_%p%p\n", (void*)size_t('_'), realm);
  }
}

const char* LCovRealm::getScriptName(JSScript* script) {
  JSFunction* fun = script->function();
  if (fun && fun->fullDisplayAtom()) {
    JSAtom* atom = fun->fullDisplayAtom();
    size_t lenWithNull = js::PutEscapedString(nullptr, 0, atom, 0) + 1;
    char* name = alloc_.newArray<char>(lenWithNull);
    if (name) {
      js::PutEscapedString(name, lenWithNull, atom, 0);
    }
    return name;
  }
  return "top-level";
}

bool gLCovIsEnabled = false;

void InitLCov() {
  const char* outDir = getenv("JS_CODE_COVERAGE_OUTPUT_DIR");
  if (outDir && *outDir != 0) {
    EnableLCov();
  }
}

void EnableLCov() {
  MOZ_ASSERT(!JSRuntime::hasLiveRuntimes(),
             "EnableLCov must not be called after creating a runtime!");
  gLCovIsEnabled = true;
}

LCovRuntime::LCovRuntime() : pid_(getpid()), isEmpty_(true) {}

LCovRuntime::~LCovRuntime() {
  if (out_.isInitialized()) {
    finishFile();
  }
}

bool LCovRuntime::fillWithFilename(char* name, size_t length) {
  const char* outDir = getenv("JS_CODE_COVERAGE_OUTPUT_DIR");
  if (!outDir || *outDir == 0) {
    return false;
  }

  int64_t timestamp = static_cast<double>(PRMJ_Now()) / PRMJ_USEC_PER_SEC;
  static mozilla::Atomic<size_t> globalRuntimeId(0);
  size_t rid = globalRuntimeId++;

  int len = snprintf(name, length, "%s/%" PRId64 "-%" PRIu32 "-%zu.info",
                     outDir, timestamp, pid_, rid);
  if (len < 0 || size_t(len) >= length) {
    fprintf(stderr,
            "Warning: LCovRuntime::init: Cannot serialize file name.\n");
    return false;
  }

  return true;
}

void LCovRuntime::init() {
  char name[1024];
  if (!fillWithFilename(name, sizeof(name))) {
    return;
  }

  // If we cannot open the file, report a warning.
  if (!out_.init(name)) {
    fprintf(stderr,
            "Warning: LCovRuntime::init: Cannot open file named '%s'.\n", name);
  }
  isEmpty_ = true;
}

void LCovRuntime::finishFile() {
  MOZ_ASSERT(out_.isInitialized());
  out_.finish();

  if (isEmpty_) {
    char name[1024];
    if (!fillWithFilename(name, sizeof(name))) {
      return;
    }
    remove(name);
  }
}

void LCovRuntime::writeLCovResult(LCovRealm& realm) {
  if (!out_.isInitialized()) {
    init();
    if (!out_.isInitialized()) {
      return;
    }
  }

  uint32_t p = getpid();
  if (pid_ != p) {
    pid_ = p;
    finishFile();
    init();
    if (!out_.isInitialized()) {
      return;
    }
  }

  realm.exportInto(out_, &isEmpty_);
  out_.flush();
  finishFile();
}

bool InitScriptCoverage(JSContext* cx, JSScript* script) {
  MOZ_ASSERT(IsLCovEnabled());
  MOZ_ASSERT(script->hasBytecode(),
             "Only initialize coverage data for fully initialized scripts.");

  const char* filename = script->filename();
  if (!filename) {
    return true;
  }

  // Create LCovRealm if necessary.
  LCovRealm* lcovRealm = script->realm()->lcovRealm();
  if (!lcovRealm) {
    ReportOutOfMemory(cx);
    return false;
  }

  // Create LCovSource if necessary.
  LCovSource* source = lcovRealm->lookupOrAdd(filename);
  if (!source) {
    ReportOutOfMemory(cx);
    return false;
  }

  // Computed the formated script name.
  const char* scriptName = lcovRealm->getScriptName(script);
  if (!scriptName) {
    ReportOutOfMemory(cx);
    return false;
  }

  // Create Zone::scriptLCovMap if necessary.
  JS::Zone* zone = script->zone();
  if (!zone->scriptLCovMap) {
    zone->scriptLCovMap = cx->make_unique<ScriptLCovMap>();
  }
  if (!zone->scriptLCovMap) {
    return false;
  }

  MOZ_ASSERT(script->hasBytecode());

  // Save source in map for when we collect coverage.
  if (!zone->scriptLCovMap->putNew(script,
                                   std::make_tuple(source, scriptName))) {
    ReportOutOfMemory(cx);
    return false;
  }

  return true;
}

bool CollectScriptCoverage(JSScript* script, bool finalizing) {
  MOZ_ASSERT(IsLCovEnabled());

  ScriptLCovMap* map = script->zone()->scriptLCovMap.get();
  if (!map) {
    return false;
  }

  auto p = map->lookup(script);
  if (!p.found()) {
    return false;
  }

  auto [source, scriptName] = p->value();

  if (script->hasBytecode()) {
    source->writeScript(script, scriptName);
  }

  if (finalizing) {
    map->remove(p);
  }

  // Propagate the failure in case caller wants to terminate early.
  return !source->hadOutOfMemory();
}

}  // namespace coverage
}  // namespace js

Messung V0.5
C=89 H=96 G=92

¤ Dauer der Verarbeitung: 0.16 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge