Global Metrics

path: .metrics.halstead.N1
old: 295.0
new: 861.0

path: .metrics.halstead.level
old: 0.036521739130434785
new: 0.021782774543264403

path: .metrics.halstead.bugs
old: 0.6633095336205289
new: 2.1817781290704517

path: .metrics.halstead.estimated_program_length
old: 653.051068257784
new: 1857.2303540429637

path: .metrics.halstead.n2
old: 84.0
new: 217.0

path: .metrics.halstead.purity_ratio
old: 1.3633633992855616
new: 1.2835040456413018

path: .metrics.halstead.time
old: 4931.553353575885
new: 29418.820259039974

path: .metrics.halstead.length
old: 479.0
new: 1447.0

path: .metrics.halstead.vocabulary
old: 109.0
new: 251.0

path: .metrics.halstead.difficulty
old: 27.38095238095238
new: 45.90783410138249

path: .metrics.halstead.effort
old: 88767.96036436594
new: 529538.7646627196

path: .metrics.halstead.N2
old: 184.0
new: 586.0

path: .metrics.halstead.n1
old: 25.0
new: 34.0

path: .metrics.halstead.volume
old: 3241.9602915681476
new: 11534.823522566769

path: .metrics.nargs.average
old: 1.5
new: 0.4642857142857143

path: .metrics.nargs.sum
old: 12.0
new: 13.0

path: .metrics.nom.functions
old: 8.0
new: 28.0

path: .metrics.nom.total
old: 8.0
new: 28.0

path: .metrics.cyclomatic.average
old: 2.4615384615384617
new: 2.2

path: .metrics.cyclomatic.sum
old: 32.0
new: 66.0

path: .metrics.nexits.sum
old: 6.0
new: 15.0

path: .metrics.nexits.average
old: 0.75
new: 0.5357142857142857

path: .metrics.cognitive.sum
old: 14.0
new: 42.0

path: .metrics.cognitive.average
old: 1.75
new: 1.5

path: .metrics.loc.ploc
old: 123.0
new: 376.0

path: .metrics.loc.cloc
old: 18.0
new: 85.0

path: .metrics.loc.blank
old: 24.0
new: 81.0

path: .metrics.loc.lloc
old: 39.0
new: 175.0

path: .metrics.loc.sloc
old: 165.0
new: 542.0

path: .metrics.mi.mi_sei
old: 8.14200981805325
new: -32.69141157438759

path: .metrics.mi.mi_visual_studio
old: 22.741069802064573
new: 3.0411907874204376

path: .metrics.mi.mi_original
old: 38.887229361530416
new: 5.200436246488948

Spaces Data

Minimal test - lines (33, 542)

path: .spaces[0].metrics.cognitive.average
old: 0.0
new: 1.5

path: .spaces[0].metrics.cognitive.sum
old: 0.0
new: 42.0

path: .spaces[0].metrics.cyclomatic.average
old: 1.6666666666666667
new: 2.2413793103448274

path: .spaces[0].metrics.cyclomatic.sum
old: 5.0
new: 65.0

path: .spaces[0].metrics.mi.mi_original
old: 98.2992938131474
new: 6.535677759864498

path: .spaces[0].metrics.mi.mi_sei
old: 66.62415101212449
new: -31.340718242370414

path: .spaces[0].metrics.mi.mi_visual_studio
old: 57.48496714219147
new: 3.822033777698537

path: .spaces[0].metrics.nom.functions
old: 1.0
new: 28.0

path: .spaces[0].metrics.nom.total
old: 1.0
new: 28.0

path: .spaces[0].metrics.halstead.difficulty
old: 10.0
new: 47.65024630541872

path: .spaces[0].metrics.halstead.n1
old: 11.0
new: 34.0

path: .spaces[0].metrics.halstead.effort
old: 2541.8760226232594
new: 537161.8985969517

path: .spaces[0].metrics.halstead.N2
old: 20.0
new: 569.0

path: .spaces[0].metrics.halstead.length
old: 57.0
new: 1429.0

path: .spaces[0].metrics.halstead.N1
old: 37.0
new: 860.0

path: .spaces[0].metrics.halstead.purity_ratio
old: 1.3352192212284304
new: 1.209962860595593

