[Reproducers] Instrumentation Framework: Serialization

This is the is serialization/deserialization part of the reproducer
instrumentation framework.

For all the details refer to the RFC on the mailing list:
http://lists.llvm.org/pipermail/lldb-dev/2019-January/014530.html

Differential revision: https://reviews.llvm.org/D57714

llvm-svn: 353195
This commit is contained in:
Jonas Devlieghere 2019-02-05 18:46:36 +00:00
parent 40a7f63c37
commit 494fd8f84f
5 changed files with 556 additions and 0 deletions

View File

@ -0,0 +1,302 @@
//===-- ReproducerInstrumentation.h -----------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLDB_UTILITY_REPRODUCER_INSTRUMENTATION_H
#define LLDB_UTILITY_REPRODUCER_INSTRUMENTATION_H
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/Logging.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/ErrorHandling.h"
#include <map>
#include <mutex>
namespace lldb_private {
namespace repro {
/// Mapping between serialized indices and their corresponding objects.
///
/// This class is used during replay to map indices back to in-memory objects.
///
/// When objects are constructed, they are added to this mapping using
/// AddObjectForIndex.
///
/// When an object is passed to a function, its index is deserialized and
/// AddObjectForIndex returns the corresponding object. If there is no object
/// for the given index, a nullptr is returend. The latter is valid when custom
/// replay code is in place and the actual object is ignored.
class IndexToObject {
public:
/// Returns an object as a pointer for the given index or nullptr if not
/// present in the map.
template <typename T> T *GetObjectForIndex(unsigned idx) {
assert(idx != 0 && "Cannot get object for sentinel");
void *object = GetObjectForIndexImpl(idx);
return static_cast<T *>(object);
}
/// Adds a pointer to an object to the mapping for the given index.
template <typename T> void AddObjectForIndex(unsigned idx, T *object) {
AddObjectForIndexImpl(
idx, static_cast<void *>(
const_cast<typename std::remove_const<T>::type *>(object)));
}
/// Adds a reference to an object to the mapping for the given index.
template <typename T> void AddObjectForIndex(unsigned idx, T &object) {
AddObjectForIndexImpl(
idx, static_cast<void *>(
const_cast<typename std::remove_const<T>::type *>(&object)));
}
private:
/// Helper method that does the actual lookup. The void* result is later cast
/// by the caller.
void *GetObjectForIndexImpl(unsigned idx);
/// Helper method that does the actual insertion.
void AddObjectForIndexImpl(unsigned idx, void *object);
/// Keeps a mapping between indices and their corresponding object.
llvm::DenseMap<unsigned, void *> m_mapping;
};
/// We need to differentiate between pointers to fundamental and
/// non-fundamental types. See the corresponding Deserializer::Read method
/// for the reason why.
struct PointerTag {};
struct ReferenceTag {};
struct ValueTag {};
struct FundamentalPointerTag {};
struct FundamentalReferenceTag {};
/// Return the deserialization tag for the given type T.
template <class T> struct serializer_tag { typedef ValueTag type; };
template <class T> struct serializer_tag<T *> {
typedef
typename std::conditional<std::is_fundamental<T>::value,
FundamentalPointerTag, PointerTag>::type type;
};
template <class T> struct serializer_tag<T &> {
typedef typename std::conditional<std::is_fundamental<T>::value,
FundamentalReferenceTag, ReferenceTag>::type
type;
};
/// Deserializes data from a buffer. It is used to deserialize function indices
/// to replay, their arguments and return values.
///
/// Fundamental types and strings are read by value. Objects are read by their
/// index, which get translated by the IndexToObject mapping maintained in
/// this class.
///
/// Additional bookkeeping with regards to the IndexToObject is required to
/// deserialize objects. When a constructor is run or an object is returned by
/// value, we need to capture the object and add it to the index together with
/// its index. This is the job of HandleReplayResult(Void).
class Deserializer {
public:
Deserializer(llvm::StringRef buffer) : m_buffer(buffer) {}
/// Returns true when the buffer has unread data.
bool HasData(unsigned size) { return size <= m_buffer.size(); }
/// Deserialize and interpret value as T.
template <typename T> T Deserialize() {
return Read<T>(typename serializer_tag<T>::type());
}
/// Store the returned value in the index-to-object mapping.
template <typename T> void HandleReplayResult(const T &t) {
unsigned result = Deserialize<unsigned>();
if (std::is_fundamental<T>::value)
return;
// We need to make a copy as the original object might go out of scope.
m_index_to_object.AddObjectForIndex(result, new T(t));
}
/// Store the returned value in the index-to-object mapping.
template <typename T> void HandleReplayResult(T *t) {
unsigned result = Deserialize<unsigned>();
if (std::is_fundamental<T>::value)
return;
m_index_to_object.AddObjectForIndex(result, t);
}
/// All returned types are recorded, even when the function returns a void.
/// The latter requires special handling.
void HandleReplayResultVoid() {
unsigned result = Deserialize<unsigned>();
assert(result == 0);
}
protected:
IndexToObject &GetIndexToObject() { return m_index_to_object; }
private:
template <typename T> T Read(ValueTag) {
assert(HasData(sizeof(T)));
T t;
std::memcpy(reinterpret_cast<char *>(&t), m_buffer.data(), sizeof(T));
m_buffer = m_buffer.drop_front(sizeof(T));
return t;
}
template <typename T> T Read(PointerTag) {
typedef typename std::remove_pointer<T>::type UnderlyingT;
return m_index_to_object.template GetObjectForIndex<UnderlyingT>(
Deserialize<unsigned>());
}
template <typename T> T Read(ReferenceTag) {
typedef typename std::remove_reference<T>::type UnderlyingT;
// If this is a reference to a fundamental type we just read its value.
return *m_index_to_object.template GetObjectForIndex<UnderlyingT>(
Deserialize<unsigned>());
}
/// This method is used to parse references to fundamental types. Because
/// they're not recorded in the object table we have serialized their value.
/// We read its value, allocate a copy on the heap, and return a pointer to
/// the copy.
template <typename T> T Read(FundamentalPointerTag) {
typedef typename std::remove_pointer<T>::type UnderlyingT;
return new UnderlyingT(Deserialize<UnderlyingT>());
}
/// This method is used to parse references to fundamental types. Because
/// they're not recorded in the object table we have serialized their value.
/// We read its value, allocate a copy on the heap, and return a reference to
/// the copy.
template <typename T> T Read(FundamentalReferenceTag) {
// If this is a reference to a fundamental type we just read its value.
typedef typename std::remove_reference<T>::type UnderlyingT;
return *(new UnderlyingT(Deserialize<UnderlyingT>()));
}
/// Mapping of indices to objects.
IndexToObject m_index_to_object;
/// Buffer containing the serialized data.
llvm::StringRef m_buffer;
};
/// Partial specialization for C-style strings. We read the string value
/// instead of treating it as pointer.
template <> const char *Deserializer::Deserialize<const char *>();
template <> char *Deserializer::Deserialize<char *>();
/// Helpers to auto-synthesize function replay code. It deserializes the replay
/// function's arguments one by one and finally calls the corresponding
/// function.
template <typename... Remaining> struct DeserializationHelper;
template <typename Head, typename... Tail>
struct DeserializationHelper<Head, Tail...> {
template <typename Result, typename... Deserialized> struct deserialized {
static Result doit(Deserializer &deserializer,
Result (*f)(Deserialized..., Head, Tail...),
Deserialized... d) {
return DeserializationHelper<Tail...>::
template deserialized<Result, Deserialized..., Head>::doit(
deserializer, f, d..., deserializer.Deserialize<Head>());
}
};
};
template <> struct DeserializationHelper<> {
template <typename Result, typename... Deserialized> struct deserialized {
static Result doit(Deserializer &deserializer, Result (*f)(Deserialized...),
Deserialized... d) {
return f(d...);
}
};
};
/// Maps an object to an index for serialization. Indices are unique and
/// incremented for every new object.
///
/// Indices start at 1 in order to differentiate with an invalid index (0) in
/// the serialized buffer.
class ObjectToIndex {
public:
template <typename T> unsigned GetIndexForObject(T *t) {
return GetIndexForObjectImpl((void *)t);
}
private:
unsigned GetIndexForObjectImpl(void *object);
std::mutex m_mutex;
llvm::DenseMap<void *, unsigned> m_mapping;
};
/// Serializes functions, their arguments and their return type to a stream.
class Serializer {
public:
Serializer(llvm::raw_ostream &stream = llvm::outs()) : m_stream(stream) {}
/// Recursively serialize all the given arguments.
template <typename Head, typename... Tail>
void SerializeAll(const Head &head, const Tail &... tail) {
Serialize(head);
SerializeAll(tail...);
}
void SerializeAll() {}
private:
/// Serialize pointers. We need to differentiate between pointers to
/// fundamental types (in which case we serialize its value) and pointer to
/// objects (in which case we serialize their index).
template <typename T> void Serialize(T *t) {
if (std::is_fundamental<T>::value) {
Serialize(*t);
} else {
unsigned idx = m_tracker.GetIndexForObject(t);
Serialize(idx);
}
}
/// Serialize references. We need to differentiate between references to
/// fundamental types (in which case we serialize its value) and references
/// to objects (in which case we serialize their index).
template <typename T> void Serialize(T &t) {
if (std::is_fundamental<T>::value) {
m_stream.write(reinterpret_cast<const char *>(&t), sizeof(T));
} else {
unsigned idx = m_tracker.GetIndexForObject(&t);
Serialize(idx);
}
}
void Serialize(void *v) {
// FIXME: Support void*
llvm_unreachable("void* is currently unsupported.");
}
void Serialize(const char *t) {
m_stream << t;
m_stream.write(0x0);
}
/// Serialization stream.
llvm::raw_ostream &m_stream;
/// Mapping of objects to indices.
ObjectToIndex m_tracker;
};
} // namespace repro
} // namespace lldb_private
#endif // LLDB_UTILITY_REPRODUCER_INSTRUMENTATION_H

