//===--- SerializedDiagnosticReader.cpp - Reads diagnostics ---------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "clang/Frontend/SerializedDiagnosticReader.h"
#include "clang/Frontend/SerializedDiagnostics.h"
#include "clang/Basic/FileManager.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/MemoryBuffer.h"

using namespace clang;
using namespace clang::serialized_diags;

std::error_code SerializedDiagnosticReader::readDiagnostics(StringRef File) {
  // Open the diagnostics file.
  FileSystemOptions FO;
  FileManager FileMgr(FO);

  auto Buffer = FileMgr.getBufferForFile(File);
  if (!Buffer)
    return SDError::CouldNotLoad;

  llvm::BitstreamReader StreamFile;
  StreamFile.init((const unsigned char *)(*Buffer)->getBufferStart(),
                  (const unsigned char *)(*Buffer)->getBufferEnd());

  llvm::BitstreamCursor Stream(StreamFile);

  // Sniff for the signature.
  if (Stream.Read(8) != 'D' ||
      Stream.Read(8) != 'I' ||
      Stream.Read(8) != 'A' ||
      Stream.Read(8) != 'G')
    return SDError::InvalidSignature;

  // Read the top level blocks.
  while (!Stream.AtEndOfStream()) {
    if (Stream.ReadCode() != llvm::bitc::ENTER_SUBBLOCK)
      return SDError::InvalidDiagnostics;

    std::error_code EC;
    switch (Stream.ReadSubBlockID()) {
    case llvm::bitc::BLOCKINFO_BLOCK_ID:
      if (Stream.ReadBlockInfoBlock())
        return SDError::MalformedBlockInfoBlock;
      continue;
    case BLOCK_META:
      if ((EC = readMetaBlock(Stream)))
        return EC;
      continue;
    case BLOCK_DIAG:
      if ((EC = readDiagnosticBlock(Stream)))
        return EC;
      continue;
    default:
      if (!Stream.SkipBlock())
        return SDError::MalformedTopLevelBlock;
      continue;
    }
  }
  return std::error_code();
}

enum class SerializedDiagnosticReader::Cursor {
  Record = 1,
  BlockEnd,
  BlockBegin
};

llvm::ErrorOr<SerializedDiagnosticReader::Cursor>
SerializedDiagnosticReader::skipUntilRecordOrBlock(
    llvm::BitstreamCursor &Stream, unsigned &BlockOrRecordID) {
  BlockOrRecordID = 0;

  while (!Stream.AtEndOfStream()) {
    unsigned Code = Stream.ReadCode();

    switch ((llvm::bitc::FixedAbbrevIDs)Code) {
    case llvm::bitc::ENTER_SUBBLOCK:
      BlockOrRecordID = Stream.ReadSubBlockID();
      return Cursor::BlockBegin;

    case llvm::bitc::END_BLOCK:
      if (Stream.ReadBlockEnd())
        return SDError::InvalidDiagnostics;
      return Cursor::BlockEnd;

    case llvm::bitc::DEFINE_ABBREV:
      Stream.ReadAbbrevRecord();
      continue;

    case llvm::bitc::UNABBREV_RECORD:
      return SDError::UnsupportedConstruct;

    default:
      // We found a record.
      BlockOrRecordID = Code;
      return Cursor::Record;
    }
  }

  return SDError::InvalidDiagnostics;
}

std::error_code
SerializedDiagnosticReader::readMetaBlock(llvm::BitstreamCursor &Stream) {
  if (Stream.EnterSubBlock(clang::serialized_diags::BLOCK_META))
    return SDError::MalformedMetadataBlock;

  bool VersionChecked = false;

  while (true) {
    unsigned BlockOrCode = 0;
    llvm::ErrorOr<Cursor> Res = skipUntilRecordOrBlock(Stream, BlockOrCode);
    if (!Res)
      Res.getError();

    switch (Res.get()) {
    case Cursor::Record:
      break;
    case Cursor::BlockBegin:
      if (Stream.SkipBlock())
        return SDError::MalformedMetadataBlock;
    case Cursor::BlockEnd:
      if (!VersionChecked)
        return SDError::MissingVersion;
      return std::error_code();
    }

    SmallVector<uint64_t, 1> Record;
    unsigned RecordID = Stream.readRecord(BlockOrCode, Record);

    if (RecordID == RECORD_VERSION) {
      if (Record.size() < 1)
        return SDError::MissingVersion;
      if (Record[0] > VersionNumber)
        return SDError::VersionMismatch;
      VersionChecked = true;
    }
  }
}

