Global Metrics

path: .metrics.halstead.bugs
old: 1.6610538565683188
new: 2.0811604723074724

path: .metrics.halstead.N2
old: 1010.0
new: 574.0

path: .metrics.halstead.effort
old: 351768.9098607888
new: 493333.07713167695

path: .metrics.halstead.vocabulary
old: 247.0
new: 189.0

path: .metrics.halstead.n1
old: 10.0
new: 28.0

path: .metrics.halstead.N1
old: 1067.0
new: 733.0

path: .metrics.halstead.difficulty
old: 21.30801687763713
new: 49.91304347826087

path: .metrics.halstead.n2
old: 237.0
new: 161.0

path: .metrics.halstead.length
old: 2077.0
new: 1307.0

path: .metrics.halstead.volume
old: 16508.758740001376
new: 9883.850848456945

path: .metrics.halstead.level
old: 0.04693069306930693
new: 0.020034843205574915

path: .metrics.halstead.time
old: 19542.717214488264
new: 27407.393173982055

path: .metrics.halstead.purity_ratio
old: 0.9161537943850558
new: 1.006031794333639

path: .metrics.halstead.estimated_program_length
old: 1902.851430937761
new: 1314.883555194066

path: .metrics.nom.functions
old: 0.0
new: 15.0

path: .metrics.nom.closures
old: 0.0
new: 2.0

path: .metrics.nom.total
old: 0.0
new: 17.0

path: .metrics.cognitive.average
old: null
new: 1.1764705882352942

path: .metrics.cognitive.sum
old: 0.0
new: 20.0

path: .metrics.mi.mi_original
old: 16.654019488795512
new: 14.057110188185035

path: .metrics.mi.mi_visual_studio
old: 9.739192683506149
new: 8.220532273792418

path: .metrics.mi.mi_sei
old: -40.522422291641625
new: -17.49561427264998

path: .metrics.nargs.average
old: null
new: 3.411764705882353

path: .metrics.nargs.sum
old: 0.0
new: 58.0

path: .metrics.loc.ploc
old: 484.0
new: 292.0

path: .metrics.loc.lloc
old: 0.0
new: 77.0

path: .metrics.loc.cloc
old: 12.0
new: 103.0

path: .metrics.loc.blank
old: 95.0
new: 62.0

path: .metrics.loc.sloc
old: 591.0
new: 457.0

path: .metrics.cyclomatic.sum
old: 2.0
new: 43.0

path: .metrics.cyclomatic.average
old: 1.0
new: 2.15

path: .metrics.nexits.average
old: null
new: 0.8823529411764706

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

Spaces Data

Minimal test - lines (33, 388)

path: .spaces[0].metrics.nom.closures
old: 0.0
new: 1.0

path: .spaces[0].metrics.nom.functions
old: 0.0
new: 13.0

path: .spaces[0].metrics.nom.total
old: 0.0
new: 14.0

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

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

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

path: .spaces[0].metrics.loc.sloc
old: 568.0
new: 356.0

path: .spaces[0].metrics.loc.ploc
old: 476.0
new: 227.0

path: .spaces[0].metrics.loc.blank
old: 92.0
new: 48.0

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

path: .spaces[0].metrics.nargs.average
old: null
new: 4.142857142857143

path: .spaces[0].metrics.nargs.sum
old: 0.0
new: 58.0

path: .spaces[0].metrics.mi.mi_original
old: 17.535912016900838
new: 20.539086189106413

path: .spaces[0].metrics.mi.mi_sei
old: -50.300058828360235
new: -8.625764022220217

path: .spaces[0].metrics.mi.mi_visual_studio
old: 10.254919308129145
new: 12.01116151409732

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

path: .spaces[0].metrics.nexits.average
old: null
new: 1.0

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

path: .spaces[0].metrics.cognitive.average
old: null
new: 1.2857142857142858

path: .spaces[0].metrics.halstead.vocabulary
old: 246.0
new: 148.0

path: .spaces[0].metrics.halstead.estimated_program_length
old: 1893.523040598268
new: 963.4328092906352

path: .spaces[0].metrics.halstead.length
old: 2075.0
new: 1119.0

path: .spaces[0].metrics.halstead.effort
old: 351961.0876984651
new: 458361.5446619526

path: .spaces[0].metrics.halstead.n2
old: 236.0
new: 120.0

path: .spaces[0].metrics.halstead.purity_ratio
old: 0.9125412243847076
new: 0.8609765945403353

path: .spaces[0].metrics.halstead.time
old: 19553.39376102584
new: 25464.530258997365

path: .spaces[0].metrics.halstead.bugs
old: 1.6616587777173484
new: 1.9816070019897325

path: .spaces[0].metrics.halstead.level
old: 0.046825396825396826
new: 0.01760046934584922

