Index: include/lldb/API/SBReproducer.h =================================================================== --- /dev/null +++ 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: include/lldb/Utility/ReproducerInstrumentation.h =================================================================== --- include/lldb/Utility/ReproducerInstrumentation.h +++ 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,6 @@ #include "llvm/Support/ErrorHandling.h" #include -#include namespace lldb_private { namespace repro { @@ -222,6 +220,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,14 +342,13 @@ private: unsigned GetIndexForObjectImpl(void *object); - std::mutex m_mutex; llvm::DenseMap 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) {} + Serializer(llvm::raw_ostream &stream = llvm::outs()) : m_stream(&stream) {} /// Recursively serialize all the given arguments. template @@ -255,6 +360,7 @@ void SerializeAll() {} private: + llvm::raw_ostream &os() { return *m_stream; } /// 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). @@ -272,7 +378,7 @@ /// to objects (in which case we serialize their index). template void Serialize(T &t) { if (std::is_fundamental::value) { - m_stream.write(reinterpret_cast(&t), sizeof(T)); + os().write(reinterpret_cast(&t), sizeof(T)); } else { unsigned idx = m_tracker.GetIndexForObject(&t); Serialize(idx); @@ -285,17 +391,113 @@ } void Serialize(const char *t) { - m_stream << t; - m_stream.write(0x0); + os() << t; + os().write(0x0); } /// Serialization stream. - llvm::raw_ostream &m_stream; + llvm::raw_ostream *m_stream; /// Mapping of objects to indices. ObjectToIndex m_tracker; }; +/// 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; } + + /// Pretty function for logging. + llvm::StringRef m_pretty_func; + + /// The serializer is set from the reproducer framework. If the serializer is + /// not set, we're not in recording mode. + Serializer &m_serializer; + + // The registry with a mapping between function addresses and indices. + Registry &m_registry; + + /// 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: source/API/CMakeLists.txt =================================================================== --- source/API/CMakeLists.txt +++ source/API/CMakeLists.txt @@ -50,6 +50,7 @@ SBProcessInfo.cpp SBQueue.cpp SBQueueItem.cpp + SBReproducer.cpp SBSection.cpp SBSourceManager.cpp SBStream.cpp Index: source/API/SBReproducer.cpp =================================================================== --- /dev/null +++ source/API/SBReproducer.cpp @@ -0,0 +1,61 @@ +//===-- 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; + +#define SB_REGISTER_CONSTRUCTOR(Class, Signature) \ + Register(&construct::doit) +#define SB_REGISTER_METHOD(Result, Class, Method, Signature) \ + Register(&invoke::method<&Class::Method>::doit) +#define SB_REGISTER_METHOD_CONST(Result, Class, Method, Signature) \ + Register(&invoke::method_const<&Class::Method>::doit) +#define SB_REGISTER_STATIC_METHOD(Result, Class, Method, Signature) \ + Register(static_cast(&Class::Method)) + +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: source/API/SBReproducerPrivate.h =================================================================== --- /dev/null +++ source/API/SBReproducerPrivate.h @@ -0,0 +1,151 @@ +//===-- 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" + +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; +}; + +} // namespace repro +} // namespace lldb_private + +#define SB_RECORD_CONSTRUCTOR(Class, Signature, ...) \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + lldb_private::repro::Recorder sb_recorder( \ + g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder.Record(&lldb_private::repro::construct::doit, \ + __VA_ARGS__); \ + sb_recorder.RecordResult(this); \ + } + +#define SB_RECORD_CONSTRUCTOR_NO_ARGS(Class) \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + lldb_private::repro::Recorder sb_recorder( \ + g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder.Record(&lldb_private::repro::construct::doit); \ + sb_recorder.RecordResult(this); \ + } + +#define SB_RECORD_METHOD(Result, Class, Method, Signature, ...) \ + llvm::Optional sb_recorder; \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + sb_recorder.emplace(g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record( \ + &lldb_private::repro::invoke::method<&Class::Method>::doit, \ + this, __VA_ARGS__); \ + } + +#define SB_RECORD_METHOD_CONST(Result, Class, Method, Signature, ...) \ + llvm::Optional sb_recorder; \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + sb_recorder.emplace(g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record( \ + &lldb_private::repro::invoke::method_const<&Class::Method>::doit, \ + this, __VA_ARGS__); \ + } + +#define SB_RECORD_METHOD_NO_ARGS(Result, Class, Method) \ + llvm::Optional sb_recorder; \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + sb_recorder.emplace(g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record(&lldb_private::repro::invoke::method<&Class::Method>::doit, \ + this); \ + } + +#define SB_RECORD_METHOD_CONST_NO_ARGS(Result, Class, Method) \ + llvm::Optional sb_recorder; \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + sb_recorder.emplace(g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record( \ + &lldb_private::repro::invoke::method_const<&Class::Method>::doit, \ + this); \ + } + +#define SB_RECORD_STATIC_METHOD(Result, Class, Method, Signature, ...) \ + llvm::Optional sb_recorder; \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + sb_recorder.emplace(g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record(static_cast(&Class::Method), \ + __VA_ARGS__); \ + } + +#define SB_RECORD_STATIC_METHOD_NO_ARGS(Result, Class, Method) \ + llvm::Optional sb_recorder; \ + if (auto *g = lldb_private::repro::Reproducer::Instance().GetGenerator()) { \ + sb_recorder.emplace(g->GetOrCreate().GetSerializer(), \ + g->GetOrCreate().GetRegistry(), \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record(static_cast(&Class::Method)); \ + } + +#define SB_RECORD_RESULT(Result) \ + sb_recorder ? sb_recorder->RecordResult(Result) : Result; + +#endif Index: source/Utility/ReproducerInstrumentation.cpp =================================================================== --- source/Utility/ReproducerInstrumentation.cpp +++ source/Utility/ReproducerInstrumentation.cpp @@ -34,11 +34,61 @@ 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_pretty_func(pretty_func), m_serializer(serializer), + m_registry(registry), 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 SB_RECORD_RESULT?"); + UpdateBoundary(); +} + +bool lldb_private::repro::Recorder::g_global_boundary; Index: tools/driver/Driver.cpp =================================================================== --- tools/driver/Driver.cpp +++ 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: unittests/Utility/ReproducerInstrumentationTest.cpp =================================================================== --- unittests/Utility/ReproducerInstrumentationTest.cpp +++ unittests/Utility/ReproducerInstrumentationTest.cpp @@ -9,12 +9,24 @@ #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 { +#define SB_REGISTER_CONSTRUCTOR(Class, Signature) \ + Register(&construct::doit) +#define SB_REGISTER_METHOD(Result, Class, Method, Signature) \ + Register(&invoke::method<&Class::Method>::doit) +#define SB_REGISTER_METHOD_CONST(Result, Class, Method, Signature) \ + Register(&invoke::method_const<&Class::Method>::doit) +#define SB_REGISTER_STATIC_METHOD(Result, Class, Method, Signature) \ + Register(static_cast(&Class::Method)) + struct Foo { int m = 1; }; @@ -40,7 +52,166 @@ unsigned long l = 8; unsigned short m = 9; }; -} // namespace +class TestingRegistry : public Registry { +public: + TestingRegistry(); +}; + +template bool Equals(T LHS, T RHS) { + return std::fabs(RHS - LHS) < std::numeric_limits::epsilon(); +} + +static Serializer g_serializer; +static TestingRegistry g_registry; + +#define SB_RECORD_CONSTRUCTOR(Class, Signature, ...) \ + lldb_private::repro::Recorder sb_recorder(g_serializer, g_registry, \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder.Record(&lldb_private::repro::construct::doit, \ + __VA_ARGS__); \ + sb_recorder.RecordResult(this); + +#define SB_RECORD_CONSTRUCTOR_NO_ARGS(Class) \ + lldb_private::repro::Recorder sb_recorder(g_serializer, g_registry, \ + LLVM_PRETTY_FUNCTION); \ + sb_recorder.Record(&lldb_private::repro::construct::doit); \ + sb_recorder.RecordResult(this); + +#define SB_RECORD_METHOD(Result, Class, Method, Signature, ...) \ + llvm::Optional sb_recorder; \ + sb_recorder.emplace(g_serializer, g_registry, LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record(&lldb_private::repro::invoke::method<&Class::Method>::doit, \ + this, __VA_ARGS__); + +#define SB_RECORD_METHOD_CONST(Result, Class, Method, Signature, ...) \ + llvm::Optional sb_recorder; \ + sb_recorder.emplace(g_serializer, g_registry, LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record( \ + &lldb_private::repro::invoke::method_const<&Class::Method>::doit, \ + this, __VA_ARGS__); + +#define SB_RECORD_METHOD_NO_ARGS(Result, Class, Method) \ + llvm::Optional sb_recorder; \ + sb_recorder.emplace(g_serializer, g_registry, LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record(&lldb_private::repro::invoke::method<&Class::Method>::doit, \ + this); + +#define SB_RECORD_METHOD_CONST_NO_ARGS(Result, Class, Method) \ + llvm::Optional sb_recorder; \ + sb_recorder.emplace(g_serializer, g_registry, LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record( \ + &lldb_private::repro::invoke::method_const<&Class::Method>::doit, \ + this); + +#define SB_RECORD_STATIC_METHOD(Result, Class, Method, Signature, ...) \ + llvm::Optional sb_recorder; \ + sb_recorder.emplace(g_serializer, g_registry, LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record(static_cast(&Class::Method), \ + __VA_ARGS__); + +#define SB_RECORD_STATIC_METHOD_NO_ARGS(Result, Class, Method) \ + llvm::Optional sb_recorder; \ + sb_recorder.emplace(g_serializer, g_registry, LLVM_PRETTY_FUNCTION); \ + sb_recorder->Record(static_cast(&Class::Method)); + +#define SB_RECORD_RESULT(Result) \ + sb_recorder ? sb_recorder->RecordResult(Result) : Result; + +class InstrumentedFoo { +public: + InstrumentedFoo(); + 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(); + + // No instrumented. + static void Reset() { + g_a = 0; + g_b = 0; + g_c = 0; + g_d = ""; + g_e = 0; + g_f = false; + + EXPECT_EQ(g_a, 0); + EXPECT_EQ(g_b, 0); + EXPECT_EQ(g_c, 0); + EXPECT_EQ(g_d, ""); + EXPECT_EQ(g_e, 0); + EXPECT_EQ(g_f, false); + } + + // Members are all static because we have no handle into the replayed object. + static int g_a; + static int g_b; + static float g_c; + static std::string g_d; + static double g_e; + static bool g_f; +}; + +int InstrumentedFoo::g_a = 0; +int InstrumentedFoo::g_b = 0; +float InstrumentedFoo::g_c = 0; +std::string InstrumentedFoo::g_d = ""; +double InstrumentedFoo::g_e = 0; +bool InstrumentedFoo::g_f = false; + +InstrumentedFoo::InstrumentedFoo() { + SB_RECORD_CONSTRUCTOR_NO_ARGS(InstrumentedFoo); +} + +void InstrumentedFoo::A(int a) { + SB_RECORD_METHOD(void, InstrumentedFoo, A, (int), a); + g_a = a; +} + +void InstrumentedFoo::B(int &b) const { + SB_RECORD_METHOD_CONST(void, InstrumentedFoo, B, (int &), b); + g_b = b; +} + +int InstrumentedFoo::C(float *c) { + SB_RECORD_METHOD(int, InstrumentedFoo, C, (float *), c); + g_c = *c; + return 1; +} + +int InstrumentedFoo::D(const char *d) const { + SB_RECORD_METHOD_CONST(int, InstrumentedFoo, D, (const char *), d); + g_d = std::string(d); + return 2; +} + +void InstrumentedFoo::E(double e) { + SB_RECORD_STATIC_METHOD(void, InstrumentedFoo, E, (double), e); + g_e = e; +} + +int InstrumentedFoo::F() { + SB_RECORD_STATIC_METHOD_NO_ARGS(int, InstrumentedFoo, F); + g_f = true; + return 3; +} + +class InstrumentedBar {}; + +TestingRegistry::TestingRegistry() { + SB_REGISTER_CONSTRUCTOR(InstrumentedFoo, ()); + SB_REGISTER_METHOD(void, InstrumentedFoo, A, (int)); + SB_REGISTER_METHOD_CONST(void, InstrumentedFoo, B, (int &)); + SB_REGISTER_METHOD(int, InstrumentedFoo, C, (float *)); + SB_REGISTER_METHOD_CONST(int, InstrumentedFoo, D, (const char *)); + SB_REGISTER_STATIC_METHOD(void, InstrumentedFoo, E, (double)); + SB_REGISTER_STATIC_METHOD(int, InstrumentedFoo, F, ()); +} static const Pod p; @@ -206,3 +377,41 @@ EXPECT_EQ(foo, deserializer.Deserialize()); EXPECT_EQ(bar, deserializer.Deserialize()); } + +TEST(RecordReplayTest, InstrumentedFoo) { + std::string str; + llvm::raw_string_ostream os(str); + g_serializer = Serializer(os); + + int b = 200; + float c = 300.3; + double e = 400.4; + + InstrumentedFoo foo; + foo.A(100); + foo.B(b); + foo.C(&c); + foo.D("bar"); + + InstrumentedFoo::E(e); + InstrumentedFoo::F(); + + EXPECT_EQ(foo.g_a, 100); + EXPECT_EQ(foo.g_b, 200); + EXPECT_TRUE(Equals(foo.g_c, c)); + EXPECT_EQ(foo.g_d, "bar"); + EXPECT_TRUE(Equals(foo.g_e, e)); + EXPECT_EQ(foo.g_f, true); + + InstrumentedFoo::Reset(); + + TestingRegistry registry; + registry.Replay(os.str()); + + EXPECT_EQ(foo.g_a, 100); + EXPECT_EQ(foo.g_b, 200); + EXPECT_TRUE(Equals(foo.g_c, c)); + EXPECT_EQ(foo.g_d, "bar"); + EXPECT_TRUE(Equals(foo.g_e, e)); + EXPECT_EQ(foo.g_f, true); +}