Index: lldb/trunk/include/lldb/API/SBReproducer.h =================================================================== --- lldb/trunk/include/lldb/API/SBReproducer.h +++ lldb/trunk/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 Index: lldb/trunk/include/lldb/Utility/ReproducerInstrumentation.h =================================================================== --- lldb/trunk/include/lldb/Utility/ReproducerInstrumentation.h +++ lldb/trunk/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 -#include + +#define LLDB_REGISTER_CONSTRUCTOR(Class, Signature) \ + Register(&construct::doit) +#define LLDB_REGISTER_METHOD(Result, Class, Method, Signature) \ + Register(&invoke::method<&Class::Method>::doit) +#define LLDB_REGISTER_METHOD_CONST(Result, Class, Method, Signature) \ + Register(&invoke::method_const<&Class::Method>::doit) +#define LLDB_REGISTER_STATIC_METHOD(Result, Class, Method, Signature) \ + Register(static_cast(&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::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::doit); \ + sb_recorder.RecordResult(this); \ + } + +#define LLDB_RECORD_METHOD(Result, Class, Method, Signature, ...) \ + llvm::Optional 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::method<&Class::Method>::doit, \ + this, __VA_ARGS__); \ + } + +#define LLDB_RECORD_METHOD_CONST(Result, Class, Method, Signature, ...) \ + llvm::Optional 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::method_const<&Class::Method>::doit, \ + this, __VA_ARGS__); \ + } + +#define LLDB_RECORD_METHOD_NO_ARGS(Result, Class, Method) \ + llvm::Optional 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::method<&Class::Method>::doit, \ + this); \ + } + +#define LLDB_RECORD_METHOD_CONST_NO_ARGS(Result, Class, Method) \ + llvm::Optional 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::method_const<&Class::Method>::doit, \ + this); \ + } + +#define LLDB_RECORD_STATIC_METHOD(Result, Class, Method, Signature, ...) \ + llvm::Optional 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(&Class::Method), \ + __VA_ARGS__); \ + } + +#define LLDB_RECORD_STATIC_METHOD_NO_ARGS(Result, Class, Method) \ + llvm::Optional 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(&Class::Method)); \ + } + +#define LLDB_RECORD_RESULT(Result) \ + sb_recorder ? sb_recorder->RecordResult(Result) : Result; namespace lldb_private { namespace repro { @@ -139,9 +235,6 @@ assert(result == 0); } -protected: - IndexToObject &GetIndexToObject() { return m_index_to_object; } - private: template T Read(ValueTag) { assert(HasData(sizeof(T))); @@ -222,6 +315,114 @@ }; }; +/// 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 struct DefaultReplayer; +template +struct DefaultReplayer : public Replayer { + DefaultReplayer(Result (*f)(Args...)) : Replayer(), f(f) {} + + void operator()(Deserializer &deserializer) const override { + deserializer.HandleReplayResult( + DeserializationHelper::template deserialized::doit( + deserializer, f)); + } + + Result (*f)(Args...); +}; + +/// Partial specialization for function returning a void type. It ignores the +/// (absent) return value. +template +struct DefaultReplayer : public Replayer { + DefaultReplayer(void (*f)(Args...)) : Replayer(), f(f) {} + + void operator()(Deserializer &deserializer) const override { + DeserializationHelper::template deserialized::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 void Register(Signature *f) { + DoRegister(uintptr_t(f), llvm::make_unique>(f)); + } + + /// Register a replayer that invokes a custom function with the same + /// signature as the replayed function. + template void Register(Signature *f, Signature *g) { + DoRegister(uintptr_t(f), llvm::make_unique>(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); + +private: + /// Mapping of function addresses to replayers and their ID. + std::map, unsigned>> + m_replayers; + + /// Mapping of IDs to replayer instances. + std::map m_ids; +}; + +/// To be used as the "Runtime ID" of a constructor. It also invokes the +/// constructor when called. +template struct construct; +template struct construct { + 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 struct invoke; +template +struct invoke { + template struct method { + static Result doit(Class *c, Args... args) { return (c->*m)(args...); } + }; +}; + +template +struct invoke { + template struct method_const { + static Result doit(Class *c, Args... args) { return (c->*m)(args...); } + }; +}; + +template +struct invoke { + template 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 @@ private: unsigned GetIndexForObjectImpl(void *object); - std::mutex m_mutex; llvm::DenseMap m_mapping; }; @@ -296,6 +496,114 @@ ObjectToIndex m_tracker; }; +class InstrumentationData { +public: + InstrumentationData() : m_serializer(nullptr), m_registry(nullptr){}; + InstrumentationData(Serializer &serializer, Registry ®istry) + : m_serializer(&serializer), m_registry(®istry){}; + + 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 ®istry, + llvm::StringRef pretty_func = {}); + ~Recorder(); + + /// Records a single function call. + template + 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::type>::type>::value) { + m_result_recorded = false; + } else { + m_serializer.SerializeAll(0); + m_result_recorded = true; + } + } + + /// Records a single function call. + template + 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 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 Index: lldb/trunk/source/API/CMakeLists.txt =================================================================== --- lldb/trunk/source/API/CMakeLists.txt +++ lldb/trunk/source/API/CMakeLists.txt @@ -50,6 +50,7 @@ SBProcessInfo.cpp SBQueue.cpp SBQueueItem.cpp + SBReproducer.cpp SBSection.cpp SBSourceManager.cpp SBStream.cpp Index: lldb/trunk/source/API/SBReproducer.cpp =================================================================== --- lldb/trunk/source/API/SBReproducer.cpp +++ lldb/trunk/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(); + 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"; Index: lldb/trunk/source/API/SBReproducerPrivate.h =================================================================== --- lldb/trunk/source/API/SBReproducerPrivate.h +++ lldb/trunk/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 { +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(); + return {p.GetSerializer(), p.GetRegistry()}; + } + return {}; +} + +} // namespace repro +} // namespace lldb_private + +#endif Index: lldb/trunk/source/Utility/ReproducerInstrumentation.cpp =================================================================== --- lldb/trunk/source/Utility/ReproducerInstrumentation.cpp +++ lldb/trunk/source/Utility/ReproducerInstrumentation.cpp @@ -34,11 +34,62 @@ 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(); + LLDB_LOG(log, "Replaying function #{0}", id); + m_ids[id]->operator()(deserializer); + } + + return true; +} + +void Registry::DoRegister(uintptr_t RunID, std::unique_ptr 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 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 ®istry, + 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; Index: lldb/trunk/tools/driver/Driver.cpp =================================================================== --- lldb/trunk/tools/driver/Driver.cpp +++ lldb/trunk/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 @@ << '\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 @@ auto arg_value = arg->getValue(); options.SetReplayReproducer(true); options.SetReproducerPath(arg_value); + replay = true; } SBError error = SBDebugger::Initialize(options); @@ -909,6 +913,14 @@ return 1; } + if (replay) { + SBReproducer reproducer; + if (!reproducer.Replay()) { + WithColor::error() << "something went wrong running the reporducer.\n"; + } + return 0; + } + SBHostOS::ThreadCreated(""); signal(SIGINT, sigint_handler); Index: lldb/trunk/unittests/Utility/ReproducerInstrumentationTest.cpp =================================================================== --- lldb/trunk/unittests/Utility/ReproducerInstrumentationTest.cpp +++ lldb/trunk/unittests/Utility/ReproducerInstrumentationTest.cpp @@ -9,12 +9,14 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include +#include + #include "lldb/Utility/ReproducerInstrumentation.h" using namespace lldb_private; using namespace lldb_private::repro; -namespace { struct Foo { int m = 1; }; @@ -40,7 +42,205 @@ unsigned long l = 8; unsigned short m = 9; }; -} // namespace + +class TestingRegistry : public Registry { +public: + TestingRegistry(); +}; + +static llvm::Optional g_serializer; +static llvm::Optional 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 g_foos; +static std::vector 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 @@ EXPECT_EQ(foo, deserializer.Deserialize()); EXPECT_EQ(bar, deserializer.Deserialize()); } + +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); +}