path: .spaces[0].metrics.halstead.N2
old: 1008.0
new: 487.0

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

path: .spaces[0].metrics.halstead.N1
old: 1067.0
new: 632.0

path: .spaces[0].metrics.halstead.difficulty
old: 21.35593220338983
new: 56.81666666666667

path: .spaces[0].metrics.halstead.volume
old: 16480.71759857892
new: 8067.3783161387955

Code

namespace {

// Keep track of poisoned state. Notice that there is no reason to lock access
// to this variable as it's only changed in InitPoisonIOInterposer and
// ClearPoisonIOInterposer which may only be called on the main-thread when no
// other threads are running.
static bool sIOPoisoned = false;

/************************ Internal NT API Declarations ************************/

/*
 * Function pointer declaration for internal NT routine to create/open files.
 * For documentation on the NtCreateFile routine, see MSDN.
 */
typedef NTSTATUS(NTAPI* NtCreateFileFn)(
    PHANDLE aFileHandle, ACCESS_MASK aDesiredAccess,
    POBJECT_ATTRIBUTES aObjectAttributes, PIO_STATUS_BLOCK aIoStatusBlock,
    PLARGE_INTEGER aAllocationSize, ULONG aFileAttributes, ULONG aShareAccess,
    ULONG aCreateDisposition, ULONG aCreateOptions, PVOID aEaBuffer,
    ULONG aEaLength);

/**
 * Function pointer declaration for internal NT routine to read data from file.
 * For documentation on the NtReadFile routine, see ZwReadFile on MSDN.
 */
typedef NTSTATUS(NTAPI* NtReadFileFn)(HANDLE aFileHandle, HANDLE aEvent,
                                      PIO_APC_ROUTINE aApc, PVOID aApcCtx,
                                      PIO_STATUS_BLOCK aIoStatus, PVOID aBuffer,
                                      ULONG aLength, PLARGE_INTEGER aOffset,
                                      PULONG aKey);

/**
 * Function pointer declaration for internal NT routine to read data from file.
 * No documentation exists, see wine sources for details.
 */
typedef NTSTATUS(NTAPI* NtReadFileScatterFn)(
    HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx,
    PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength,
    PLARGE_INTEGER aOffset, PULONG aKey);

/**
 * Function pointer declaration for internal NT routine to write data to file.
 * For documentation on the NtWriteFile routine, see ZwWriteFile on MSDN.
 */
typedef NTSTATUS(NTAPI* NtWriteFileFn)(HANDLE aFileHandle, HANDLE aEvent,
                                       PIO_APC_ROUTINE aApc, PVOID aApcCtx,
                                       PIO_STATUS_BLOCK aIoStatus,
                                       PVOID aBuffer, ULONG aLength,
                                       PLARGE_INTEGER aOffset, PULONG aKey);

/**
 * Function pointer declaration for internal NT routine to write data to file.
 * No documentation exists, see wine sources for details.
 */
typedef NTSTATUS(NTAPI* NtWriteFileGatherFn)(
    HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx,
    PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength,
    PLARGE_INTEGER aOffset, PULONG aKey);

/**
 * Function pointer declaration for internal NT routine to flush to disk.
 * For documentation on the NtFlushBuffersFile routine, see ZwFlushBuffersFile
 * on MSDN.
 */
typedef NTSTATUS(NTAPI* NtFlushBuffersFileFn)(HANDLE aFileHandle,
                                              PIO_STATUS_BLOCK aIoStatusBlock);

typedef struct _FILE_NETWORK_OPEN_INFORMATION* PFILE_NETWORK_OPEN_INFORMATION;
/**
 * Function pointer delaration for internal NT routine to query file attributes.
 * (equivalent to stat)
 */
typedef NTSTATUS(NTAPI* NtQueryFullAttributesFileFn)(
    POBJECT_ATTRIBUTES aObjectAttributes,
    PFILE_NETWORK_OPEN_INFORMATION aFileInformation);

/*************************** Auxiliary Declarations ***************************/

// Cache of filenames associated with handles.
// `static` to be shared between all calls to `Filename()`.
// This assumes handles are not reused, at least within a windows of 32
// handles.
// Profiling showed that during startup, around half of `Filename()` calls are
// resolved with the first entry (best case), and 32 entries cover >95% of
// cases, reducing the average `Filename()` cost by 5-10x.
using HandleToFilenameCache = mozilla::SmallArrayLRUCache;
static mozilla::UniquePtr sHandleToFilenameCache;

/**
 * RAII class for timing the duration of an I/O call and reporting the result
 * to the mozilla::IOInterposeObserver API.
 */
class WinIOAutoObservation : public mozilla::IOInterposeObserver::Observation {
 public:
  WinIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp,
                       HANDLE aFileHandle, const LARGE_INTEGER* aOffset)
      : mozilla::IOInterposeObserver::Observation(
            aOp, sReference,
            !mozilla::IsDebugFile(reinterpret_cast(aFileHandle))),
        mFileHandle(aFileHandle),
        mFileHandleType(GetFileType(aFileHandle)),
        mHasQueriedFilename(false) {
    if (mShouldReport) {
      mOffset.QuadPart = aOffset ? aOffset->QuadPart : 0;
    }
  }

  WinIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp,
                       nsAString& aFilename)
      : mozilla::IOInterposeObserver::Observation(aOp, sReference),
        mFileHandle(nullptr),
        mFileHandleType(FILE_TYPE_UNKNOWN),
        mHasQueriedFilename(false) {
    if (mShouldReport) {
      nsAutoString dosPath;
      if (mozilla::NtPathToDosPath(aFilename, dosPath)) {
        mFilename = dosPath;
      } else {
        // If we can't get a dosPath, what we have is better than nothing.
        mFilename = aFilename;
      }
      mHasQueriedFilename = true;
      mOffset.QuadPart = 0;
    }
  }

  void SetHandle(HANDLE aFileHandle) {
    mFileHandle = aFileHandle;
    if (aFileHandle) {
      // Note: `GetFileType()` is fast enough that we don't need to cache it.
      mFileHandleType = GetFileType(aFileHandle);

      if (mHasQueriedFilename) {
        // `mHasQueriedFilename` indicates we already have a filename, add it to
        // the cache with the now-known handle.
        sHandleToFilenameCache->Add(aFileHandle, mFilename);
      }
    }
  }

  const char* FileType() const override;

  void Filename(nsAString& aFilename) override;

  ~WinIOAutoObservation() { Report(); }

 private:
  HANDLE mFileHandle;
  DWORD mFileHandleType;
  LARGE_INTEGER mOffset;
  bool mHasQueriedFilename;
  nsString mFilename;
  static const char* sReference;
};

const char* WinIOAutoObservation::sReference = "PoisonIOInterposer";

// Get filename for this observation
void WinIOAutoObservation::Filename(nsAString& aFilename) {
  // If mHasQueriedFilename is true, then filename is already stored in
  // mFilename
  if (mHasQueriedFilename) {
    aFilename = mFilename;
    return;
  }

  if (mFileHandle) {
    mFilename = sHandleToFilenameCache->FetchOrAdd(mFileHandle, [&]() {
      nsString filename;
      if (!mozilla::HandleToFilename(mFileHandle, mOffset, filename)) {
        // HandleToFilename could fail (return false) but still have added
        // something to `filename`, so it should be cleared in this case.
        filename.Truncate();
      }
      return filename;
    });
  }
  mHasQueriedFilename = true;

  aFilename = mFilename;
}

const char* WinIOAutoObservation::FileType() const {
  if (mFileHandle) {
    switch (mFileHandleType) {
      case FILE_TYPE_CHAR:
        return "Char";
      case FILE_TYPE_DISK:
        return "File";
      case FILE_TYPE_PIPE:
        return "Pipe";
      case FILE_TYPE_REMOTE:
        return "Remote";
      case FILE_TYPE_UNKNOWN:
      default:
        break;
    }
  }
  // Fallback to base class default implementation.
  return mozilla::IOInterposeObserver::Observation::FileType();
}

/*************************** IO Interposing Methods ***************************/

// Function pointers to original functions
static mozilla::WindowsDllInterceptor::FuncHookType
    gOriginalNtCreateFile;
static mozilla::WindowsDllInterceptor::FuncHookType
    gOriginalNtReadFile;
static mozilla::WindowsDllInterceptor::FuncHookType
    gOriginalNtReadFileScatter;
static mozilla::WindowsDllInterceptor::FuncHookType
    gOriginalNtWriteFile;
static mozilla::WindowsDllInterceptor::FuncHookType
    gOriginalNtWriteFileGather;
static mozilla::WindowsDllInterceptor::FuncHookType
    gOriginalNtFlushBuffersFile;
static mozilla::WindowsDllInterceptor::FuncHookType
    gOriginalNtQueryFullAttributesFile;

