public void selfCheck() throws IOException {
    //VFS errors early on startup cause terrifying UX error messages. It is much better to catch VFS
    // corruption early on and rebuild VFS from 0.
    //But we also don't want to always check each VFS file on startup -- it delays regular startup
    // (i.e. without corruptions) quite a lot.
    //So the tradeoff: we use few heuristics to quickly check for the most likely signs of corruption,
    // and if we find any such sign -- switch to more vigilant checking:

    if (!recordsStorage.wasClosedProperly()) {
      addProblem(NOT_CLOSED_PROPERLY, "VFS wasn't safely shut down: records.wasClosedProperly is false");
    }
    int errorsAccumulated = recordsStorage.getErrorsAccumulated();
    if (errorsAccumulated > 0) {
      addProblem(HAS_ERRORS_IN_PREVIOUS_SESSION, "VFS accumulated " + errorsAccumulated + " errors in last session");
    }

    if (attributesEnumerator.isEmpty() && !attributesStorage.isEmpty()) {
      addProblem(ATTRIBUTES_STORAGE_CORRUPTED, "Attributes enumerator is empty, while attributesStorage is !empty");
    }

    int maxAllocatedID = recordsStorage.maxAllocatedID();

    //Faster to look only for the first error of each kind:
    boolean nameStorageHasErrors = false;
    boolean contentHashesStorageHasErrors = false;
    boolean attributesStorageHasErrors = false;

    //Try to resolve few nameId/contentId/attributeRefId against apt enumerators/storages -- which is quite
    // likely fails if storages/enumerators' files are corrupted anyhow, hence serves as a self-check heuristic.
    //Check _every_ fileId is quite slow, but it is definitely worth checking _some_.
    // So a tradeoff:
    // If there were _no_ signs of corruption -- check only fileId(=)2, 4, 8...) -> always < 32 checks, given fileId
    // is int32.
    // If _any_ signs of corruption arises during quick-scan (or even before it) -> fallback to full scan, check
    // _every_ fileId.
    if (problemsDuringLoad.isEmpty()) {
      for (int fileId = FSRecords.MIN_REGULAR_FILE_ID; fileId <= maxAllocatedID; fileId *= 2) {
        if (!nameStorageHasErrors) {
          if (!nameResolvedSuccessfully(fileId)) {
            nameStorageHasErrors = true;
          }
        }

        if (!contentHashesStorageHasErrors) {
          if (!contentResolvedSuccessfully(fileId)) {
            contentHashesStorageHasErrors = true;
          }
        }
        if (!attributesStorageHasErrors) {
          if (!attributeRecordIsValid(fileId)) {
            attributesStorageHasErrors = true;
          }
        }
      }
    }

    //...If there are some errors, or other signs of possible corruption
    // -> fallback to a full scan:
    if (!problemsDuringLoad.isEmpty()) {
      for (int fileId = FSRecords.MIN_REGULAR_FILE_ID; fileId <= maxAllocatedID; fileId++) {
        if (!nameStorageHasErrors) {
          if (!nameResolvedSuccessfully(fileId)) {
            nameStorageHasErrors = true;
          }
        }

        if (!contentHashesStorageHasErrors) {
          if (!contentResolvedSuccessfully(fileId)) {
            contentHashesStorageHasErrors = true;
          }
        }
        if (!attributesStorageHasErrors) {
          if (!attributeRecordIsValid(fileId)) {
            attributesStorageHasErrors = true;
          }
        }
      }
    }
  }
public void selfCheck()