path: .spaces[0].metrics.halstead.level
old: 0.1
new: 0.02098625038767704

path: .spaces[0].metrics.halstead.n2
old: 11.0
new: 203.0

path: .spaces[0].metrics.halstead.volume
old: 254.18760226232595
new: 11273.014102675612

path: .spaces[0].metrics.halstead.estimated_program_length
old: 76.10749561002054
new: 1729.0369277911022

path: .spaces[0].metrics.halstead.vocabulary
old: 22.0
new: 237.0

path: .spaces[0].metrics.halstead.bugs
old: 0.062084280859812054
new: 2.2026671694574387

path: .spaces[0].metrics.halstead.time
old: 141.21533459018107
new: 29842.327699830657

path: .spaces[0].metrics.nargs.average
old: 2.0
new: 0.4642857142857143

path: .spaces[0].metrics.nargs.sum
old: 2.0
new: 13.0

path: .spaces[0].metrics.nexits.average
old: 0.0
new: 0.5357142857142857

path: .spaces[0].metrics.nexits.sum
old: 0.0
new: 15.0

path: .spaces[0].metrics.loc.cloc
old: 0.0
new: 77.0

path: .spaces[0].metrics.loc.lloc
old: 0.0
new: 175.0

path: .spaces[0].metrics.loc.ploc
old: 12.0
new: 356.0

path: .spaces[0].metrics.loc.blank
old: 2.0
new: 77.0

path: .spaces[0].metrics.loc.sloc
old: 14.0
new: 510.0

Code