std::error_code
SerializedDiagnosticReader::readDiagnosticBlock(llvm::BitstreamCursor &Stream) {
  if (Stream.EnterSubBlock(clang::serialized_diags::BLOCK_DIAG))
    return SDError::MalformedDiagnosticBlock;

  std::error_code EC;
  if ((EC = visitStartOfDiagnostic()))
    return EC;

  SmallVector<uint64_t, 16> Record;
  while (true) {
    unsigned BlockOrCode = 0;
    llvm::ErrorOr<Cursor> Res = skipUntilRecordOrBlock(Stream, BlockOrCode);
    if (!Res)
      Res.getError();

    switch (Res.get()) {
    case Cursor::BlockBegin:
      // The only blocks we care about are subdiagnostics.
      if (BlockOrCode == serialized_diags::BLOCK_DIAG) {
        if ((EC = readDiagnosticBlock(Stream)))
          return EC;
      } else if (!Stream.SkipBlock())
        return SDError::MalformedSubBlock;
      continue;
    case Cursor::BlockEnd:
      if ((EC = visitEndOfDiagnostic()))
        return EC;
      return std::error_code();
    case Cursor::Record:
      break;
    }

    // Read the record.
    Record.clear();
    StringRef Blob;
    unsigned RecID = Stream.readRecord(BlockOrCode, Record, &Blob);

    if (RecID < serialized_diags::RECORD_FIRST ||
        RecID > serialized_diags::RECORD_LAST)
      continue;

    switch ((RecordIDs)RecID) {
    case RECORD_CATEGORY:
      // A category has ID and name size.
      if (Record.size() != 2)
        return SDError::MalformedDiagnosticRecord;
      if ((EC = visitCategoryRecord(Record[0], Blob)))
        return EC;
      continue;
    case RECORD_DIAG:
      // A diagnostic has severity, location (4), category, flag, and message
      // size.
      if (Record.size() != 8)
        return SDError::MalformedDiagnosticRecord;
      if ((EC = visitDiagnosticRecord(
               Record[0], Location(Record[1], Record[2], Record[3], Record[4]),
               Record[5], Record[6], Blob)))
        return EC;
      continue;
    case RECORD_DIAG_FLAG:
      // A diagnostic flag has ID and name size.
      if (Record.size() != 2)
        return SDError::MalformedDiagnosticRecord;
      if ((EC = visitDiagFlagRecord(Record[0], Blob)))
        return EC;
      continue;
    case RECORD_FILENAME:
      // A filename has ID, size, timestamp, and name size. The size and
      // timestamp are legacy fields that are always zero these days.
      if (Record.size() != 4)
        return SDError::MalformedDiagnosticRecord;
      if ((EC = visitFilenameRecord(Record[0], Record[1], Record[2], Blob)))
        return EC;
      continue;
    case RECORD_FIXIT:
      // A fixit has two locations (4 each) and message size.
      if (Record.size() != 9)
        return SDError::MalformedDiagnosticRecord;
      if ((EC = visitFixitRecord(
               Location(Record[0], Record[1], Record[2], Record[3]),
               Location(Record[4], Record[5], Record[6], Record[7]), Blob)))
        return EC;
      continue;
    case RECORD_SOURCE_RANGE:
      // A source range is two locations (4 each).
      if (Record.size() != 8)
        return SDError::MalformedDiagnosticRecord;
      if ((EC = visitSourceRangeRecord(
               Location(Record[0], Record[1], Record[2], Record[3]),
               Location(Record[4], Record[5], Record[6], Record[7]))))
        return EC;
      continue;
    case RECORD_VERSION:
      // A version is just a number.
      if (Record.size() != 1)
        return SDError::MalformedDiagnosticRecord;
      if ((EC = visitVersionRecord(Record[0])))
        return EC;
      continue;
    }
  }
}

namespace {
class SDErrorCategoryType final : public std::error_category {
  const char *name() const LLVM_NOEXCEPT override {
    return "clang.serialized_diags";
  }
  std::string message(int IE) const override {
    SDError E = static_cast<SDError>(IE);
    switch (E) {
    case SDError::CouldNotLoad:
      return "Failed to open diagnostics file";
    case SDError::InvalidSignature:
      return "Invalid diagnostics signature";
    case SDError::InvalidDiagnostics:
      return "Parse error reading diagnostics";
    case SDError::MalformedTopLevelBlock:
      return "Malformed block at top-level of diagnostics";
    case SDError::MalformedSubBlock:
      return "Malformed sub-block in a diagnostic";
    case SDError::MalformedBlockInfoBlock:
      return "Malformed BlockInfo block";
    case SDError::MalformedMetadataBlock:
      return "Malformed Metadata block";
    case SDError::MalformedDiagnosticBlock:
      return "Malformed Diagnostic block";
    case SDError::MalformedDiagnosticRecord:
      return "Malformed Diagnostic record";
    case SDError::MissingVersion:
      return "No version provided in diagnostics";
    case SDError::VersionMismatch:
      return "Unsupported diagnostics version";
    case SDError::UnsupportedConstruct:
      return "Bitcode constructs that are not supported in diagnostics appear";
    case SDError::HandlerFailed:
      return "Generic error occurred while handling a record";
    }
    llvm_unreachable("Unknown error type!");
  }
};
}

static llvm::ManagedStatic<SDErrorCategoryType> ErrorCategory;
const std::error_category &clang::serialized_diags::SDErrorCategory() {
  return *ErrorCategory;
}
