summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lldb/include/lldb/API/SBReproducer.h23
-rw-r--r--lldb/include/lldb/Utility/ReproducerInstrumentation.h320
-rw-r--r--lldb/source/API/CMakeLists.txt1
-rw-r--r--lldb/source/API/SBReproducer.cpp51
-rw-r--r--lldb/source/API/SBReproducerPrivate.h72
-rw-r--r--lldb/source/Utility/ReproducerInstrumentation.cpp53
-rw-r--r--lldb/tools/driver/Driver.cpp14
-rw-r--r--lldb/unittests/Utility/ReproducerInstrumentationTest.cpp306
8 files changed, 830 insertions, 10 deletions
diff --git a/lldb/include/lldb/API/SBReproducer.h b/lldb/include/lldb/API/SBReproducer.h
new file mode 100644
index 00000000000..771d73419cc
--- /dev/null
+++ b/lldb/include/lldb/API/SBReproducer.h
@@ -0,0 +1,23 @@
+//===-- SBReproducer.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_API_SBREPRODUCER_H
+#define LLDB_API_SBREPRODUCER_H
+
+#include "lldb/lldb-defines.h"
+
+namespace lldb {
+
+class LLDB_API SBReproducer {
+public:
+ static bool Replay();
+};
+
+} // namespace lldb
+
+#endif
diff --git a/lldb/include/lldb/Utility/ReproducerInstrumentation.h b/lldb/include/lldb/Utility/ReproducerInstrumentation.h
index 61e25ca641a..aca58335a8b 100644
--- a/lldb/include/lldb/Utility/ReproducerInstrumentation.h
+++ b/lldb/include/lldb/Utility/ReproducerInstrumentation.h
@@ -1,5 +1,4 @@
//===-- 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
@@ -18,7 +17,104 @@
#include "llvm/Support/ErrorHandling.h"
#include <map>
-#include <mutex>
+
+#define LLDB_REGISTER_CONSTRUCTOR(Class, Signature) \
+ Register<Class * Signature>(&construct<Class Signature>::doit)
+#define LLDB_REGISTER_METHOD(Result, Class, Method, Signature) \
+ Register(&invoke<Result(Class::*) Signature>::method<&Class::Method>::doit)
+#define LLDB_REGISTER_METHOD_CONST(Result, Class, Method, Signature) \
+ Register(&invoke<Result(Class::*) \
+ Signature const>::method_const<&Class::Method>::doit)
+#define LLDB_REGISTER_STATIC_METHOD(Result, Class, Method, Signature) \
+ Register<Result Signature>(static_cast<Result(*) Signature>(&Class::Method))
+
+#define LLDB_RECORD_CONSTRUCTOR(Class, Signature, ...) \
+ if (lldb_private::repro::InstrumentationData data = \
+ LLDB_GET_INSTRUMENTATION_DATA()) { \
+ lldb_private::repro::Recorder sb_recorder( \
+ data.GetSerializer(), data.GetRegistry(), LLVM_PRETTY_FUNCTION); \
+ sb_recorder.Record(&lldb_private::repro::construct<Class Signature>::doit, \
+ __VA_ARGS__); \
+ sb_recorder.RecordResult(this); \
+ }
+
+#define LLDB_RECORD_CONSTRUCTOR_NO_ARGS(Class) \
+ if (lldb_private::repro::InstrumentationData data = \
+ LLDB_GET_INSTRUMENTATION_DATA()) { \
+ lldb_private::repro::Recorder sb_recorder( \
+ data.GetSerializer(), data.GetRegistry(), LLVM_PRETTY_FUNCTION); \
+ sb_recorder.Record(&lldb_private::repro::construct<Class()>::doit); \
+ sb_recorder.RecordResult(this); \
+ }
+
+#define LLDB_RECORD_METHOD(Result, Class, Method, Signature, ...) \
+ llvm::Optional<lldb_private::repro::Recorder> sb_recorder; \
+ if (lldb_private::repro::InstrumentationData data = \
+ LLDB_GET_INSTRUMENTATION_DATA()) { \
+ sb_recorder.emplace(data.GetSerializer(), data.GetRegistry(), \
+ LLVM_PRETTY_FUNCTION); \
+ sb_recorder->Record( \
+ &lldb_private::repro::invoke<Result( \
+ Class::*) Signature>::method<&Class::Method>::doit, \
+ this, __VA_ARGS__); \
+ }
+
+#define LLDB_RECORD_METHOD_CONST(Result, Class, Method, Signature, ...) \
+ llvm::Optional<lldb_private::repro::Recorder> sb_recorder; \
+ if (lldb_private::repro::InstrumentationData data = \
+ LLDB_GET_INSTRUMENTATION_DATA()) { \
+ sb_recorder.emplace(data.GetSerializer(), data.GetRegistry(), \
+ LLVM_PRETTY_FUNCTION); \
+ sb_recorder->Record( \
+ &lldb_private::repro::invoke<Result( \
+ Class::*) Signature const>::method_const<&Class::Method>::doit, \
+ this, __VA_ARGS__); \
+ }
+
+#define LLDB_RECORD_METHOD_NO_ARGS(Result, Class, Method) \
+ llvm::Optional<lldb_private::repro::Recorder> sb_recorder; \
+ if (lldb_private::repro::InstrumentationData data = \
+ LLDB_GET_INSTRUMENTATION_DATA()) { \
+ sb_recorder.emplace(data.GetSerializer(), data.GetRegistry(), \
+ LLVM_PRETTY_FUNCTION); \
+ sb_recorder->Record(&lldb_private::repro::invoke<Result ( \
+ Class::*)()>::method<&Class::Method>::doit, \
+ this); \
+ }
+
+#define LLDB_RECORD_METHOD_CONST_NO_ARGS(Result, Class, Method) \
+ llvm::Optional<lldb_private::repro::Recorder> sb_recorder; \
+ if (lldb_private::repro::InstrumentationData data = \
+ LLDB_GET_INSTRUMENTATION_DATA()) { \
+ sb_recorder.emplace(data.GetSerializer(), data.GetRegistry(), \
+ LLVM_PRETTY_FUNCTION); \
+ sb_recorder->Record( \
+ &lldb_private::repro::invoke<Result ( \
+ Class::*)() const>::method_const<&Class::Method>::doit, \
+ this); \
+ }
+
+#define LLDB_RECORD_STATIC_METHOD(Result, Class, Method, Signature, ...) \
+ llvm::Optional<lldb_private::repro::Recorder> sb_recorder; \
+ if (lldb_private::repro::InstrumentationData data = \
+ LLDB_GET_INSTRUMENTATION_DATA()) { \
+ sb_recorder.emplace(data.GetSerializer(), data.GetRegistry(), \
+ LLVM_PRETTY_FUNCTION); \
+ sb_recorder->Record(static_cast<Result(*) Signature>(&Class::Method), \
+ __VA_ARGS__); \
+ }
+
+#define LLDB_RECORD_STATIC_METHOD_NO_ARGS(Result, Class, Method) \
+ llvm::Optional<lldb_private::repro::Recorder> sb_recorder; \
+ if (lldb_private::repro::InstrumentationData data = \
+ LLDB_GET_INSTRUMENTATION_DATA()) { \
+ sb_recorder.emplace(data.GetSerializer(), data.GetRegistry(), \
+ LLVM_PRETTY_FUNCTION); \
+ sb_recorder->Record(static_cast<Result (*)()>(&Class::Method)); \
+ }
+
+#define LLDB_RECORD_RESULT(Result) \
+ sb_recorder ? sb_recorder->RecordResult(Result) : Result;
namespace lldb_private {
namespace repro {
@@ -139,9 +235,6 @@ public:
assert(result == 0);
}
-protected:
- IndexToObject &GetIndexToObject() { return m_index_to_object; }
-
private:
template <typename T> T Read(ValueTag) {
assert(HasData(sizeof(T)));
@@ -222,6 +315,114 @@ template <> struct DeserializationHelper<> {
};
};
+/// The replayer interface.
+struct Replayer {
+ virtual ~Replayer() {}
+ virtual void operator()(Deserializer &deserializer) const = 0;
+};
+
+/// The default replayer deserializes the arguments and calls the function.
+template <typename Signature> struct DefaultReplayer;
+template <typename Result, typename... Args>
+struct DefaultReplayer<Result(Args...)> : public Replayer {
+ DefaultReplayer(Result (*f)(Args...)) : Replayer(), f(f) {}
+
+ void operator()(Deserializer &deserializer) const override {
+ deserializer.HandleReplayResult(
+ DeserializationHelper<Args...>::template deserialized<Result>::doit(
+ deserializer, f));
+ }
+
+ Result (*f)(Args...);
+};
+
+/// Partial specialization for function returning a void type. It ignores the
+/// (absent) return value.
+template <typename... Args>
+struct DefaultReplayer<void(Args...)> : public Replayer {
+ DefaultReplayer(void (*f)(Args...)) : Replayer(), f(f) {}
+
+ void operator()(Deserializer &deserializer) const override {
+ DeserializationHelper<Args...>::template deserialized<void>::doit(
+ deserializer, f);
+ deserializer.HandleReplayResultVoid();
+ }
+
+ void (*f)(Args...);
+};
+
+/// The registry contains a unique mapping between functions and their ID. The
+/// IDs can be serialized and deserialized to replay a function. Functions need
+/// to be registered with the registry for this to work.
+class Registry {
+public:
+ Registry() = default;
+ virtual ~Registry() = default;
+
+ /// Register a default replayer for a function.
+ template <typename Signature> void Register(Signature *f) {
+ DoRegister(uintptr_t(f), llvm::make_unique<DefaultReplayer<Signature>>(f));
+ }
+
+ /// Register a replayer that invokes a custom function with the same
+ /// signature as the replayed function.
+ template <typename Signature> void Register(Signature *f, Signature *g) {
+ DoRegister(uintptr_t(f), llvm::make_unique<DefaultReplayer<Signature>>(g));
+ }
+
+ /// Replay functions from a file.
+ bool Replay(const FileSpec &file);
+
+ /// Replay functions from a buffer.
+ bool Replay(llvm::StringRef buffer);
+
+ /// Returns the ID for a given function address.
+ unsigned GetID(uintptr_t addr);
+
+protected:
+ /// Register the given replayer for a function (and the ID mapping).
+ void DoRegister(uintptr_t RunID, std::unique_ptr<Replayer> replayer);
+
+private:
+ /// Mapping of function addresses to replayers and their ID.
+ std::map<uintptr_t, std::pair<std::unique_ptr<Replayer>, unsigned>>
+ m_replayers;
+
+ /// Mapping of IDs to replayer instances.
+ std::map<unsigned, Replayer *> m_ids;
+};
+
+/// To be used as the "Runtime ID" of a constructor. It also invokes the
+/// constructor when called.
+template <typename Signature> struct construct;
+template <typename Class, typename... Args> struct construct<Class(Args...)> {
+ static Class *doit(Args... args) { return new Class(args...); }
+};
+
+/// To be used as the "Runtime ID" of a member function. It also invokes the
+/// member function when called.
+template <typename Signature> struct invoke;
+template <typename Result, typename Class, typename... Args>
+struct invoke<Result (Class::*)(Args...)> {
+ template <Result (Class::*m)(Args...)> struct method {
+ static Result doit(Class *c, Args... args) { return (c->*m)(args...); }
+ };
+};
+
+template <typename Result, typename Class, typename... Args>
+struct invoke<Result (Class::*)(Args...) const> {
+ template <Result (Class::*m)(Args...) const> struct method_const {
+ static Result doit(Class *c, Args... args) { return (c->*m)(args...); }
+ };
+};
+
+template <typename Class, typename... Args>
+struct invoke<void (Class::*)(Args...)> {
+ template <void (Class::*m)(Args...)> struct method {
+ static void doit(Class *c, Args... args) { (c->*m)(args...); }
+ };
+};
+
/// Maps an object to an index for serialization. Indices are unique and
/// incremented for every new object.
///
@@ -236,7 +437,6 @@ public:
private:
unsigned GetIndexForObjectImpl(void *object);
- std::mutex m_mutex;
llvm::DenseMap<void *, unsigned> m_mapping;
};
@@ -296,6 +496,114 @@ private:
ObjectToIndex m_tracker;
};
+class InstrumentationData {
+public:
+ InstrumentationData() : m_serializer(nullptr), m_registry(nullptr){};
+ InstrumentationData(Serializer &serializer, Registry &registry)
+ : m_serializer(&serializer), m_registry(&registry){};
+
+ Serializer &GetSerializer() { return *m_serializer; }
+ Registry &GetRegistry() { return *m_registry; }
+
+ operator bool() { return m_serializer != nullptr && m_registry != nullptr; }
+
+private:
+ Serializer *m_serializer;
+ Registry *m_registry;
+};
+
+/// RAII object that tracks the function invocations and their return value.
+///
+/// API calls are only captured when the API boundary is crossed. Once we're in
+/// the API layer, and another API function is called, it doesn't need to be
+/// recorded.
+///
+/// When a call is recored, its result is always recorded as well, even if the
+/// function returns a void. For functions that return by value, RecordResult
+/// should be used. Otherwise a sentinel value (0) will be serialized.
+class Recorder {
+public:
+ Recorder(Serializer &serializer, Registry &registry,
+ llvm::StringRef pretty_func = {});
+ ~Recorder();
+
+ /// Records a single function call.
+ template <typename Result, typename... FArgs, typename... RArgs>
+ void Record(Result (*f)(FArgs...), const RArgs &... args) {
+ if (!ShouldCapture())
+ return;
+
+ unsigned id = m_registry.GetID(uintptr_t(f));
+
+ LLDB_LOG(GetLogIfAllCategoriesSet(LIBLLDB_LOG_API), "#{0} '{1}'", id,
+ m_pretty_func);
+
+ m_serializer.SerializeAll(id);
+ m_serializer.SerializeAll(args...);
+
+ if (std::is_class<typename std::remove_pointer<
+ typename std::remove_reference<Result>::type>::type>::value) {
+ m_result_recorded = false;
+ } else {
+ m_serializer.SerializeAll(0);
+ m_result_recorded = true;
+ }
+ }
+
+ /// Records a single function call.
+ template <typename... Args>
+ void Record(void (*f)(Args...), const Args &... args) {
+ if (!ShouldCapture())
+ return;
+
+ unsigned id = m_registry.GetID(uintptr_t(f));
+
+ LLDB_LOG(GetLogIfAllCategoriesSet(LIBLLDB_LOG_API), "#{0} '{1}'", id,
+ m_pretty_func);
+
+ m_serializer.SerializeAll(id);
+ m_serializer.SerializeAll(args...);
+
+ // Record result.
+ m_serializer.SerializeAll(0);
+ m_result_recorded = true;
+ }
+
+ /// Record the result of a function call.
+ template <typename Result> Result RecordResult(const Result &r) {
+ UpdateBoundary();
+ if (ShouldCapture()) {
+ assert(!m_result_recorded);
+ m_serializer.SerializeAll(r);
+ m_result_recorded = true;
+ }
+ return r;
+ }
+
+private:
+ void UpdateBoundary() {
+ if (m_local_boundary)
+ g_global_boundary = false;
+ }
+
+ bool ShouldCapture() { return m_local_boundary; }
+
+ Serializer &m_serializer;
+ Registry &m_registry;
+
+ /// Pretty function for logging.
+ llvm::StringRef m_pretty_func;
+
+ /// Whether this function call was the one crossing the API boundary.
+ bool m_local_boundary;
+
+ /// Whether the return value was recorded explicitly.
+ bool m_result_recorded;
+
+ /// Whether we're currently across the API boundary.
+ static bool g_global_boundary;
+};
+
} // namespace repro
} // namespace lldb_private
diff --git a/lldb/source/API/CMakeLists.txt b/lldb/source/API/CMakeLists.txt
index e4ec64e537a..f5c33ed6ab5 100644
--- a/lldb/source/API/CMakeLists.txt
+++ b/lldb/source/API/CMakeLists.txt
@@ -50,6 +50,7 @@ add_lldb_library(liblldb SHARED
SBProcessInfo.cpp
SBQueue.cpp
SBQueueItem.cpp
+ SBReproducer.cpp
SBSection.cpp
SBSourceManager.cpp
SBStream.cpp
diff --git a/lldb/source/API/SBReproducer.cpp b/lldb/source/API/SBReproducer.cpp
new file mode 100644
index 00000000000..f453f8cdaa3
--- /dev/null
+++ b/lldb/source/API/SBReproducer.cpp
@@ -0,0 +1,51 @@
+//===-- SBReproducer.cpp ----------------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "SBReproducerPrivate.h"
+
+#include "lldb/API/LLDB.h"
+#include "lldb/API/SBAddress.h"
+#include "lldb/API/SBAttachInfo.h"
+#include "lldb/API/SBBlock.h"
+#include "lldb/API/SBBreakpoint.h"
+#include "lldb/API/SBCommandInterpreter.h"
+#include "lldb/API/SBData.h"
+#include "lldb/API/SBDebugger.h"
+#include "lldb/API/SBDeclaration.h"
+#include "lldb/API/SBError.h"
+#include "lldb/API/SBFileSpec.h"
+#include "lldb/API/SBHostOS.h"
+#include "lldb/API/SBReproducer.h"
+
+#include "lldb/Host/FileSystem.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::repro;
+
+SBRegistry::SBRegistry() {}
+
+bool SBReproducer::Replay() {
+ repro::Loader *loader = repro::Reproducer::Instance().GetLoader();
+ if (!loader)
+ return false;
+
+ FileSpec file = loader->GetFile<SBInfo>();
+ if (!file)
+ return false;
+
+ SBRegistry registry;
+ registry.Replay(file);
+
+ return true;
+}
+
+char lldb_private::repro::SBProvider::ID = 0;
+const char *SBInfo::name = "sbapi";
+const char *SBInfo::file = "sbapi.bin";
diff --git a/lldb/source/API/SBReproducerPrivate.h b/lldb/source/API/SBReproducerPrivate.h
new file mode 100644
index 00000000000..b550f5ed3da
--- /dev/null
+++ b/lldb/source/API/SBReproducerPrivate.h
@@ -0,0 +1,72 @@
+//===-- SBReproducerPrivate.h -----------------------------------*- C++ -*-===//
+//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_API_SBREPRODUCER_PRIVATE_H
+#define LLDB_API_SBREPRODUCER_PRIVATE_H
+
+#include "lldb/API/SBReproducer.h"
+
+#include "lldb/Utility/FileSpec.h"
+#include "lldb/Utility/Log.h"
+#include "lldb/Utility/Reproducer.h"
+#include "lldb/Utility/ReproducerInstrumentation.h"
+
+#include "llvm/ADT/DenseMap.h"
+
+#define LLDB_GET_INSTRUMENTATION_DATA() \
+ lldb_private::repro::GetInstrumentationData()
+
+namespace lldb_private {
+namespace repro {
+
+class SBRegistry : public Registry {
+public:
+ SBRegistry();
+};
+
+struct SBInfo {
+ static const char *name;
+ static const char *file;
+};
+
+class SBProvider : public Provider<SBProvider> {
+public:
+ typedef SBInfo info;
+
+ SBProvider(const FileSpec &directory)
+ : Provider(directory),
+ m_stream(directory.CopyByAppendingPathComponent("sbapi.bin").GetPath(),
+ m_ec, llvm::sys::fs::OpenFlags::F_None),
+ m_serializer(m_stream) {}
+
+ Serializer &GetSerializer() { return m_serializer; }
+ Registry &GetRegistry() { return m_registry; }
+
+ static char ID;
+
+private:
+ std::error_code m_ec;
+ llvm::raw_fd_ostream m_stream;
+ Serializer m_serializer;
+ SBRegistry m_registry;
+};
+
+inline InstrumentationData GetInstrumentationData() {
+ if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) {
+ auto &p = g->GetOrCreate<SBProvider>();
+ return {p.GetSerializer(), p.GetRegistry()};
+ }
+ return {};
+}
+
+} // namespace repro
+} // namespace lldb_private
+
+#endif
diff --git a/lldb/source/Utility/ReproducerInstrumentation.cpp b/lldb/source/Utility/ReproducerInstrumentation.cpp
index 8b53c48872f..984540d1c0b 100644
--- a/lldb/source/Utility/ReproducerInstrumentation.cpp
+++ b/lldb/source/Utility/ReproducerInstrumentation.cpp
@@ -34,11 +34,62 @@ template <> const char *Deserializer::Deserialize<const char *>() {
return str;
}
+bool Registry::Replay(const FileSpec &file) {
+ auto error_or_file = llvm::MemoryBuffer::getFile(file.GetPath());
+ if (auto err = error_or_file.getError())
+ return false;
+
+ return Replay((*error_or_file)->getBuffer());
+}
+
+bool Registry::Replay(llvm::StringRef buffer) {
+ Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_API);
+
+ Deserializer deserializer(buffer);
+ while (deserializer.HasData(1)) {
+ unsigned id = deserializer.Deserialize<unsigned>();
+ LLDB_LOG(log, "Replaying function #{0}", id);
+ m_ids[id]->operator()(deserializer);
+ }
+
+ return true;
+}
+
+void Registry::DoRegister(uintptr_t RunID, std::unique_ptr<Replayer> replayer) {
+ const unsigned id = m_replayers.size() + 1;
+ assert(m_replayers.find(RunID) == m_replayers.end());
+ m_replayers[RunID] = std::make_pair(std::move(replayer), id);
+ m_ids[id] = m_replayers[RunID].first.get();
+}
+
+unsigned Registry::GetID(uintptr_t addr) {
+ unsigned id = m_replayers[addr].second;
+ assert(id != 0 && "Forgot to add function to registry?");
+ return id;
+}
+
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];
}
+
+Recorder::Recorder(Serializer &serializer, Registry &registry,
+ llvm::StringRef pretty_func)
+ : m_serializer(serializer), m_registry(registry),
+ m_pretty_func(pretty_func), m_local_boundary(false),
+ m_result_recorded(true) {
+ if (!g_global_boundary) {
+ g_global_boundary = true;
+ m_local_boundary = true;
+ }
+}
+
+Recorder::~Recorder() {
+ assert(m_result_recorded && "Did you forget LLDB_RECORD_RESULT?");
+ UpdateBoundary();
+}
+
+bool lldb_private::repro::Recorder::g_global_boundary;
diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp
index 8d7decde776..45effa4e518 100644
--- a/lldb/tools/driver/Driver.cpp
+++ b/lldb/tools/driver/Driver.cpp
@@ -13,6 +13,7 @@
#include "lldb/API/SBDebugger.h"
#include "lldb/API/SBHostOS.h"
#include "lldb/API/SBLanguageRuntime.h"
+#include "lldb/API/SBReproducer.h"
#include "lldb/API/SBStream.h"
#include "lldb/API/SBStringList.h"
@@ -888,8 +889,10 @@ main(int argc, char const *argv[])
<< '\n';
}
- SBInitializerOptions options;
+ // Remember if we're in replay mode for later.
+ bool replay = false;
+ SBInitializerOptions options;
if (auto *arg = input_args.getLastArg(OPT_capture)) {
auto arg_value = arg->getValue();
options.SetReproducerPath(arg_value);
@@ -900,6 +903,7 @@ main(int argc, char const *argv[])
auto arg_value = arg->getValue();
options.SetReplayReproducer(true);
options.SetReproducerPath(arg_value);
+ replay = true;
}
SBError error = SBDebugger::Initialize(options);
@@ -909,6 +913,14 @@ main(int argc, char const *argv[])
return 1;
}
+ if (replay) {
+ SBReproducer reproducer;
+ if (!reproducer.Replay()) {
+ WithColor::error() << "something went wrong running the reporducer.\n";
+ }
+ return 0;
+ }
+
SBHostOS::ThreadCreated("<lldb.driver.main-thread>");
signal(SIGINT, sigint_handler);
diff --git a/lldb/unittests/Utility/ReproducerInstrumentationTest.cpp b/lldb/unittests/Utility/ReproducerInstrumentationTest.cpp
index 65ee425892f..ec708f6e6ca 100644
--- a/lldb/unittests/Utility/ReproducerInstrumentationTest.cpp
+++ b/lldb/unittests/Utility/ReproducerInstrumentationTest.cpp
@@ -9,12 +9,14 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include <cmath>
+#include <limits>
+
#include "lldb/Utility/ReproducerInstrumentation.h"
using namespace lldb_private;
using namespace lldb_private::repro;
-namespace {
struct Foo {
int m = 1;
};
@@ -40,7 +42,205 @@ struct Pod {
unsigned long l = 8;
unsigned short m = 9;
};
-} // namespace
+
+class TestingRegistry : public Registry {
+public:
+ TestingRegistry();
+};
+
+static llvm::Optional<Serializer> g_serializer;
+static llvm::Optional<TestingRegistry> g_registry;
+
+#define LLDB_GET_INSTRUMENTATION_DATA() \
+ InstrumentationData(*g_serializer, *g_registry)
+
+class InstrumentedFoo {
+public:
+ InstrumentedFoo() = default;
+ /// Instrumented methods.
+ /// {
+ InstrumentedFoo(int i);
+ InstrumentedFoo(const InstrumentedFoo &foo);
+ InstrumentedFoo &operator=(const InstrumentedFoo &foo);
+ void A(int a);
+ void B(int &b) const;
+ int C(float *c);
+ int D(const char *d) const;
+ static void E(double e);
+ static int F();
+ void Validate();
+ //// }
+
+private:
+ int m_a = 0;
+ mutable int m_b = 0;
+ float m_c = 0;
+ mutable std::string m_d = {};
+ static double g_e;
+ static bool g_f;
+ mutable int m_called = 0;
+};
+
+class InstrumentedBar {
+public:
+ /// Instrumented methods.
+ /// {
+ InstrumentedBar();
+ InstrumentedFoo GetInstrumentedFoo();
+ void SetInstrumentedFoo(InstrumentedFoo *foo);
+ void SetInstrumentedFoo(InstrumentedFoo &foo);
+ void Validate();
+ /// }
+
+private:
+ bool m_get_instrumend_foo_called = false;
+ InstrumentedFoo *m_foo_set_by_ptr = nullptr;
+ InstrumentedFoo *m_foo_set_by_ref = nullptr;
+};
+
+double InstrumentedFoo::g_e = 0;
+bool InstrumentedFoo::g_f = false;
+
+static std::vector<InstrumentedFoo *> g_foos;
+static std::vector<InstrumentedBar *> g_bars;
+
+void ClearObjects() {
+ g_foos.clear();
+ g_bars.clear();
+}
+
+void ValidateObjects(size_t expected_foos, size_t expected_bars) {
+ EXPECT_EQ(expected_foos, g_foos.size());
+ EXPECT_EQ(expected_bars, g_bars.size());
+
+ for (auto *foo : g_foos) {
+ foo->Validate();
+ }
+
+ for (auto *bar : g_bars) {
+ bar->Validate();
+ }
+}
+
+InstrumentedFoo::InstrumentedFoo(int i) {
+ LLDB_RECORD_CONSTRUCTOR(InstrumentedFoo, (int), i);
+ g_foos.push_back(this);
+}
+
+InstrumentedFoo::InstrumentedFoo(const InstrumentedFoo &foo) {
+ LLDB_RECORD_CONSTRUCTOR(InstrumentedFoo, (const InstrumentedFoo &), foo);
+ g_foos.erase(std::remove(g_foos.begin(), g_foos.end(), &foo));
+ g_foos.push_back(this);
+}
+
+InstrumentedFoo &InstrumentedFoo::operator=(const InstrumentedFoo &foo) {
+ LLDB_RECORD_METHOD(InstrumentedFoo &,
+ InstrumentedFoo, operator=,(const InstrumentedFoo &), foo);
+ g_foos.erase(std::remove(g_foos.begin(), g_foos.end(), &foo));
+ g_foos.push_back(this);
+ return *this;
+}
+
+void InstrumentedFoo::A(int a) {
+ LLDB_RECORD_METHOD(void, InstrumentedFoo, A, (int), a);
+ B(a);
+ m_a = a;
+}
+
+void InstrumentedFoo::B(int &b) const {
+ LLDB_RECORD_METHOD_CONST(void, InstrumentedFoo, B, (int &), b);
+ m_called++;
+ m_b = b;
+}
+
+int InstrumentedFoo::C(float *c) {
+ LLDB_RECORD_METHOD(int, InstrumentedFoo, C, (float *), c);
+ m_c = *c;
+ return 1;
+}
+
+int InstrumentedFoo::D(const char *d) const {
+ LLDB_RECORD_METHOD_CONST(int, InstrumentedFoo, D, (const char *), d);
+ m_d = std::string(d);
+ return 2;
+}
+
+void InstrumentedFoo::E(double e) {
+ LLDB_RECORD_STATIC_METHOD(void, InstrumentedFoo, E, (double), e);
+ g_e = e;
+}
+
+int InstrumentedFoo::F() {
+ LLDB_RECORD_STATIC_METHOD_NO_ARGS(int, InstrumentedFoo, F);
+ g_f = true;
+ return 3;
+}
+
+void InstrumentedFoo::Validate() {
+ LLDB_RECORD_METHOD_NO_ARGS(void, InstrumentedFoo, Validate);
+ EXPECT_EQ(m_a, 100);
+ EXPECT_EQ(m_b, 200);
+ EXPECT_NEAR(m_c, 300.3, 0.01);
+ EXPECT_EQ(m_d, "bar");
+ EXPECT_NEAR(g_e, 400.4, 0.01);
+ EXPECT_EQ(g_f, true);
+ EXPECT_EQ(2, m_called);
+}
+
+InstrumentedBar::InstrumentedBar() {
+ LLDB_RECORD_CONSTRUCTOR_NO_ARGS(InstrumentedBar);
+ g_bars.push_back(this);
+}
+
+InstrumentedFoo InstrumentedBar::GetInstrumentedFoo() {
+ LLDB_RECORD_METHOD_NO_ARGS(InstrumentedFoo, InstrumentedBar,
+ GetInstrumentedFoo);
+ m_get_instrumend_foo_called = true;
+ return LLDB_RECORD_RESULT(InstrumentedFoo(0));
+}
+
+void InstrumentedBar::SetInstrumentedFoo(InstrumentedFoo *foo) {
+ LLDB_RECORD_METHOD(void, InstrumentedBar, SetInstrumentedFoo,
+ (InstrumentedFoo *), foo);
+ m_foo_set_by_ptr = foo;
+}
+
+void InstrumentedBar::SetInstrumentedFoo(InstrumentedFoo &foo) {
+ LLDB_RECORD_METHOD(void, InstrumentedBar, SetInstrumentedFoo,
+ (InstrumentedFoo &), foo);
+ m_foo_set_by_ref = &foo;
+}
+
+void InstrumentedBar::Validate() {
+ LLDB_RECORD_METHOD_NO_ARGS(void, InstrumentedBar, Validate);
+
+ EXPECT_TRUE(m_get_instrumend_foo_called);
+ EXPECT_NE(m_foo_set_by_ptr, nullptr);
+ EXPECT_EQ(m_foo_set_by_ptr, m_foo_set_by_ref);
+}
+
+TestingRegistry::TestingRegistry() {
+ LLDB_REGISTER_CONSTRUCTOR(InstrumentedFoo, (int i));
+ LLDB_REGISTER_CONSTRUCTOR(InstrumentedFoo, (const InstrumentedFoo &));
+ LLDB_REGISTER_METHOD(InstrumentedFoo &,
+ InstrumentedFoo, operator=,(const InstrumentedFoo &));
+ LLDB_REGISTER_METHOD(void, InstrumentedFoo, A, (int));
+ LLDB_REGISTER_METHOD_CONST(void, InstrumentedFoo, B, (int &));
+ LLDB_REGISTER_METHOD(int, InstrumentedFoo, C, (float *));
+ LLDB_REGISTER_METHOD_CONST(int, InstrumentedFoo, D, (const char *));
+ LLDB_REGISTER_STATIC_METHOD(void, InstrumentedFoo, E, (double));
+ LLDB_REGISTER_STATIC_METHOD(int, InstrumentedFoo, F, ());
+ LLDB_REGISTER_METHOD(void, InstrumentedFoo, Validate, ());
+
+ LLDB_REGISTER_CONSTRUCTOR(InstrumentedBar, ());
+ LLDB_REGISTER_METHOD(InstrumentedFoo, InstrumentedBar, GetInstrumentedFoo,
+ ());
+ LLDB_REGISTER_METHOD(void, InstrumentedBar, SetInstrumentedFoo,
+ (InstrumentedFoo *));
+ LLDB_REGISTER_METHOD(void, InstrumentedBar, SetInstrumentedFoo,
+ (InstrumentedFoo &));
+ LLDB_REGISTER_METHOD(void, InstrumentedBar, Validate, ());
+}
static const Pod p;
@@ -206,3 +406,105 @@ TEST(SerializationRountripTest, SerializeDeserializeObjectReference) {
EXPECT_EQ(foo, deserializer.Deserialize<Foo &>());
EXPECT_EQ(bar, deserializer.Deserialize<Bar &>());
}
+
+TEST(RecordReplayTest, InstrumentedFoo) {
+ std::string str;
+ llvm::raw_string_ostream os(str);
+ g_registry.emplace();
+ g_serializer.emplace(os);
+
+ {
+ int b = 200;
+ float c = 300.3;
+ double e = 400.4;
+
+ InstrumentedFoo foo(0);
+ foo.A(100);
+ foo.B(b);
+ foo.C(&c);
+ foo.D("bar");
+ InstrumentedFoo::E(e);
+ InstrumentedFoo::F();
+ foo.Validate();
+ }
+
+ ClearObjects();
+
+ TestingRegistry registry;
+ registry.Replay(os.str());
+
+ ValidateObjects(1, 0);
+}
+
+TEST(RecordReplayTest, InstrumentedFooSameThis) {
+ std::string str;
+ llvm::raw_string_ostream os(str);
+ g_registry.emplace();
+ g_serializer.emplace(os);
+
+ int b = 200;
+ float c = 300.3;
+ double e = 400.4;
+
+ InstrumentedFoo *foo = new InstrumentedFoo(0);
+ foo->A(100);
+ foo->B(b);
+ foo->C(&c);
+ foo->D("bar");
+ InstrumentedFoo::E(e);
+ InstrumentedFoo::F();
+ foo->Validate();
+ foo->~InstrumentedFoo();
+
+ InstrumentedFoo *foo2 = new (foo) InstrumentedFoo(0);
+ foo2->A(100);
+ foo2->B(b);
+ foo2->C(&c);
+ foo2->D("bar");
+ InstrumentedFoo::E(e);
+ InstrumentedFoo::F();
+ foo2->Validate();
+ delete foo2;
+
+ ClearObjects();
+
+ TestingRegistry registry;
+ registry.Replay(os.str());
+
+ ValidateObjects(2, 0);
+}
+
+TEST(RecordReplayTest, InstrumentedBar) {
+ std::string str;
+ llvm::raw_string_ostream os(str);
+ g_registry.emplace();
+ g_serializer.emplace(os);
+
+ {
+ InstrumentedBar bar;
+ InstrumentedFoo foo = bar.GetInstrumentedFoo();
+
+ int b = 200;
+ float c = 300.3;
+ double e = 400.4;
+
+ foo.A(100);
+ foo.B(b);
+ foo.C(&c);
+ foo.D("bar");
+ InstrumentedFoo::E(e);
+ InstrumentedFoo::F();
+ foo.Validate();
+
+ bar.SetInstrumentedFoo(foo);
+ bar.SetInstrumentedFoo(&foo);
+ bar.Validate();
+ }
+
+ ClearObjects();
+
+ TestingRegistry registry;
+ registry.Replay(os.str());
+
+ ValidateObjects(1, 1);
+}
OpenPOWER on IntegriCloud