namespace mozilla {
//
// BlockingResourceBase implementation
//

// static members
const char* const BlockingResourceBase::kResourceTypeName[] = {
    // needs to be kept in sync with BlockingResourceType
    "Mutex", "ReentrantMonitor", "CondVar", "RecursiveMutex"};

#ifdef DEBUG

PRCallOnceType BlockingResourceBase::sCallOnce;
MOZ_THREAD_LOCAL(BlockingResourceBase*)
BlockingResourceBase::sResourceAcqnChainFront;
BlockingResourceBase::DDT* BlockingResourceBase::sDeadlockDetector;

void BlockingResourceBase::StackWalkCallback(uint32_t aFrameNumber, void* aPc,
                                             void* aSp, void* aClosure) {
#  ifndef MOZ_CALLSTACK_DISABLED
  AcquisitionState* state = (AcquisitionState*)aClosure;
  state->ref().AppendElement(aPc);
#  endif
}

void BlockingResourceBase::GetStackTrace(AcquisitionState& aState) {
#  ifndef MOZ_CALLSTACK_DISABLED
  // Skip this function and the calling function.
  const uint32_t kSkipFrames = 2;

  // Clear the array...
  aState.reset();
  // ...and create a new one; this also puts the state to 'acquired' status
  // regardless of whether we obtain a stack trace or not.
  aState.emplace();

  // NB: Ignore the return value, there's nothing useful we can do if this
  //     this fails.
  MozStackWalk(StackWalkCallback, kSkipFrames, kAcquisitionStateStackSize,
               aState.ptr());
#  endif
}

/**
 * PrintCycle
 * Append to |aOut| detailed information about the circular
 * dependency in |aCycle|.  Returns true if it *appears* that this
 * cycle may represent an imminent deadlock, but this is merely a
 * heuristic; the value returned may be a false positive or false
 * negative.
 *
 * *NOT* thread safe.  Calls |Print()|.
 *
 * FIXME bug 456272 hack alert: because we can't write call
 * contexts into strings, all info is written to stderr, but only
 * some info is written into |aOut|
 */
static bool PrintCycle(
    const BlockingResourceBase::DDT::ResourceAcquisitionArray& aCycle,
    nsACString& aOut) {
  NS_ASSERTION(aCycle.Length() > 1, "need > 1 element for cycle!");

  bool maybeImminent = true;

  fputs("=== Cyclical dependency starts at\n", stderr);
  aOut += "Cyclical dependency starts at\n";

  const BlockingResourceBase::DDT::ResourceAcquisitionArray::elem_type res =
      aCycle.ElementAt(0);
  maybeImminent &= res->Print(aOut);

  BlockingResourceBase::DDT::ResourceAcquisitionArray::index_type i;
  BlockingResourceBase::DDT::ResourceAcquisitionArray::size_type len =
      aCycle.Length();
  const BlockingResourceBase::DDT::ResourceAcquisitionArray::elem_type* it =
      1 + aCycle.Elements();
  for (i = 1; i < len - 1; ++i, ++it) {
    fputs("\n--- Next dependency:\n", stderr);
    aOut += "\nNext dependency:\n";

    maybeImminent &= (*it)->Print(aOut);
  }

  fputs("\n=== Cycle completed at\n", stderr);
  aOut += "Cycle completed at\n";
  (*it)->Print(aOut);

  return maybeImminent;
}

bool BlockingResourceBase::Print(nsACString& aOut) const {
  fprintf(stderr, "--- %s : %s", kResourceTypeName[mType], mName);
  aOut += BlockingResourceBase::kResourceTypeName[mType];
  aOut += " : ";
  aOut += mName;

  bool acquired = IsAcquired();

  if (acquired) {
    fputs(" (currently acquired)\n", stderr);
    aOut += " (currently acquired)\n";
  }

  fputs(" calling context\n", stderr);
#  ifdef MOZ_CALLSTACK_DISABLED
  fputs("  [stack trace unavailable]\n", stderr);
#  else
  const AcquisitionState& state = acquired ? mAcquired : mFirstSeen;

  CodeAddressService<> addressService;

  for (uint32_t i = 0; i < state.ref().Length(); i++) {
    const size_t kMaxLength = 1024;
    char buffer[kMaxLength];
    addressService.GetLocation(i + 1, state.ref()[i], buffer, kMaxLength);
    const char* fmt = "    %s\n";
    aOut.AppendLiteral("    ");
    aOut.Append(buffer);
    aOut.AppendLiteral("\n");
    fprintf(stderr, fmt, buffer);
  }

#  endif

  return acquired;
}

BlockingResourceBase::BlockingResourceBase(
    const char* aName, BlockingResourceBase::BlockingResourceType aType)
    : mName(aName),
      mType(aType)
#  ifdef MOZ_CALLSTACK_DISABLED
      ,
      mAcquired(false)
#  else
      ,
      mAcquired()
#  endif
{
  MOZ_ASSERT(mName, "Name must be nonnull");
  // PR_CallOnce guaranatees that InitStatics is called in a
  // thread-safe way
  if (PR_SUCCESS != PR_CallOnce(&sCallOnce, InitStatics)) {
    MOZ_CRASH("can't initialize blocking resource static members");
  }

  mChainPrev = 0;
  sDeadlockDetector->Add(this);
}

BlockingResourceBase::~BlockingResourceBase() {
  // we don't check for really obviously bad things like freeing
  // Mutexes while they're still locked.  it is assumed that the
  // base class, or its underlying primitive, will check for such
  // stupid mistakes.
  mChainPrev = 0;  // racy only for stupidly buggy client code
  if (sDeadlockDetector) {
    sDeadlockDetector->Remove(this);
  }
}

size_t BlockingResourceBase::SizeOfDeadlockDetector(
    MallocSizeOf aMallocSizeOf) {
  return sDeadlockDetector
             ? sDeadlockDetector->SizeOfIncludingThis(aMallocSizeOf)
             : 0;
}

PRStatus BlockingResourceBase::InitStatics() {
  MOZ_ASSERT(sResourceAcqnChainFront.init());
  sDeadlockDetector = new DDT();
  if (!sDeadlockDetector) {
    MOZ_CRASH("can't allocate deadlock detector");
  }
  return PR_SUCCESS;
}

void BlockingResourceBase::Shutdown() {
  delete sDeadlockDetector;
  sDeadlockDetector = 0;
}

void BlockingResourceBase::CheckAcquire() {
  if (mType == eCondVar) {
    MOZ_ASSERT_UNREACHABLE(
        "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars");
    return;
  }

  BlockingResourceBase* chainFront = ResourceChainFront();
  mozilla::UniquePtr cycle(
      sDeadlockDetector->CheckAcquisition(chainFront ? chainFront : 0, this));
  if (!cycle) {
    return;
  }

#  ifndef MOZ_CALLSTACK_DISABLED
  // Update the current stack before printing.
  GetStackTrace(mAcquired);
#  endif

  fputs("###!!! ERROR: Potential deadlock detected:\n", stderr);
  nsAutoCString out("Potential deadlock detected:\n");
  bool maybeImminent = PrintCycle(*cycle, out);

  if (maybeImminent) {
    fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr);
    out.AppendLiteral("\n###!!! Deadlock may happen NOW!\n\n");
  } else {
    fputs("\nDeadlock may happen for some other execution\n\n", stderr);
    out.AppendLiteral("\nDeadlock may happen for some other execution\n\n");
  }