View File

@ -67,6 +67,7 @@ add_lldb_library(lldbUtility
RegisterValue.cpp
RegularExpression.cpp
Reproducer.cpp
ReproducerInstrumentation.cpp
Scalar.cpp
SelectHelper.cpp
SharingPtr.cpp

View File

@ -0,0 +1,44 @@
//===-- ReproducerInstrumentation.cpp ---------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "lldb/Utility/ReproducerInstrumentation.h"
#include "lldb/Utility/Reproducer.h"
using namespace lldb_private;
using namespace lldb_private::repro;
void *IndexToObject::GetObjectForIndexImpl(unsigned idx) {
return m_mapping.lookup(idx);
}
void IndexToObject::AddObjectForIndexImpl(unsigned idx, void *object) {
assert(idx != 0 && "Cannot add object for sentinel");
m_mapping[idx] = object;
}
template <> char *Deserializer::Deserialize<char *>() {
return const_cast<char *>(Deserialize<const char *>());
}
template <> const char *Deserializer::Deserialize<const char *>() {
auto pos = m_buffer.find('\0');
if (pos == llvm::StringRef::npos)
return nullptr;
const char *str = m_buffer.data();
m_buffer = m_buffer.drop_front(pos + 1);
return str;
}
unsigned ObjectToIndex::GetIndexForObjectImpl(void *object) {
std::lock_guard<std::mutex> guard(m_mutex);
unsigned index = m_mapping.size() + 1;
auto it = m_mapping.find(object);
if (it == m_mapping.end())
m_mapping[object] = index;
return m_mapping[object];
}

View File

@ -20,6 +20,7 @@ add_lldb_unittest(UtilityTests
PredicateTest.cpp
RegisterValueTest.cpp
ReproducerTest.cpp
ReproducerInstrumentationTest.cpp
ScalarTest.cpp
StateTest.cpp
StatusTest.cpp

View File

@ -0,0 +1,208 @@
//===-- ReproducerInstrumentationTest.cpp -----------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "lldb/Utility/ReproducerInstrumentation.h"
using namespace lldb_private;
using namespace lldb_private::repro;
namespace {
struct Foo {
int m = 1;
};
struct Bar {
double m = 2;
};
bool operator==(const Foo &LHS, const Foo &RHS) { return LHS.m == RHS.m; }
bool operator==(const Bar &LHS, const Bar &RHS) { return LHS.m == RHS.m; }
struct Pod {
bool a = true;
bool b = false;
char c = 'a';
float d = 1.1;
int e = 2;
long long f = 3;
long g = 4;
short h = 5;
unsigned char i = 'b';
unsigned int j = 6;
unsigned long long k = 7;
unsigned long l = 8;
unsigned short m = 9;
};
} // namespace
static const Pod p;
TEST(IndexToObjectTest, ObjectForIndex) {
IndexToObject index_to_object;
Foo foo;
Bar bar;
EXPECT_EQ(nullptr, index_to_object.GetObjectForIndex<Foo>(1));
EXPECT_EQ(nullptr, index_to_object.GetObjectForIndex<Bar>(2));
index_to_object.AddObjectForIndex<Foo>(1, foo);
index_to_object.AddObjectForIndex<Bar>(2, &bar);
EXPECT_EQ(&foo, index_to_object.GetObjectForIndex<Foo>(1));
EXPECT_EQ(&bar, index_to_object.GetObjectForIndex<Bar>(2));
}
TEST(DeserializerTest, HasData) {
{
Deserializer deserializer("");
EXPECT_FALSE(deserializer.HasData(1));
}
{
Deserializer deserializer("a");
EXPECT_TRUE(deserializer.HasData(1));
EXPECT_FALSE(deserializer.HasData(2));
}
}
TEST(SerializationRountripTest, SerializeDeserializePod) {
std::string str;
llvm::raw_string_ostream os(str);
Serializer serializer(os);
serializer.SerializeAll(p.a, p.b, p.c, p.d, p.e, p.f, p.g, p.h, p.i, p.j, p.k,
p.l, p.m);
llvm::StringRef buffer(os.str());
Deserializer deserializer(buffer);
EXPECT_EQ(p.a, deserializer.Deserialize<bool>());
EXPECT_EQ(p.b, deserializer.Deserialize<bool>());
EXPECT_EQ(p.c, deserializer.Deserialize<char>());
EXPECT_EQ(p.d, deserializer.Deserialize<float>());
EXPECT_EQ(p.e, deserializer.Deserialize<int>());
EXPECT_EQ(p.f, deserializer.Deserialize<long long>());
EXPECT_EQ(p.g, deserializer.Deserialize<long>());
EXPECT_EQ(p.h, deserializer.Deserialize<short>());
EXPECT_EQ(p.i, deserializer.Deserialize<unsigned char>());
EXPECT_EQ(p.j, deserializer.Deserialize<unsigned int>());
EXPECT_EQ(p.k, deserializer.Deserialize<unsigned long long>());
EXPECT_EQ(p.l, deserializer.Deserialize<unsigned long>());
EXPECT_EQ(p.m, deserializer.Deserialize<unsigned short>());
}
TEST(SerializationRountripTest, SerializeDeserializePodPointers) {
std::string str;
llvm::raw_string_ostream os(str);
Serializer serializer(os);
serializer.SerializeAll(&p.a, &p.b, &p.c, &p.d, &p.e, &p.f, &p.g, &p.h, &p.i,
&p.j, &p.k, &p.l, &p.m);
llvm::StringRef buffer(os.str());
Deserializer deserializer(buffer);
EXPECT_EQ(p.a, *deserializer.Deserialize<bool *>());
EXPECT_EQ(p.b, *deserializer.Deserialize<bool *>());
EXPECT_EQ(p.c, *deserializer.Deserialize<char *>());
EXPECT_EQ(p.d, *deserializer.Deserialize<float *>());
EXPECT_EQ(p.e, *deserializer.Deserialize<int *>());
EXPECT_EQ(p.f, *deserializer.Deserialize<long long *>());
EXPECT_EQ(p.g, *deserializer.Deserialize<long *>());
EXPECT_EQ(p.h, *deserializer.Deserialize<short *>());
EXPECT_EQ(p.i, *deserializer.Deserialize<unsigned char *>());
EXPECT_EQ(p.j, *deserializer.Deserialize<unsigned int *>());
EXPECT_EQ(p.k, *deserializer.Deserialize<unsigned long long *>());
EXPECT_EQ(p.l, *deserializer.Deserialize<unsigned long *>());
EXPECT_EQ(p.m, *deserializer.Deserialize<unsigned short *>());
}
TEST(SerializationRountripTest, SerializeDeserializePodReferences) {
std::string str;
llvm::raw_string_ostream os(str);
Serializer serializer(os);
serializer.SerializeAll(p.a, p.b, p.c, p.d, p.e, p.f, p.g, p.h, p.i, p.j, p.k,
p.l, p.m);
llvm::StringRef buffer(os.str());
Deserializer deserializer(buffer);
EXPECT_EQ(p.a, deserializer.Deserialize<bool &>());
EXPECT_EQ(p.b, deserializer.Deserialize<bool &>());
EXPECT_EQ(p.c, deserializer.Deserialize<char &>());
EXPECT_EQ(p.d, deserializer.Deserialize<float &>());
EXPECT_EQ(p.e, deserializer.Deserialize<int &>());
EXPECT_EQ(p.f, deserializer.Deserialize<long long &>());
EXPECT_EQ(p.g, deserializer.Deserialize<long &>());
EXPECT_EQ(p.h, deserializer.Deserialize<short &>());
EXPECT_EQ(p.i, deserializer.Deserialize<unsigned char &>());
EXPECT_EQ(p.j, deserializer.Deserialize<unsigned int &>());
EXPECT_EQ(p.k, deserializer.Deserialize<unsigned long long &>());
EXPECT_EQ(p.l, deserializer.Deserialize<unsigned long &>());
EXPECT_EQ(p.m, deserializer.Deserialize<unsigned short &>());
}
TEST(SerializationRountripTest, SerializeDeserializeCString) {
const char *cstr = "string";
std::string str;
llvm::raw_string_ostream os(str);
Serializer serializer(os);
serializer.SerializeAll(cstr);
llvm::StringRef buffer(os.str());
Deserializer deserializer(buffer);
EXPECT_STREQ(cstr, deserializer.Deserialize<const char *>());
}
TEST(SerializationRountripTest, SerializeDeserializeObjectPointer) {
Foo foo;
Bar bar;
std::string str;
llvm::raw_string_ostream os(str);
Serializer serializer(os);
serializer.SerializeAll(static_cast<unsigned>(1), static_cast<unsigned>(2));
serializer.SerializeAll(&foo, &bar);
llvm::StringRef buffer(os.str());
Deserializer deserializer(buffer);
deserializer.HandleReplayResult(&foo);
deserializer.HandleReplayResult(&bar);
EXPECT_EQ(foo, *deserializer.Deserialize<Foo *>());
EXPECT_EQ(bar, *deserializer.Deserialize<Bar *>());
}
TEST(SerializationRountripTest, SerializeDeserializeObjectReference) {
Foo foo;
Bar bar;
std::string str;
llvm::raw_string_ostream os(str);
Serializer serializer(os);
serializer.SerializeAll(static_cast<unsigned>(1), static_cast<unsigned>(2));
serializer.SerializeAll(foo, bar);
llvm::StringRef buffer(os.str());
Deserializer deserializer(buffer);
deserializer.HandleReplayResult(&foo);
deserializer.HandleReplayResult(&bar);
EXPECT_EQ(foo, deserializer.Deserialize<Foo &>());
EXPECT_EQ(bar, deserializer.Deserialize<Bar &>());
}