namespace codeview {
+class TypeServerHandler;
+
/// Dumper for CodeView type streams found in COFF object files and PDB files.
class CVTypeDumper {
public:
- explicit CVTypeDumper(TypeDatabase &TypeDB) : TypeDB(TypeDB) {}
+ explicit CVTypeDumper(TypeDatabase &TypeDB,
+ TypeServerHandler *Handler = nullptr)
+ : TypeDB(TypeDB), Handler(Handler) {}
/// Dumps one type record. Returns false if there was a type parsing error,
/// and true otherwise. This should be called in order, since the dumper
private:
TypeDatabase &TypeDB;
+ TypeServerHandler *Handler;
};
} // end namespace codeview
#ifndef LLVM_DEBUGINFO_CODEVIEW_CVTYPEVISITOR_H
#define LLVM_DEBUGINFO_CODEVIEW_CVTYPEVISITOR_H
-#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/TinyPtrVector.h"
#include "llvm/DebugInfo/CodeView/CVRecord.h"
#include "llvm/DebugInfo/CodeView/TypeRecord.h"
+#include "llvm/DebugInfo/CodeView/TypeServerHandler.h"
#include "llvm/DebugInfo/CodeView/TypeVisitorCallbacks.h"
#include "llvm/Support/Error.h"
public:
explicit CVTypeVisitor(TypeVisitorCallbacks &Callbacks);
+ void addTypeServerHandler(TypeServerHandler &Handler);
+
Error visitTypeRecord(CVType &Record);
Error visitMemberRecord(CVMemberRecord &Record);
/// Visits the type records in Data. Sets the error flag on parse failures.
Error visitTypeStream(const CVTypeArray &Types);
+ Error visitTypeStream(CVTypeRange Types);
Error visitFieldListMemberStream(ArrayRef<uint8_t> FieldList);
Error visitFieldListMemberStream(msf::StreamReader Reader);
private:
/// The interface to the class that gets notified of each visitation.
TypeVisitorCallbacks &Callbacks;
+
+ TinyPtrVector<TypeServerHandler *> Handlers;
};
} // end namespace codeview
insufficient_buffer,
operation_unsupported,
corrupt_record,
+ no_records,
unknown_member_record,
};
ArrayRef<uint8_t> Data;
};
typedef msf::VarStreamArray<CVType> CVTypeArray;
+typedef iterator_range<CVTypeArray::Iterator> CVTypeRange;
/// Equvalent to CV_fldattr_t in cvinfo.h.
struct MemberAttributes {
--- /dev/null
+//===- TypeServerHandler.h --------------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_DEBUGINFO_CODEVIEW_TYPESERVERHANDLER_H
+#define LLVM_DEBUGINFO_CODEVIEW_TYPESERVERHANDLER_H
+
+#include "llvm/DebugInfo/CodeView/TypeRecord.h"
+#include "llvm/Support/Error.h"
+
+namespace llvm {
+namespace codeview {
+class TypeVisitorCallbacks;
+
+class TypeServerHandler {
+public:
+ virtual ~TypeServerHandler() {}
+
+ /// Handle a TypeServer record. If the implementation returns true
+ /// the record will not be processed by the top-level visitor. If
+ /// it returns false, it will be processed. If it returns an Error,
+ /// then the top-level visitor will fail.
+ virtual Expected<bool> handle(TypeServer2Record &TS,
+ TypeVisitorCallbacks &Callbacks) {
+ return false;
+ }
+};
+}
+}
+
+#endif
namespace llvm {
namespace codeview {
+class TypeServerHandler;
+
/// Merges one type stream into another. Returns true on success.
-Error mergeTypeStreams(TypeTableBuilder &DestStream, const CVTypeArray &Types);
+Error mergeTypeStreams(TypeTableBuilder &DestStream, TypeServerHandler *Handler,
+ const CVTypeArray &Types);
} // end namespace codeview
} // end namespace llvm
friend PDBFileBuilder;
public:
- PDBFile(std::unique_ptr<msf::ReadableStream> PdbFileBuffer,
+ PDBFile(StringRef Path, std::unique_ptr<msf::ReadableStream> PdbFileBuffer,
BumpPtrAllocator &Allocator);
~PDBFile() override;
+ StringRef getFileDirectory() const;
+ StringRef getFilePath() const;
+
uint32_t getFreeBlockMapBlock() const;
uint32_t getUnknown1() const;
const msf::ReadableStream &MsfData,
uint32_t StreamIndex) const;
+ std::string FilePath;
BumpPtrAllocator &Allocator;
std::unique_ptr<msf::ReadableStream> Buffer;
--- /dev/null
+//===- PDBTypeServerHandler.h -----------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_DEBUGINFO_PDB_PDBTYPESERVERHANDLER_H
+#define LLVM_DEBUGINFO_PDB_PDBTYPESERVERHANDLER_H
+
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/DebugInfo/CodeView/CVTypeVisitor.h"
+#include "llvm/DebugInfo/CodeView/TypeRecord.h"
+#include "llvm/DebugInfo/CodeView/TypeServerHandler.h"
+#include "llvm/DebugInfo/PDB/Native/NativeSession.h"
+#include "llvm/DebugInfo/PDB/PDBTypes.h"
+
+#include <memory>
+#include <string>
+
+namespace llvm {
+namespace pdb {
+class NativeSession;
+
+class PDBTypeServerHandler : public codeview::TypeServerHandler {
+public:
+ PDBTypeServerHandler(bool RevisitAlways = false);
+
+ void addSearchPath(StringRef Path);
+ Expected<bool> handle(codeview::TypeServer2Record &TS,
+ codeview::TypeVisitorCallbacks &Callbacks) override;
+
+private:
+ Expected<bool> handleInternal(PDBFile &File,
+ codeview::TypeVisitorCallbacks &Callbacks);
+
+ bool RevisitAlways;
+ std::unique_ptr<NativeSession> Session;
+ SmallVector<SmallString<64>, 4> SearchPaths;
+};
+}
+}
+
+#endif
msf::FixedStreamArray<TypeIndexOffset> getTypeIndexOffsets() const;
HashTable &getHashAdjusters();
- iterator_range<codeview::CVTypeArray::Iterator> types(bool *HadError) const;
+ codeview::CVTypeRange types(bool *HadError) const;
Error commit();
Pipeline.addCallbackToPipeline(Dumper);
CVTypeVisitor Visitor(Pipeline);
+ if (Handler)
+ Visitor.addTypeServerHandler(*Handler);
CVType RecordCopy = Record;
if (auto EC = Visitor.visitTypeRecord(RecordCopy))
Pipeline.addCallbackToPipeline(Dumper);
CVTypeVisitor Visitor(Pipeline);
+ if (Handler)
+ Visitor.addTypeServerHandler(*Handler);
if (auto EC = Visitor.visitTypeStream(Types))
return EC;
#include "llvm/DebugInfo/CodeView/CVTypeVisitor.h"
#include "llvm/DebugInfo/CodeView/CodeViewError.h"
+#include "llvm/DebugInfo/CodeView/TypeDatabase.h"
+#include "llvm/DebugInfo/CodeView/TypeDatabaseVisitor.h"
#include "llvm/DebugInfo/CodeView/TypeDeserializer.h"
+#include "llvm/DebugInfo/CodeView/TypeRecordMapping.h"
+#include "llvm/DebugInfo/CodeView/TypeServerHandler.h"
#include "llvm/DebugInfo/CodeView/TypeVisitorCallbackPipeline.h"
#include "llvm/DebugInfo/MSF/ByteStream.h"
+#include "llvm/DebugInfo/MSF/StreamReader.h"
using namespace llvm;
using namespace llvm::codeview;
: Callbacks(Callbacks) {}
template <typename T>
-static Error visitKnownRecord(CVType &Record, TypeVisitorCallbacks &Callbacks) {
+static Error visitKnownRecord(CVTypeVisitor &Visitor, CVType &Record,
+ TypeVisitorCallbacks &Callbacks) {
TypeRecordKind RK = static_cast<TypeRecordKind>(Record.Type);
T KnownRecord(RK);
if (auto EC = Callbacks.visitKnownRecord(Record, KnownRecord))
return Error::success();
}
+static Expected<TypeServer2Record> deserializeTypeServerRecord(CVType &Record) {
+ class StealTypeServerVisitor : public TypeVisitorCallbacks {
+ public:
+ explicit StealTypeServerVisitor(TypeServer2Record &TR) : TR(TR) {}
+
+ Error visitKnownRecord(CVType &CVR, TypeServer2Record &Record) override {
+ TR = Record;
+ return Error::success();
+ }
+
+ private:
+ TypeServer2Record &TR;
+ };
+
+ TypeServer2Record R(TypeRecordKind::TypeServer2);
+ TypeDeserializer Deserializer;
+ StealTypeServerVisitor Thief(R);
+ TypeVisitorCallbackPipeline Pipeline;
+ Pipeline.addCallbackToPipeline(Deserializer);
+ Pipeline.addCallbackToPipeline(Thief);
+ CVTypeVisitor Visitor(Pipeline);
+ if (auto EC = Visitor.visitTypeRecord(Record))
+ return std::move(EC);
+
+ return R;
+}
+
+void CVTypeVisitor::addTypeServerHandler(TypeServerHandler &Handler) {
+ Handlers.push_back(&Handler);
+}
+
Error CVTypeVisitor::visitTypeRecord(CVType &Record) {
+ if (Record.Type == TypeLeafKind::LF_TYPESERVER2 && !Handlers.empty()) {
+ auto TS = deserializeTypeServerRecord(Record);
+ if (!TS)
+ return TS.takeError();
+
+ for (auto Handler : Handlers) {
+ auto ExpectedResult = Handler->handle(*TS, Callbacks);
+ // If there was an error, return the error.
+ if (!ExpectedResult)
+ return ExpectedResult.takeError();
+
+ // If the handler processed the record, return success.
+ if (*ExpectedResult)
+ return Error::success();
+
+ // Otherwise keep searching for a handler, eventually falling out and
+ // using the default record handler.
+ }
+ }
+
if (auto EC = Callbacks.visitTypeBegin(Record))
return EC;
break;
#define TYPE_RECORD(EnumName, EnumVal, Name) \
case EnumName: { \
- if (auto EC = visitKnownRecord<Name##Record>(Record, Callbacks)) \
+ if (auto EC = visitKnownRecord<Name##Record>(*this, Record, Callbacks)) \
return EC; \
break; \
}
return Error::success();
}
+Error CVTypeVisitor::visitTypeStream(CVTypeRange Types) {
+ for (auto I : Types) {
+ if (auto EC = visitTypeRecord(I))
+ return EC;
+ }
+ return Error::success();
+}
+
Error CVTypeVisitor::visitFieldListMemberStream(msf::StreamReader Reader) {
FieldListDeserializer Deserializer(Reader);
TypeVisitorCallbackPipeline Pipeline;
"bytes.";
case cv_error_code::corrupt_record:
return "The CodeView record is corrupted.";
+ case cv_error_code::no_records:
+ return "There are no records";
case cv_error_code::operation_unsupported:
return "The requested operation is not supported.";
case cv_error_code::unknown_member_record:
/// existing destination type index.
class TypeStreamMerger : public TypeVisitorCallbacks {
public:
- TypeStreamMerger(TypeTableBuilder &DestStream)
- : DestStream(DestStream), FieldListBuilder(DestStream) {}
+ TypeStreamMerger(TypeTableBuilder &DestStream, TypeServerHandler *Handler)
+ : DestStream(DestStream), FieldListBuilder(DestStream), Handler(Handler) {
+ }
/// TypeVisitorCallbacks overrides.
#define TYPE_RECORD(EnumName, EnumVal, Name) \
TypeTableBuilder &DestStream;
FieldListRecordBuilder FieldListBuilder;
+ TypeServerHandler *Handler;
bool IsInFieldList{false};
size_t BeginIndexMapSize = 0;
Pipeline.addCallbackToPipeline(*this);
CVTypeVisitor Visitor(Pipeline);
+ if (Handler)
+ Visitor.addTypeServerHandler(*Handler);
if (auto EC = Visitor.visitTypeStream(Types))
return EC;
}
Error llvm::codeview::mergeTypeStreams(TypeTableBuilder &DestStream,
+ TypeServerHandler *Handler,
const CVTypeArray &Types) {
- return TypeStreamMerger(DestStream).mergeStream(Types);
+ return TypeStreamMerger(DestStream, Handler).mergeStream(Types);
}
Native/NativeSession.cpp
Native/PDBFile.cpp
Native/PDBFileBuilder.cpp
+ Native/PDBTypeServerHandler.cpp
Native/PublicsStream.cpp
Native/RawError.cpp
Native/StringTable.cpp
auto Stream = llvm::make_unique<MemoryBufferByteStream>(std::move(Buffer));
auto Allocator = llvm::make_unique<BumpPtrAllocator>();
- auto File = llvm::make_unique<PDBFile>(std::move(Stream), *Allocator);
+ auto File = llvm::make_unique<PDBFile>(Path, std::move(Stream), *Allocator);
if (auto EC = File->parseFileHeaders())
return EC;
if (auto EC = File->parseStreamData())
#include "llvm/DebugInfo/PDB/Native/TpiStream.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/Error.h"
+#include "llvm/Support/Path.h"
#include <algorithm>
#include <cassert>
#include <cstdint>
typedef FixedStreamArray<support::ulittle32_t> ulittle_array;
} // end anonymous namespace
-PDBFile::PDBFile(std::unique_ptr<ReadableStream> PdbFileBuffer,
+PDBFile::PDBFile(StringRef Path, std::unique_ptr<ReadableStream> PdbFileBuffer,
BumpPtrAllocator &Allocator)
- : Allocator(Allocator), Buffer(std::move(PdbFileBuffer)) {}
+ : FilePath(Path), Allocator(Allocator), Buffer(std::move(PdbFileBuffer)) {}
PDBFile::~PDBFile() = default;
+StringRef PDBFile::getFilePath() const { return FilePath; }
+
+StringRef PDBFile::getFileDirectory() const {
+ return sys::path::parent_path(FilePath);
+}
+
uint32_t PDBFile::getBlockSize() const { return ContainerLayout.SB->BlockSize; }
uint32_t PDBFile::getFreeBlockMapBlock() const {
--- /dev/null
+//===- PDBTypeServerHandler.cpp ---------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+// Handles CodeView LF_TYPESERVER2 records by attempting to locate a matching
+// PDB file, then loading the PDB file and visiting all types from the
+// referenced PDB using the original supplied visitor.
+//
+// The net effect of this is that when visiting a PDB containing a TypeServer
+// record, the TypeServer record is "replaced" with all of the records in
+// the referenced PDB file. If a single instance of PDBTypeServerHandler
+// encounters the same TypeServer multiple times (for example reusing one
+// PDBTypeServerHandler across multiple visitations of distinct object files or
+// PDB files), PDBTypeServerHandler will optionally revisit all the records
+// again, or simply consume the record and do nothing.
+//===----------------------------------------------------------------------===//
+
+#include "llvm/DebugInfo/PDB/Native/PDBTypeServerHandler.h"
+
+#include "llvm/DebugInfo/CodeView/CodeViewError.h"
+#include "llvm/DebugInfo/PDB/GenericError.h"
+#include "llvm/DebugInfo/PDB/Native/InfoStream.h"
+#include "llvm/DebugInfo/PDB/Native/NativeSession.h"
+#include "llvm/DebugInfo/PDB/Native/PDBFile.h"
+#include "llvm/DebugInfo/PDB/Native/TpiStream.h"
+#include "llvm/DebugInfo/PDB/PDB.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+
+using namespace llvm;
+using namespace llvm::codeview;
+using namespace llvm::pdb;
+
+static void ignoreErrors(Error EC) {
+ llvm::handleAllErrors(std::move(EC), [&](ErrorInfoBase &EIB) {});
+}
+
+PDBTypeServerHandler::PDBTypeServerHandler(bool RevisitAlways)
+ : RevisitAlways(RevisitAlways) {}
+
+void PDBTypeServerHandler::addSearchPath(StringRef Path) {
+ if (Path.empty() || !sys::fs::is_directory(Path))
+ return;
+
+ SearchPaths.push_back(Path);
+}
+
+Expected<bool>
+PDBTypeServerHandler::handleInternal(PDBFile &File,
+ TypeVisitorCallbacks &Callbacks) {
+ auto ExpectedTpi = File.getPDBTpiStream();
+ if (!ExpectedTpi)
+ return ExpectedTpi.takeError();
+ CVTypeVisitor Visitor(Callbacks);
+
+ if (auto EC = Visitor.visitTypeStream(ExpectedTpi->types(nullptr)))
+ return std::move(EC);
+
+ return true;
+}
+
+Expected<bool> PDBTypeServerHandler::handle(TypeServer2Record &TS,
+ TypeVisitorCallbacks &Callbacks) {
+ if (Session) {
+ // If we've already handled this TypeServer and we only want to handle each
+ // TypeServer once, consume the record without doing anything.
+ if (!RevisitAlways)
+ return true;
+
+ return handleInternal(Session->getPDBFile(), Callbacks);
+ }
+
+ StringRef File = sys::path::filename(TS.Name);
+ if (File.empty())
+ return make_error<CodeViewError>(
+ cv_error_code::corrupt_record,
+ "TypeServer2Record does not contain filename!");
+
+ for (auto Path : SearchPaths) {
+ sys::path::append(Path, File);
+ if (!sys::fs::exists(Path))
+ continue;
+
+ std::unique_ptr<IPDBSession> ThisSession;
+ if (auto EC = loadDataForPDB(PDB_ReaderType::Native, Path, ThisSession)) {
+ // It is not an error if this PDB fails to load, it just means that it
+ // doesn't match and we should continue searching.
+ ignoreErrors(std::move(EC));
+ continue;
+ }
+
+ std::unique_ptr<NativeSession> NS(
+ static_cast<NativeSession *>(ThisSession.release()));
+ PDBFile &File = NS->getPDBFile();
+ auto ExpectedInfo = File.getPDBInfoStream();
+ // All PDB Files should have an Info stream.
+ if (!ExpectedInfo)
+ return ExpectedInfo.takeError();
+
+ // Just because a file with a matching name was found and it was an actual
+ // PDB file doesn't mean it matches. For it to match the InfoStream's GUID
+ // must match the GUID specified in the TypeServer2 record.
+ ArrayRef<uint8_t> GuidBytes(ExpectedInfo->getGuid().Guid);
+ StringRef GuidStr(reinterpret_cast<const char *>(GuidBytes.begin()),
+ GuidBytes.size());
+ if (GuidStr != TS.Guid)
+ continue;
+
+ Session = std::move(NS);
+ return handleInternal(File, Callbacks);
+ }
+
+ // We couldn't find a matching PDB, so let it be handled by someone else.
+ return false;
+}
#include "llvm/DebugInfo/MSF/MappedBlockStream.h"
#include "llvm/DebugInfo/MSF/StreamReader.h"
#include "llvm/DebugInfo/PDB/Native/PDBFile.h"
+#include "llvm/DebugInfo/PDB/Native/PDBTypeServerHandler.h"
#include "llvm/DebugInfo/PDB/Native/RawConstants.h"
#include "llvm/DebugInfo/PDB/Native/RawError.h"
#include "llvm/DebugInfo/PDB/Native/RawTypes.h"
HashTable &TpiStream::getHashAdjusters() { return HashAdjusters; }
-iterator_range<CVTypeArray::Iterator> TpiStream::types(bool *HadError) const {
+CVTypeRange TpiStream::types(bool *HadError) const {
return make_range(TypeRecords.begin(HadError), TypeRecords.end());
}
Support
DebugInfoCodeView
DebugInfoMSF
+ DebugInfoPDB
)
add_llvm_tool(llvm-readobj
#include "llvm/Support/Compiler.h"
#include "llvm/Support/DataExtractor.h"
#include "llvm/Support/Format.h"
+#include "llvm/Support/Path.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/Win64EH.h"
error(object_error::parse_failed);
}
- if (auto EC = mergeTypeStreams(CVTypes, Types)) {
+ if (auto EC = mergeTypeStreams(CVTypes, nullptr, Types)) {
consumeError(std::move(EC));
return error(object_error::parse_failed);
}
type = Tool
name = llvm-readobj
parent = Tools
-required_libraries = all-targets BitReader Object DebugInfoCodeView DebugInfoMSF
+required_libraries = all-targets BitReader Object DebugInfoCodeView DebugInfoPDB DebugInfoMSF
StringTableBuilderTest.cpp
MSFBuilderTest.cpp
PDBApiTest.cpp
+ TypeServerHandlerTest.cpp
)
add_llvm_unittest(DebugInfoPDBTests
--- /dev/null
+//===- llvm/unittest/DebugInfo/PDB/TypeServerHandlerTest.cpp --------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ErrorChecking.h"
+
+#include "llvm/DebugInfo/CodeView/CVTypeVisitor.h"
+#include "llvm/DebugInfo/CodeView/TypeRecord.h"
+#include "llvm/DebugInfo/CodeView/TypeRecordMapping.h"
+#include "llvm/DebugInfo/CodeView/TypeSerializer.h"
+#include "llvm/DebugInfo/CodeView/TypeServerHandler.h"
+#include "llvm/DebugInfo/CodeView/TypeTableBuilder.h"
+#include "llvm/DebugInfo/CodeView/TypeVisitorCallbackPipeline.h"
+#include "llvm/DebugInfo/CodeView/TypeVisitorCallbacks.h"
+#include "llvm/DebugInfo/PDB/Native/RawTypes.h"
+#include "llvm/Support/Allocator.h"
+#include "llvm/Support/Error.h"
+
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace llvm::codeview;
+using namespace llvm::pdb;
+
+namespace {
+
+constexpr uint8_t Guid[] = {0x2a, 0x2c, 0x1c, 0x2a, 0xcb, 0x9e, 0x48, 0x18,
+ 0x82, 0x82, 0x7a, 0x87, 0xc3, 0xfe, 0x16, 0xe8};
+StringRef GuidStr(reinterpret_cast<const char *>(Guid),
+ llvm::array_lengthof(Guid));
+
+constexpr const char *Name = "Test Name";
+constexpr int Age = 1;
+
+class MockTypeServerHandler : public TypeServerHandler {
+public:
+ explicit MockTypeServerHandler(bool HandleAlways)
+ : HandleAlways(HandleAlways) {}
+
+ Expected<bool> handle(TypeServer2Record &TS,
+ TypeVisitorCallbacks &Callbacks) override {
+ if (TS.Age != Age || TS.Guid != GuidStr || TS.Name != Name)
+ return make_error<CodeViewError>(cv_error_code::corrupt_record,
+ "Invalid TypeServer record!");
+
+ if (Handled && !HandleAlways)
+ return false;
+
+ Handled = true;
+ return true;
+ }
+
+ bool Handled = false;
+ bool HandleAlways;
+};
+
+class MockTypeVisitorCallbacks : public TypeVisitorCallbacks {
+public:
+ enum class State {
+ Ready,
+ VisitTypeBegin,
+ VisitKnownRecord,
+ VisitTypeEnd,
+ };
+ Error visitTypeBegin(CVType &CVT) override {
+ if (S != State::Ready)
+ return make_error<CodeViewError>(cv_error_code::unspecified,
+ "Invalid visitor state!");
+
+ S = State::VisitTypeBegin;
+ return Error::success();
+ }
+
+ Error visitKnownRecord(CVType &CVT, TypeServer2Record &TS) override {
+ if (S != State::VisitTypeBegin)
+ return make_error<CodeViewError>(cv_error_code::unspecified,
+ "Invalid visitor state!");
+
+ S = State::VisitKnownRecord;
+ return Error::success();
+ }
+
+ Error visitTypeEnd(CVType &CVT) override {
+ if (S != State::VisitKnownRecord)
+ return make_error<CodeViewError>(cv_error_code::unspecified,
+ "Invalid visitor state!");
+
+ S = State::VisitTypeEnd;
+ return Error::success();
+ }
+
+ State S = State::Ready;
+};
+
+class TypeServerHandlerTest : public testing::Test {
+public:
+ void SetUp() override {
+ TypeServer2Record R(TypeRecordKind::TypeServer2);
+ R.Age = Age;
+ R.Guid = GuidStr;
+ R.Name = Name;
+
+ TypeTableBuilder Builder(Allocator);
+ Builder.writeKnownType(R);
+ TypeServerRecord.RecordData = Builder.records().front();
+ TypeServerRecord.Type = TypeLeafKind::LF_TYPESERVER2;
+ }
+
+protected:
+ BumpPtrAllocator Allocator;
+ CVType TypeServerRecord;
+};
+
+// Test that when no type server handler is registered, it gets handled by the
+// normal
+// visitor callbacks.
+TEST_F(TypeServerHandlerTest, VisitRecordNoTypeServer) {
+ MockTypeVisitorCallbacks C2;
+ MockTypeVisitorCallbacks C1;
+ TypeVisitorCallbackPipeline Pipeline;
+
+ Pipeline.addCallbackToPipeline(C1);
+ Pipeline.addCallbackToPipeline(C2);
+ CVTypeVisitor Visitor(Pipeline);
+ EXPECT_NO_ERROR(Visitor.visitTypeRecord(TypeServerRecord));
+
+ EXPECT_EQ(MockTypeVisitorCallbacks::State::VisitTypeEnd, C1.S);
+ EXPECT_EQ(MockTypeVisitorCallbacks::State::VisitTypeEnd, C2.S);
+}
+
+// Test that when a TypeServerHandler is registered, it gets consumed by the
+// handler if and only if the handler returns true.
+TEST_F(TypeServerHandlerTest, VisitRecordWithTypeServerOnce) {
+ MockTypeServerHandler Handler(false);
+
+ MockTypeVisitorCallbacks C1;
+ CVTypeVisitor Visitor(C1);
+ Visitor.addTypeServerHandler(Handler);
+
+ // Our mock server returns true the first time.
+ EXPECT_NO_ERROR(Visitor.visitTypeRecord(TypeServerRecord));
+ EXPECT_TRUE(Handler.Handled);
+ EXPECT_EQ(MockTypeVisitorCallbacks::State::Ready, C1.S);
+
+ // And false the second time.
+ EXPECT_NO_ERROR(Visitor.visitTypeRecord(TypeServerRecord));
+ EXPECT_TRUE(Handler.Handled);
+ EXPECT_EQ(MockTypeVisitorCallbacks::State::VisitTypeEnd, C1.S);
+}
+
+// Test that when a type server handler is registered, if the handler keeps
+// returning true, it will keep getting consumed by the handler and not go
+// to the default processor.
+TEST_F(TypeServerHandlerTest, VisitRecordWithTypeServerAlways) {
+ MockTypeServerHandler Handler(true);
+
+ MockTypeVisitorCallbacks C1;
+ CVTypeVisitor Visitor(C1);
+ Visitor.addTypeServerHandler(Handler);
+
+ EXPECT_NO_ERROR(Visitor.visitTypeRecord(TypeServerRecord));
+ EXPECT_TRUE(Handler.Handled);
+ EXPECT_EQ(MockTypeVisitorCallbacks::State::Ready, C1.S);
+
+ EXPECT_NO_ERROR(Visitor.visitTypeRecord(TypeServerRecord));
+ EXPECT_TRUE(Handler.Handled);
+ EXPECT_EQ(MockTypeVisitorCallbacks::State::Ready, C1.S);
+}
+
+} // end anonymous namespace