static NTSTATUS NTAPI InterposedNtCreateFile(
    PHANDLE aFileHandle, ACCESS_MASK aDesiredAccess,
    POBJECT_ATTRIBUTES aObjectAttributes, PIO_STATUS_BLOCK aIoStatusBlock,
    PLARGE_INTEGER aAllocationSize, ULONG aFileAttributes, ULONG aShareAccess,
    ULONG aCreateDisposition, ULONG aCreateOptions, PVOID aEaBuffer,
    ULONG aEaLength) {
  // Report IO
  const wchar_t* buf =
      aObjectAttributes ? aObjectAttributes->ObjectName->Buffer : L"";
  uint32_t len = aObjectAttributes
                     ? aObjectAttributes->ObjectName->Length / sizeof(WCHAR)
                     : 0;
  nsDependentSubstring filename(buf, len);
  WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpCreateOrOpen,
                             filename);

  // Something is badly wrong if this function is undefined
  MOZ_ASSERT(gOriginalNtCreateFile);

  // Execute original function
  NTSTATUS status = gOriginalNtCreateFile(
      aFileHandle, aDesiredAccess, aObjectAttributes, aIoStatusBlock,
      aAllocationSize, aFileAttributes, aShareAccess, aCreateDisposition,
      aCreateOptions, aEaBuffer, aEaLength);
  if (NT_SUCCESS(status) && aFileHandle) {
    timer.SetHandle(*aFileHandle);
  }
  return status;
}

static NTSTATUS NTAPI InterposedNtReadFile(HANDLE aFileHandle, HANDLE aEvent,
                                           PIO_APC_ROUTINE aApc, PVOID aApcCtx,
                                           PIO_STATUS_BLOCK aIoStatus,
                                           PVOID aBuffer, ULONG aLength,
                                           PLARGE_INTEGER aOffset,
                                           PULONG aKey) {
  // Report IO
  WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFileHandle,
                             aOffset);

  // Something is badly wrong if this function is undefined
  MOZ_ASSERT(gOriginalNtReadFile);

  // Execute original function
  return gOriginalNtReadFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus,
                             aBuffer, aLength, aOffset, aKey);
}

static NTSTATUS NTAPI InterposedNtReadFileScatter(
    HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx,
    PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength,
    PLARGE_INTEGER aOffset, PULONG aKey) {
  // Report IO
  WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFileHandle,
                             aOffset);

  // Something is badly wrong if this function is undefined
  MOZ_ASSERT(gOriginalNtReadFileScatter);

  // Execute original function
  return gOriginalNtReadFileScatter(aFileHandle, aEvent, aApc, aApcCtx,
                                    aIoStatus, aSegments, aLength, aOffset,
                                    aKey);
}

// Interposed NtWriteFile function
static NTSTATUS NTAPI InterposedNtWriteFile(HANDLE aFileHandle, HANDLE aEvent,
                                            PIO_APC_ROUTINE aApc, PVOID aApcCtx,
                                            PIO_STATUS_BLOCK aIoStatus,
                                            PVOID aBuffer, ULONG aLength,
                                            PLARGE_INTEGER aOffset,
                                            PULONG aKey) {
  // Report IO
  WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFileHandle,
                             aOffset);

  // Something is badly wrong if this function is undefined
  MOZ_ASSERT(gOriginalNtWriteFile);

  // Execute original function
  return gOriginalNtWriteFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus,
                              aBuffer, aLength, aOffset, aKey);
}

// Interposed NtWriteFileGather function
static NTSTATUS NTAPI InterposedNtWriteFileGather(
    HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx,
    PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength,
    PLARGE_INTEGER aOffset, PULONG aKey) {
  // Report IO
  WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFileHandle,
                             aOffset);

  // Something is badly wrong if this function is undefined
  MOZ_ASSERT(gOriginalNtWriteFileGather);

  // Execute original function
  return gOriginalNtWriteFileGather(aFileHandle, aEvent, aApc, aApcCtx,
                                    aIoStatus, aSegments, aLength, aOffset,
                                    aKey);
}

static NTSTATUS NTAPI InterposedNtFlushBuffersFile(
    HANDLE aFileHandle, PIO_STATUS_BLOCK aIoStatusBlock) {
  // Report IO
  WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpFSync, aFileHandle,
                             nullptr);

  // Something is badly wrong if this function is undefined
  MOZ_ASSERT(gOriginalNtFlushBuffersFile);

  // Execute original function
  return gOriginalNtFlushBuffersFile(aFileHandle, aIoStatusBlock);
}

static NTSTATUS NTAPI InterposedNtQueryFullAttributesFile(
    POBJECT_ATTRIBUTES aObjectAttributes,
    PFILE_NETWORK_OPEN_INFORMATION aFileInformation) {
  // Report IO
  const wchar_t* buf =
      aObjectAttributes ? aObjectAttributes->ObjectName->Buffer : L"";
  uint32_t len = aObjectAttributes
                     ? aObjectAttributes->ObjectName->Length / sizeof(WCHAR)
                     : 0;
  nsDependentSubstring filename(buf, len);
  WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpStat, filename);

  // Something is badly wrong if this function is undefined
  MOZ_ASSERT(gOriginalNtQueryFullAttributesFile);

  // Execute original function
  return gOriginalNtQueryFullAttributesFile(aObjectAttributes,
                                            aFileInformation);
}

}  // namespace