  // Only error out if we think a deadlock is imminent.
  if (maybeImminent) {
    NS_ERROR(out.get());
  } else {
    NS_WARNING(out.get());
  }
}

void BlockingResourceBase::Acquire() {
  if (mType == eCondVar) {
    MOZ_ASSERT_UNREACHABLE(
        "FIXME bug 456272: annots. to allow Acquire()ing condvars");
    return;
  }
  NS_ASSERTION(!IsAcquired(), "reacquiring already acquired resource");

  ResourceChainAppend(ResourceChainFront());

#  ifdef MOZ_CALLSTACK_DISABLED
  mAcquired = true;
#  else
  // Take a stack snapshot.
  GetStackTrace(mAcquired);
  MOZ_ASSERT(IsAcquired());

  if (!mFirstSeen) {
    mFirstSeen = mAcquired;
  }
#  endif
}

void BlockingResourceBase::Release() {
  if (mType == eCondVar) {
    MOZ_ASSERT_UNREACHABLE(
        "FIXME bug 456272: annots. to allow Release()ing condvars");
    return;
  }

  BlockingResourceBase* chainFront = ResourceChainFront();
  NS_ASSERTION(chainFront && IsAcquired(),
               "Release()ing something that hasn't been Acquire()ed");

  if (chainFront == this) {
    ResourceChainRemove();
  } else {
    // not an error, but makes code hard to reason about.
    NS_WARNING("Resource acquired is being released in non-LIFO order; why?\n");
    nsCString tmp;
    Print(tmp);

    // remove this resource from wherever it lives in the chain
    // we walk backwards in order of acquisition:
    //  (1)  ...node<-prev<-curr...
    //              /     /
    //  (2)  ...prev<-curr...
    BlockingResourceBase* curr = chainFront;
    BlockingResourceBase* prev = nullptr;
    while (curr && (prev = curr->mChainPrev) && (prev != this)) {
      curr = prev;
    }
    if (prev == this) {
      curr->mChainPrev = prev->mChainPrev;
    }
  }

  ClearAcquisitionState();
}

//
// Debug implementation of (OffTheBooks)Mutex
void OffTheBooksMutex::Lock() {
  CheckAcquire();
  this->lock();
  mOwningThread = PR_GetCurrentThread();
  Acquire();
}

bool OffTheBooksMutex::TryLock() {
  CheckAcquire();
  bool locked = this->tryLock();
  if (locked) {
    mOwningThread = PR_GetCurrentThread();
    Acquire();
  }
  return locked;
}

void OffTheBooksMutex::Unlock() {
  Release();
  mOwningThread = nullptr;
  this->unlock();
}

void OffTheBooksMutex::AssertCurrentThreadOwns() const {
  MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread());
}

//
// Debug implementation of RWLock
//

void RWLock::ReadLock() {
  // All we want to ensure here is that we're not attempting to acquire the
  // read lock while this thread is holding the write lock.
  CheckAcquire();
  this->ReadLockInternal();
  MOZ_ASSERT(mOwningThread == nullptr);
}

void RWLock::ReadUnlock() {
  MOZ_ASSERT(mOwningThread == nullptr);
  this->ReadUnlockInternal();
}

void RWLock::WriteLock() {
  CheckAcquire();
  this->WriteLockInternal();
  mOwningThread = PR_GetCurrentThread();
  Acquire();
}

