tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

CodeCoverage.cpp (19143B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "vm/CodeCoverage.h"
      8 
      9 #include "mozilla/Atomics.h"
     10 #include "mozilla/IntegerPrintfMacros.h"
     11 
     12 #include <stdio.h>
     13 #include <utility>
     14 
     15 #include "frontend/SourceNotes.h"  // SrcNote, SrcNoteType, SrcNoteIterator
     16 #include "gc/Zone.h"
     17 #include "util/GetPidProvider.h"  // getpid()
     18 #include "util/Text.h"
     19 #include "vm/BytecodeUtil.h"
     20 #include "vm/JSScript.h"
     21 #include "vm/Realm.h"
     22 #include "vm/Runtime.h"
     23 #include "vm/Time.h"
     24 
     25 // This file contains a few functions which are used to produce files understood
     26 // by lcov tools. A detailed description of the format is available in the man
     27 // page for "geninfo" [1].  To make it short, the following paraphrases what is
     28 // commented in the man page by using curly braces prefixed by for-each to
     29 // express repeated patterns.
     30 //
     31 //   TN:<compartment name>
     32 //   for-each <source file> {
     33 //     SF:<filename>
     34 //     for-each <script> {
     35 //       FN:<line>,<name>
     36 //     }
     37 //     for-each <script> {
     38 //       FNDA:<hits>,<name>
     39 //     }
     40 //     FNF:<number of scripts>
     41 //     FNH:<sum of scripts hits>
     42 //     for-each <script> {
     43 //       for-each <branch> {
     44 //         BRDA:<line>,<block id>,<target id>,<taken>
     45 //       }
     46 //     }
     47 //     BRF:<number of branches>
     48 //     BRH:<sum of branches hits>
     49 //     for-each <script> {
     50 //       for-each <line> {
     51 //         DA:<line>,<hits>
     52 //       }
     53 //     }
     54 //     LF:<number of lines>
     55 //     LH:<sum of lines hits>
     56 //   }
     57 //
     58 // [1] http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php
     59 //
     60 namespace js {
     61 namespace coverage {
     62 
     63 LCovSource::LCovSource(LifoAlloc* alloc, UniqueChars name)
     64    : name_(std::move(name)),
     65      outFN_(alloc),
     66      outFNDA_(alloc),
     67      numFunctionsFound_(0),
     68      numFunctionsHit_(0),
     69      outBRDA_(alloc),
     70      numBranchesFound_(0),
     71      numBranchesHit_(0),
     72      numLinesInstrumented_(0),
     73      numLinesHit_(0),
     74      maxLineHit_(0),
     75      hasTopLevelScript_(false),
     76      hadOOM_(false) {}
     77 
     78 void LCovSource::exportInto(GenericPrinter& out) {
     79  if (hadOutOfMemory()) {
     80    out.setPendingOutOfMemory();
     81  } else {
     82    out.printf("SF:%s\n", name_.get());
     83 
     84    outFN_.exportInto(out);
     85    outFNDA_.exportInto(out);
     86    out.printf("FNF:%zu\n", numFunctionsFound_);
     87    out.printf("FNH:%zu\n", numFunctionsHit_);
     88 
     89    outBRDA_.exportInto(out);
     90    out.printf("BRF:%zu\n", numBranchesFound_);
     91    out.printf("BRH:%zu\n", numBranchesHit_);
     92 
     93    if (!linesHit_.empty()) {
     94      for (size_t lineno = 1; lineno <= maxLineHit_; ++lineno) {
     95        if (auto p = linesHit_.lookup(lineno)) {
     96          out.printf("DA:%zu,%" PRIu64 "\n", lineno, p->value());
     97        }
     98      }
     99    }
    100 
    101    out.printf("LF:%zu\n", numLinesInstrumented_);
    102    out.printf("LH:%zu\n", numLinesHit_);
    103 
    104    out.put("end_of_record\n");
    105  }
    106 
    107  outFN_.clear();
    108  outFNDA_.clear();
    109  numFunctionsFound_ = 0;
    110  numFunctionsHit_ = 0;
    111  outBRDA_.clear();
    112  numBranchesFound_ = 0;
    113  numBranchesHit_ = 0;
    114  linesHit_.clear();
    115  numLinesInstrumented_ = 0;
    116  numLinesHit_ = 0;
    117  maxLineHit_ = 0;
    118 }
    119 
    120 void LCovSource::writeScript(JSScript* script, const char* scriptName) {
    121  if (hadOutOfMemory()) {
    122    return;
    123  }
    124 
    125  numFunctionsFound_++;
    126  outFN_.printf("FN:%u,%s\n", script->lineno(), scriptName);
    127 
    128  uint64_t hits = 0;
    129  ScriptCounts* sc = nullptr;
    130  if (script->hasScriptCounts()) {
    131    sc = &script->getScriptCounts();
    132    numFunctionsHit_++;
    133    const PCCounts* counts =
    134        sc->maybeGetPCCounts(script->pcToOffset(script->main()));
    135    outFNDA_.printf("FNDA:%" PRIu64 ",%s\n", counts->numExec(), scriptName);
    136 
    137    // Set the hit count of the pre-main code to 1, if the function ever got
    138    // visited.
    139    hits = 1;
    140  }
    141 
    142  jsbytecode* snpc = script->code();
    143  const SrcNote* sn = script->notes();
    144  const SrcNote* snEnd = script->notesEnd();
    145  if (sn < snEnd) {
    146    snpc += sn->delta();
    147  }
    148 
    149  size_t lineno = script->lineno();
    150  jsbytecode* end = script->codeEnd();
    151  size_t branchId = 0;
    152  bool firstLineHasBeenWritten = false;
    153  for (jsbytecode* pc = script->code(); pc != end; pc = GetNextPc(pc)) {
    154    MOZ_ASSERT(script->code() <= pc && pc < end);
    155    JSOp op = JSOp(*pc);
    156    bool jump = IsJumpOpcode(op) || op == JSOp::TableSwitch;
    157    bool fallsthrough = BytecodeFallsThrough(op);
    158 
    159    // If the current script & pc has a hit-count report, then update the
    160    // current number of hits.
    161    if (sc) {
    162      const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(pc));
    163      if (counts) {
    164        hits = counts->numExec();
    165      }
    166    }
    167 
    168    // If we have additional source notes, walk all the source notes of the
    169    // current pc.
    170    if (snpc <= pc || !firstLineHasBeenWritten) {
    171      size_t oldLine = lineno;
    172      SrcNoteIterator iter(sn, snEnd);
    173      while (!iter.atEnd() && snpc <= pc) {
    174        sn = *iter;
    175        SrcNoteType type = sn->type();
    176        if (type == SrcNoteType::SetLine) {
    177          lineno = SrcNote::SetLine::getLine(sn, script->lineno());
    178        } else if (type == SrcNoteType::SetLineColumn) {
    179          lineno = SrcNote::SetLineColumn::getLine(sn, script->lineno());
    180        } else if (type == SrcNoteType::NewLine ||
    181                   type == SrcNoteType::NewLineColumn) {
    182          lineno++;
    183        }
    184        ++iter;
    185        if (!iter.atEnd()) {
    186          snpc += (*iter)->delta();
    187        }
    188      }
    189      sn = *iter;
    190 
    191      if ((oldLine != lineno || !firstLineHasBeenWritten) &&
    192          pc >= script->main() && fallsthrough) {
    193        auto p = linesHit_.lookupForAdd(lineno);
    194        if (!p) {
    195          if (!linesHit_.add(p, lineno, hits)) {
    196            hadOOM_ = true;
    197            return;
    198          }
    199          numLinesInstrumented_++;
    200          if (hits != 0) {
    201            numLinesHit_++;
    202          }
    203          maxLineHit_ = std::max(lineno, maxLineHit_);
    204        } else {
    205          if (p->value() == 0 && hits != 0) {
    206            numLinesHit_++;
    207          }
    208          p->value() += hits;
    209        }
    210 
    211        firstLineHasBeenWritten = true;
    212      }
    213    }
    214 
    215    // If the current instruction has thrown, then decrement the hit counts
    216    // with the number of throws.
    217    if (sc) {
    218      const PCCounts* counts = sc->maybeGetThrowCounts(script->pcToOffset(pc));
    219      if (counts) {
    220        hits -= counts->numExec();
    221      }
    222    }
    223 
    224    // If the current pc corresponds to a conditional jump instruction, then
    225    // reports branch hits.
    226    if (jump && fallsthrough) {
    227      jsbytecode* fallthroughTarget = GetNextPc(pc);
    228      uint64_t fallthroughHits = 0;
    229      if (sc) {
    230        const PCCounts* counts =
    231            sc->maybeGetPCCounts(script->pcToOffset(fallthroughTarget));
    232        if (counts) {
    233          fallthroughHits = counts->numExec();
    234        }
    235      }
    236 
    237      uint64_t taken = hits - fallthroughHits;
    238      outBRDA_.printf("BRDA:%zu,%zu,0,", lineno, branchId);
    239      if (hits) {
    240        outBRDA_.printf("%" PRIu64 "\n", taken);
    241      } else {
    242        outBRDA_.put("-\n", 2);
    243      }
    244 
    245      outBRDA_.printf("BRDA:%zu,%zu,1,", lineno, branchId);
    246      if (hits) {
    247        outBRDA_.printf("%" PRIu64 "\n", fallthroughHits);
    248      } else {
    249        outBRDA_.put("-\n", 2);
    250      }
    251 
    252      // Count the number of branches, and the number of branches hit.
    253      numBranchesFound_ += 2;
    254      if (hits) {
    255        numBranchesHit_ += !!taken + !!fallthroughHits;
    256      }
    257      branchId++;
    258    }
    259 
    260    // If the current pc corresponds to a pre-computed switch case, then
    261    // reports branch hits for each case statement.
    262    if (jump && op == JSOp::TableSwitch) {
    263      // Get the default pc.
    264      jsbytecode* defaultpc = pc + GET_JUMP_OFFSET(pc);
    265      MOZ_ASSERT(script->code() <= defaultpc && defaultpc < end);
    266      MOZ_ASSERT(defaultpc > pc);
    267 
    268      // Get the low and high from the tableswitch
    269      int32_t low = GET_JUMP_OFFSET(pc + JUMP_OFFSET_LEN * 1);
    270      int32_t high = GET_JUMP_OFFSET(pc + JUMP_OFFSET_LEN * 2);
    271      MOZ_ASSERT(high - low + 1 >= 0);
    272      size_t numCases = high - low + 1;
    273 
    274      auto getCaseOrDefaultPc = [&](size_t index) {
    275        if (index < numCases) {
    276          return script->tableSwitchCasePC(pc, index);
    277        }
    278        MOZ_ASSERT(index == numCases);
    279        return defaultpc;
    280      };
    281 
    282      jsbytecode* firstCaseOrDefaultPc = end;
    283      for (size_t j = 0; j < numCases + 1; j++) {
    284        jsbytecode* testpc = getCaseOrDefaultPc(j);
    285        MOZ_ASSERT(script->code() <= testpc && testpc < end);
    286        if (testpc < firstCaseOrDefaultPc) {
    287          firstCaseOrDefaultPc = testpc;
    288        }
    289      }
    290 
    291      // Count the number of hits of the default branch, by subtracting
    292      // the number of hits of each cases.
    293      uint64_t defaultHits = hits;
    294 
    295      // Count the number of hits of the previous case entry.
    296      uint64_t fallsThroughHits = 0;
    297 
    298      // Record branches for each case and default.
    299      size_t caseId = 0;
    300      for (size_t i = 0; i < numCases + 1; i++) {
    301        jsbytecode* caseOrDefaultPc = getCaseOrDefaultPc(i);
    302        MOZ_ASSERT(script->code() <= caseOrDefaultPc && caseOrDefaultPc < end);
    303 
    304        // PCs might not be in increasing order of case indexes.
    305        jsbytecode* lastCaseOrDefaultPc = firstCaseOrDefaultPc - 1;
    306        bool foundLastCaseOrDefault = false;
    307        for (size_t j = 0; j < numCases + 1; j++) {
    308          jsbytecode* testpc = getCaseOrDefaultPc(j);
    309          MOZ_ASSERT(script->code() <= testpc && testpc < end);
    310          if (lastCaseOrDefaultPc < testpc &&
    311              (testpc < caseOrDefaultPc ||
    312               (j < i && testpc == caseOrDefaultPc))) {
    313            lastCaseOrDefaultPc = testpc;
    314            foundLastCaseOrDefault = true;
    315          }
    316        }
    317 
    318        // If multiple case instruction have the same code block, only
    319        // register the code coverage the first time we hit this case.
    320        if (!foundLastCaseOrDefault || caseOrDefaultPc != lastCaseOrDefaultPc) {
    321          uint64_t caseOrDefaultHits = 0;
    322          if (sc) {
    323            if (i < numCases) {
    324              // Case (i + low)
    325              const PCCounts* counts =
    326                  sc->maybeGetPCCounts(script->pcToOffset(caseOrDefaultPc));
    327              if (counts) {
    328                caseOrDefaultHits = counts->numExec();
    329              }
    330 
    331              // Remove fallthrough.
    332              fallsThroughHits = 0;
    333              if (foundLastCaseOrDefault) {
    334                // Walk from the previous case to the current one to
    335                // check if it fallthrough into the current block.
    336                MOZ_ASSERT(lastCaseOrDefaultPc != firstCaseOrDefaultPc - 1);
    337                jsbytecode* endpc = lastCaseOrDefaultPc;
    338                while (GetNextPc(endpc) < caseOrDefaultPc) {
    339                  endpc = GetNextPc(endpc);
    340                  MOZ_ASSERT(script->code() <= endpc && endpc < end);
    341                }
    342 
    343                if (BytecodeFallsThrough(JSOp(*endpc))) {
    344                  fallsThroughHits = script->getHitCount(endpc);
    345                }
    346              }
    347              caseOrDefaultHits -= fallsThroughHits;
    348            } else {
    349              caseOrDefaultHits = defaultHits;
    350            }
    351          }
    352 
    353          outBRDA_.printf("BRDA:%zu,%zu,%zu,", lineno, branchId, caseId);
    354          if (hits) {
    355            outBRDA_.printf("%" PRIu64 "\n", caseOrDefaultHits);
    356          } else {
    357            outBRDA_.put("-\n", 2);
    358          }
    359 
    360          numBranchesFound_++;
    361          numBranchesHit_ += !!caseOrDefaultHits;
    362          if (i < numCases) {
    363            defaultHits -= caseOrDefaultHits;
    364          }
    365          caseId++;
    366        }
    367      }
    368    }
    369  }
    370 
    371  if (outFN_.hadOutOfMemory() || outFNDA_.hadOutOfMemory() ||
    372      outBRDA_.hadOutOfMemory()) {
    373    hadOOM_ = true;
    374    return;
    375  }
    376 
    377  // If this script is the top-level script, then record it such that we can
    378  // assume that the code coverage report is complete, as this script has
    379  // references on all inner scripts.
    380  if (script->isTopLevel()) {
    381    hasTopLevelScript_ = true;
    382  }
    383 }
    384 
    385 LCovRealm::LCovRealm(JS::Realm* realm)
    386    : alloc_(4096, js::MallocArena), outTN_(&alloc_), sources_(alloc_) {
    387  // Record realm name. If we wait until finalization, the embedding may not be
    388  // able to provide us the name anymore.
    389  writeRealmName(realm);
    390 }
    391 
    392 LCovRealm::~LCovRealm() {
    393  // The LCovSource are in the LifoAlloc but we must still manually invoke
    394  // destructors to avoid leaks.
    395  while (!sources_.empty()) {
    396    LCovSource* source = sources_.popCopy();
    397    source->~LCovSource();
    398  }
    399 }
    400 
    401 LCovSource* LCovRealm::lookupOrAdd(const char* name) {
    402  // Find existing source if it exists.
    403  for (LCovSource* source : sources_) {
    404    if (source->match(name)) {
    405      return source;
    406    }
    407  }
    408 
    409  UniqueChars source_name = DuplicateString(name);
    410  if (!source_name) {
    411    return nullptr;
    412  }
    413 
    414  // Allocate a new LCovSource for the current top-level.
    415  LCovSource* source = alloc_.new_<LCovSource>(&alloc_, std::move(source_name));
    416  if (!source) {
    417    return nullptr;
    418  }
    419 
    420  if (!sources_.emplaceBack(source)) {
    421    return nullptr;
    422  }
    423 
    424  return source;
    425 }
    426 
    427 void LCovRealm::exportInto(GenericPrinter& out, bool* isEmpty) const {
    428  if (outTN_.hadOutOfMemory()) {
    429    return;
    430  }
    431 
    432  // If we only have cloned function, then do not serialize anything.
    433  bool someComplete = false;
    434  for (const LCovSource* sc : sources_) {
    435    if (sc->isComplete()) {
    436      someComplete = true;
    437      break;
    438    };
    439  }
    440 
    441  if (!someComplete) {
    442    return;
    443  }
    444 
    445  *isEmpty = false;
    446  outTN_.exportInto(out);
    447  for (LCovSource* sc : sources_) {
    448    // Only write if everything got recorded.
    449    if (sc->isComplete()) {
    450      sc->exportInto(out);
    451    }
    452  }
    453 }
    454 
    455 void LCovRealm::writeRealmName(JS::Realm* realm) {
    456  JSContext* cx = TlsContext.get();
    457 
    458  // lcov trace files are starting with an optional test case name, that we
    459  // recycle to be a realm name.
    460  //
    461  // Note: The test case name has some constraint in terms of valid character,
    462  // thus we escape invalid chracters with a "_" symbol in front of its
    463  // hexadecimal code.
    464  outTN_.put("TN:");
    465  if (cx->runtime()->realmNameCallback) {
    466    char name[1024];
    467    {
    468      // Hazard analysis cannot tell that the callback does not GC.
    469      JS::AutoSuppressGCAnalysis nogc;
    470      (*cx->runtime()->realmNameCallback)(cx, realm, name, sizeof(name), nogc);
    471    }
    472    for (char* s = name; s < name + sizeof(name) && *s; s++) {
    473      if (('a' <= *s && *s <= 'z') || ('A' <= *s && *s <= 'Z') ||
    474          ('0' <= *s && *s <= '9')) {
    475        outTN_.put(s, 1);
    476        continue;
    477      }
    478      outTN_.printf("_%p", (void*)size_t(*s));
    479    }
    480    outTN_.put("\n", 1);
    481  } else {
    482    outTN_.printf("Realm_%p%p\n", (void*)size_t('_'), realm);
    483  }
    484 }
    485 
    486 const char* LCovRealm::getScriptName(JSScript* script) {
    487  JSFunction* fun = script->function();
    488  if (fun && fun->fullDisplayAtom()) {
    489    JSAtom* atom = fun->fullDisplayAtom();
    490    size_t lenWithNull = js::PutEscapedString(nullptr, 0, atom, 0) + 1;
    491    char* name = alloc_.newArray<char>(lenWithNull);
    492    if (name) {
    493      js::PutEscapedString(name, lenWithNull, atom, 0);
    494    }
    495    return name;
    496  }
    497  return "top-level";
    498 }
    499 
    500 bool gLCovIsEnabled = false;
    501 
    502 void InitLCov() {
    503  const char* outDir = getenv("JS_CODE_COVERAGE_OUTPUT_DIR");
    504  if (outDir && *outDir != 0) {
    505    EnableLCov();
    506  }
    507 }
    508 
    509 void EnableLCov() {
    510  MOZ_ASSERT(!JSRuntime::hasLiveRuntimes(),
    511             "EnableLCov must not be called after creating a runtime!");
    512  gLCovIsEnabled = true;
    513 }
    514 
    515 LCovRuntime::LCovRuntime() : pid_(getpid()), isEmpty_(true) {}
    516 
    517 LCovRuntime::~LCovRuntime() {
    518  if (out_.isInitialized()) {
    519    finishFile();
    520  }
    521 }
    522 
    523 bool LCovRuntime::fillWithFilename(char* name, size_t length) {
    524  const char* outDir = getenv("JS_CODE_COVERAGE_OUTPUT_DIR");
    525  if (!outDir || *outDir == 0) {
    526    return false;
    527  }
    528 
    529  int64_t timestamp = static_cast<double>(PRMJ_Now()) / PRMJ_USEC_PER_SEC;
    530  static mozilla::Atomic<size_t> globalRuntimeId(0);
    531  size_t rid = globalRuntimeId++;
    532 
    533  int len = snprintf(name, length, "%s/%" PRId64 "-%" PRIu32 "-%zu.info",
    534                     outDir, timestamp, pid_, rid);
    535  if (len < 0 || size_t(len) >= length) {
    536    fprintf(stderr,
    537            "Warning: LCovRuntime::init: Cannot serialize file name.\n");
    538    return false;
    539  }
    540 
    541  return true;
    542 }
    543 
    544 void LCovRuntime::init() {
    545  char name[1024];
    546  if (!fillWithFilename(name, sizeof(name))) {
    547    return;
    548  }
    549 
    550  // If we cannot open the file, report a warning.
    551  if (!out_.init(name)) {
    552    fprintf(stderr,
    553            "Warning: LCovRuntime::init: Cannot open file named '%s'.\n", name);
    554  }
    555  isEmpty_ = true;
    556 }
    557 
    558 void LCovRuntime::finishFile() {
    559  MOZ_ASSERT(out_.isInitialized());
    560  out_.finish();
    561 
    562  if (isEmpty_) {
    563    char name[1024];
    564    if (!fillWithFilename(name, sizeof(name))) {
    565      return;
    566    }
    567    remove(name);
    568  }
    569 }
    570 
    571 void LCovRuntime::writeLCovResult(LCovRealm& realm) {
    572  if (!out_.isInitialized()) {
    573    init();
    574    if (!out_.isInitialized()) {
    575      return;
    576    }
    577  }
    578 
    579  uint32_t p = getpid();
    580  if (pid_ != p) {
    581    pid_ = p;
    582    finishFile();
    583    init();
    584    if (!out_.isInitialized()) {
    585      return;
    586    }
    587  }
    588 
    589  realm.exportInto(out_, &isEmpty_);
    590  out_.flush();
    591  finishFile();
    592 }
    593 
    594 bool InitScriptCoverage(JSContext* cx, JSScript* script) {
    595  MOZ_ASSERT(IsLCovEnabled());
    596  MOZ_ASSERT(script->hasBytecode(),
    597             "Only initialize coverage data for fully initialized scripts.");
    598 
    599  const char* filename = script->filename();
    600  if (!filename) {
    601    return true;
    602  }
    603 
    604  // Create LCovRealm if necessary.
    605  LCovRealm* lcovRealm = script->realm()->lcovRealm();
    606  if (!lcovRealm) {
    607    ReportOutOfMemory(cx);
    608    return false;
    609  }
    610 
    611  // Create LCovSource if necessary.
    612  LCovSource* source = lcovRealm->lookupOrAdd(filename);
    613  if (!source) {
    614    ReportOutOfMemory(cx);
    615    return false;
    616  }
    617 
    618  // Computed the formated script name.
    619  const char* scriptName = lcovRealm->getScriptName(script);
    620  if (!scriptName) {
    621    ReportOutOfMemory(cx);
    622    return false;
    623  }
    624 
    625  // Create Zone::scriptLCovMap if necessary.
    626  JS::Zone* zone = script->zone();
    627  if (!zone->scriptLCovMap) {
    628    zone->scriptLCovMap = cx->make_unique<ScriptLCovMap>();
    629  }
    630  if (!zone->scriptLCovMap) {
    631    return false;
    632  }
    633 
    634  MOZ_ASSERT(script->hasBytecode());
    635 
    636  // Save source in map for when we collect coverage.
    637  if (!zone->scriptLCovMap->putNew(script,
    638                                   std::make_tuple(source, scriptName))) {
    639    ReportOutOfMemory(cx);
    640    return false;
    641  }
    642 
    643  return true;
    644 }
    645 
    646 bool CollectScriptCoverage(JSScript* script, bool finalizing) {
    647  MOZ_ASSERT(IsLCovEnabled());
    648 
    649  ScriptLCovMap* map = script->zone()->scriptLCovMap.get();
    650  if (!map) {
    651    return false;
    652  }
    653 
    654  auto p = map->lookup(script);
    655  if (!p.found()) {
    656    return false;
    657  }
    658 
    659  auto [source, scriptName] = p->value();
    660 
    661  if (script->hasBytecode()) {
    662    source->writeScript(script, scriptName);
    663  }
    664 
    665  if (finalizing) {
    666    map->remove(p);
    667  }
    668 
    669  // Propagate the failure in case caller wants to terminate early.
    670  return !source->hadOutOfMemory();
    671 }
    672 
    673 }  // namespace coverage
    674 }  // namespace js