void RWLock::WriteUnlock() {
  Release();
  mOwningThread = nullptr;
  this->WriteUnlockInternal();
}

//
// Debug implementation of ReentrantMonitor
void ReentrantMonitor::Enter() {
  BlockingResourceBase* chainFront = ResourceChainFront();

  // the code below implements monitor reentrancy semantics

  if (this == chainFront) {
    // immediately re-entered the monitor: acceptable
    PR_EnterMonitor(mReentrantMonitor);
    ++mEntryCount;
    return;
  }

  // this is sort of a hack around not recording the thread that
  // owns this monitor
  if (chainFront) {
    for (BlockingResourceBase* br = ResourceChainPrev(chainFront); br;
         br = ResourceChainPrev(br)) {
      if (br == this) {
        NS_WARNING(
            "Re-entering ReentrantMonitor after acquiring other resources.");

        // show the caller why this is potentially bad
        CheckAcquire();

        PR_EnterMonitor(mReentrantMonitor);
        ++mEntryCount;
        return;
      }
    }
  }

  CheckAcquire();
  PR_EnterMonitor(mReentrantMonitor);
  NS_ASSERTION(mEntryCount == 0, "ReentrantMonitor isn't free!");
  Acquire();  // protected by mReentrantMonitor
  mEntryCount = 1;
}

void ReentrantMonitor::Exit() {
  if (--mEntryCount == 0) {
    Release();  // protected by mReentrantMonitor
  }
  PRStatus status = PR_ExitMonitor(mReentrantMonitor);
  NS_ASSERTION(PR_SUCCESS == status, "bad ReentrantMonitor::Exit()");
}

nsresult ReentrantMonitor::Wait(PRIntervalTime aInterval) {
  AssertCurrentThreadIn();

  // save monitor state and reset it to empty
  int32_t savedEntryCount = mEntryCount;
  AcquisitionState savedAcquisitionState = GetAcquisitionState();
  BlockingResourceBase* savedChainPrev = mChainPrev;
  mEntryCount = 0;
  ClearAcquisitionState();
  mChainPrev = 0;

  nsresult rv;
  {
#  if defined(MOZILLA_INTERNAL_API)
    AUTO_PROFILER_THREAD_SLEEP;
#  endif
    // give up the monitor until we're back from Wait()
    rv = PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? NS_OK
                                                             : NS_ERROR_FAILURE;
  }

  // restore saved state
  mEntryCount = savedEntryCount;
  SetAcquisitionState(savedAcquisitionState);
  mChainPrev = savedChainPrev;

  return rv;
}

//
// Debug implementation of RecursiveMutex
void RecursiveMutex::Lock() {
  BlockingResourceBase* chainFront = ResourceChainFront();

  // the code below implements mutex reentrancy semantics

  if (this == chainFront) {
    // immediately re-entered the mutex: acceptable
    LockInternal();
    ++mEntryCount;
    return;
  }

  // this is sort of a hack around not recording the thread that
  // owns this monitor
  if (chainFront) {
    for (BlockingResourceBase* br = ResourceChainPrev(chainFront); br;
         br = ResourceChainPrev(br)) {
      if (br == this) {
        NS_WARNING(
            "Re-entering RecursiveMutex after acquiring other resources.");

        // show the caller why this is potentially bad
        CheckAcquire();

        LockInternal();
        ++mEntryCount;
        return;
      }
    }
  }

  CheckAcquire();
  LockInternal();
  NS_ASSERTION(mEntryCount == 0, "RecursiveMutex isn't free!");
  Acquire();  // protected by us
  mOwningThread = PR_GetCurrentThread();
  mEntryCount = 1;
}

void RecursiveMutex::Unlock() {
  if (--mEntryCount == 0) {
    Release();  // protected by us
    mOwningThread = nullptr;
  }
  UnlockInternal();
}

void RecursiveMutex::AssertCurrentThreadIn() {
  MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread());
}

//
// Debug implementation of CondVar
void OffTheBooksCondVar::Wait() {
  // Forward to the timed version of OffTheBooksCondVar::Wait to avoid code
  // duplication.
  CVStatus status = Wait(TimeDuration::Forever());
  MOZ_ASSERT(status == CVStatus::NoTimeout);
}

CVStatus OffTheBooksCondVar::Wait(TimeDuration aDuration) {
  AssertCurrentThreadOwnsMutex();

  // save mutex state and reset to empty
  AcquisitionState savedAcquisitionState = mLock->GetAcquisitionState();
  BlockingResourceBase* savedChainPrev = mLock->mChainPrev;
  PRThread* savedOwningThread = mLock->mOwningThread;
  mLock->ClearAcquisitionState();
  mLock->mChainPrev = 0;
  mLock->mOwningThread = nullptr;

  // give up mutex until we're back from Wait()
  CVStatus status;
  {
#  if defined(MOZILLA_INTERNAL_API)
    AUTO_PROFILER_THREAD_SLEEP;
#  endif
    status = mImpl.wait_for(*mLock, aDuration);
  }

  // restore saved state
  mLock->SetAcquisitionState(savedAcquisitionState);
  mLock->mChainPrev = savedChainPrev;
  mLock->mOwningThread = savedOwningThread;

  return status;
}

#endif  // ifdef DEBUG

}  // namespace mozilla

Minimal test - lines (50, 56)

path: .spaces[0].spaces[0].metrics.mi.mi_visual_studio
old: 60.91571817306542
new: 65.91787083380372

path: .spaces[0].spaces[0].metrics.mi.mi_sei
old: 74.98602317561084
new: 87.02091682937993

path: .spaces[0].spaces[0].metrics.mi.mi_original
old: 104.16587807594188
new: 112.71955912580437

path: .spaces[0].spaces[0].metrics.nargs.sum
old: 2.0
new: 4.0

path: .spaces[0].spaces[0].metrics.nargs.average
old: 2.0
new: 4.0

path: .spaces[0].spaces[0].metrics.loc.sloc
old: 10.0
new: 7.0

path: .spaces[0].spaces[0].metrics.loc.lloc
old: 0.0
new: 1.0

path: .spaces[0].spaces[0].metrics.loc.ploc
old: 10.0
new: 7.0

path: .spaces[0].spaces[0].metrics.halstead.vocabulary
old: 22.0
new: 20.0

path: .spaces[0].spaces[0].metrics.halstead.estimated_program_length
old: 76.10749561002054
new: 66.43856189774725

path: .spaces[0].spaces[0].metrics.halstead.N2
old: 20.0
new: 14.0

path: .spaces[0].spaces[0].metrics.halstead.bugs
old: 0.060623388992107605
new: 0.03658065923319219

path: .spaces[0].spaces[0].metrics.halstead.n2
old: 11.0
new: 10.0

path: .spaces[0].spaces[0].metrics.halstead.length
old: 55.0
new: 38.0

path: .spaces[0].spaces[0].metrics.halstead.N1
old: 35.0
new: 24.0

path: .spaces[0].spaces[0].metrics.halstead.purity_ratio
old: 1.3837726474549188
new: 1.748383207835454

path: .spaces[0].spaces[0].metrics.halstead.time
old: 136.26041056947298
new: 63.868492957779914

path: .spaces[0].spaces[0].metrics.halstead.n1
old: 11.0
new: 10.0

path: .spaces[0].spaces[0].metrics.halstead.volume
old: 245.26873902505136
new: 164.2332676057198

path: .spaces[0].spaces[0].metrics.halstead.difficulty
old: 10.0
new: 7.0

path: .spaces[0].spaces[0].metrics.halstead.level
old: 0.1
new: 0.14285714285714285

path: .spaces[0].spaces[0].metrics.halstead.effort
old: 2452.6873902505135
new: 1149.6328732400384

path: .spaces[0].spaces[0].metrics.cyclomatic.average
old: 2.0
new: 1.0

path: .spaces[0].spaces[0].metrics.cyclomatic.sum
old: 4.0
new: 1.0

Code

void BlockingResourceBase::StackWalkCallback(uint32_t aFrameNumber, void* aPc,
                                             void* aSp, void* aClosure) {
#  ifndef MOZ_CALLSTACK_DISABLED
  AcquisitionState* state = (AcquisitionState*)aClosure;
  state->ref().AppendElement(aPc);
#  endif
}