Skip to content

Commit 297f69a

Browse files
committed
[lldb] Add reproducer verifier
Add a reproducer verifier that catches: - Missing or invalid home directory - Missing or invalid working directory - Missing or invalid module/symbol paths - Missing files from the VFS The verifier is enabled by default during replay, but can be skipped by passing --reproducer-no-verify. Differential revision: https://reviews.llvm.org/D86497
1 parent 867de15 commit 297f69a

File tree

13 files changed

+388
-32
lines changed

13 files changed

+388
-32
lines changed

lldb/include/lldb/API/SBReproducer.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,32 @@
1111

1212
#include "lldb/API/SBDefines.h"
1313

14+
namespace lldb_private {
15+
namespace repro {
16+
struct ReplayOptions;
17+
}
18+
} // namespace lldb_private
19+
1420
namespace lldb {
1521

22+
class LLDB_API SBReplayOptions {
23+
public:
24+
SBReplayOptions();
25+
SBReplayOptions(const SBReplayOptions &rhs);
26+
~SBReplayOptions();
27+
28+
SBReplayOptions &operator=(const SBReplayOptions &rhs);
29+
30+
void SetVerify(bool verify);
31+
bool GetVerify() const;
32+
33+
void SetCheckVersion(bool check);
34+
bool GetCheckVersion() const;
35+
36+
private:
37+
std::unique_ptr<lldb_private::repro::ReplayOptions> m_opaque_up;
38+
};
39+
1640
/// The SBReproducer class is special because it bootstraps the capture and
1741
/// replay of SB API calls. As a result we cannot rely on any other SB objects
1842
/// in the interface or implementation of this class.
@@ -22,6 +46,7 @@ class LLDB_API SBReproducer {
2246
static const char *Capture(const char *path);
2347
static const char *Replay(const char *path);
2448
static const char *Replay(const char *path, bool skip_version_check);
49+
static const char *Replay(const char *path, const SBReplayOptions &options);
2550
static const char *PassiveReplay(const char *path);
2651
static const char *GetPath();
2752
static bool SetAutoGenerate(bool b);

lldb/include/lldb/Utility/Reproducer.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,22 @@ class Reproducer {
227227
mutable std::mutex m_mutex;
228228
};
229229

230+
class Verifier {
231+
public:
232+
Verifier(Loader *loader) : m_loader(loader) {}
233+
void Verify(llvm::function_ref<void(llvm::StringRef)> error_callback,
234+
llvm::function_ref<void(llvm::StringRef)> warning_callback,
235+
llvm::function_ref<void(llvm::StringRef)> note_callback) const;
236+
237+
private:
238+
Loader *m_loader;
239+
};
240+
241+
struct ReplayOptions {
242+
bool verify = true;
243+
bool check_version = true;
244+
};
245+
230246
} // namespace repro
231247
} // namespace lldb_private
232248

lldb/source/API/SBReproducer.cpp

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,33 @@ using namespace lldb;
3030
using namespace lldb_private;
3131
using namespace lldb_private::repro;
3232

33+
SBReplayOptions::SBReplayOptions()
34+
: m_opaque_up(std::make_unique<ReplayOptions>()){};
35+
36+
SBReplayOptions::SBReplayOptions(const SBReplayOptions &rhs)
37+
: m_opaque_up(std::make_unique<ReplayOptions>(*rhs.m_opaque_up)) {}
38+
39+
SBReplayOptions::~SBReplayOptions() = default;
40+
41+
SBReplayOptions &SBReplayOptions::operator=(const SBReplayOptions &rhs) {
42+
if (this == &rhs)
43+
return *this;
44+
*m_opaque_up = *rhs.m_opaque_up;
45+
return *this;
46+
}
47+
48+
void SBReplayOptions::SetVerify(bool verify) { m_opaque_up->verify = verify; }
49+
50+
bool SBReplayOptions::GetVerify() const { return m_opaque_up->verify; }
51+
52+
void SBReplayOptions::SetCheckVersion(bool check) {
53+
m_opaque_up->check_version = check;
54+
}
55+
56+
bool SBReplayOptions::GetCheckVersion() const {
57+
return m_opaque_up->check_version;
58+
}
59+
3360
SBRegistry::SBRegistry() {
3461
Registry &R = *this;
3562

@@ -163,10 +190,18 @@ const char *SBReproducer::PassiveReplay(const char *path) {
163190
}
164191

165192
const char *SBReproducer::Replay(const char *path) {
166-
return SBReproducer::Replay(path, false);
193+
SBReplayOptions options;
194+
return SBReproducer::Replay(path, options);
167195
}
168196

169197
const char *SBReproducer::Replay(const char *path, bool skip_version_check) {
198+
SBReplayOptions options;
199+
options.SetCheckVersion(!skip_version_check);
200+
return SBReproducer::Replay(path, options);
201+
}
202+
203+
const char *SBReproducer::Replay(const char *path,
204+
const SBReplayOptions &options) {
170205
static std::string error;
171206
if (auto e = Reproducer::Initialize(ReproducerMode::Replay, FileSpec(path))) {
172207
error = llvm::toString(std::move(e));
@@ -179,7 +214,7 @@ const char *SBReproducer::Replay(const char *path, bool skip_version_check) {
179214
return error.c_str();
180215
}
181216

182-
if (!skip_version_check) {
217+
if (options.GetCheckVersion()) {
183218
llvm::Expected<std::string> version = loader->LoadBuffer<VersionProvider>();
184219
if (!version) {
185220
error = llvm::toString(version.takeError());
@@ -195,6 +230,30 @@ const char *SBReproducer::Replay(const char *path, bool skip_version_check) {
195230
}
196231
}
197232

233+
if (options.GetVerify()) {
234+
bool verification_failed = false;
235+
llvm::raw_string_ostream os(error);
236+
auto error_callback = [&](llvm::StringRef error) {
237+
verification_failed = true;
238+
os << "\nerror: " << error;
239+
};
240+
241+
auto warning_callback = [&](llvm::StringRef warning) {
242+
verification_failed = true;
243+
os << "\nwarning: " << warning;
244+
};
245+
246+
auto note_callback = [&](llvm::StringRef warning) {};
247+
248+
Verifier verifier(loader);
249+
verifier.Verify(error_callback, warning_callback, note_callback);
250+
251+
if (verification_failed) {
252+
os.flush();
253+
return error.c_str();
254+
}
255+
}
256+
198257
FileSpec file = loader->GetFile<SBProvider::Info>();
199258
if (!file) {
200259
error = "unable to get replay data from reproducer.";

lldb/source/Commands/CommandObjectReproducer.cpp

Lines changed: 136 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ static constexpr OptionEnumValues ReproducerSignalType() {
116116
#define LLDB_OPTIONS_reproducer_xcrash
117117
#include "CommandOptions.inc"
118118

119+
#define LLDB_OPTIONS_reproducer_verify
120+
#include "CommandOptions.inc"
121+
119122
template <typename T>
120123
llvm::Expected<T> static ReadFromYAML(StringRef filename) {
121124
auto error_or_file = MemoryBuffer::getFile(filename);
@@ -134,6 +137,38 @@ llvm::Expected<T> static ReadFromYAML(StringRef filename) {
134137
return t;
135138
}
136139

140+
static void SetError(CommandReturnObject &result, Error err) {
141+
result.GetErrorStream().Printf("error: %s\n",
142+
toString(std::move(err)).c_str());
143+
result.SetStatus(eReturnStatusFailed);
144+
}
145+
146+
/// Create a loader from the given path if specified. Otherwise use the current
147+
/// loader used for replay.
148+
static Loader *
149+
GetLoaderFromPathOrCurrent(llvm::Optional<Loader> &loader_storage,
150+
CommandReturnObject &result,
151+
FileSpec reproducer_path) {
152+
if (reproducer_path) {
153+
loader_storage.emplace(reproducer_path);
154+
Loader *loader = &(*loader_storage);
155+
if (Error err = loader->LoadIndex()) {
156+
// This is a hard error and will set the result to eReturnStatusFailed.
157+
SetError(result, std::move(err));
158+
return nullptr;
159+
}
160+
return loader;
161+
}
162+
163+
if (Loader *loader = Reproducer::Instance().GetLoader())
164+
return loader;
165+
166+
// This is a soft error because this is expected to fail during capture.
167+
result.SetError("Not specifying a reproducer is only support during replay.");
168+
result.SetStatus(eReturnStatusSuccessFinishNoResult);
169+
return nullptr;
170+
}
171+
137172
class CommandObjectReproducerGenerate : public CommandObjectParsed {
138173
public:
139174
CommandObjectReproducerGenerate(CommandInterpreter &interpreter)
@@ -312,12 +347,6 @@ class CommandObjectReproducerStatus : public CommandObjectParsed {
312347
}
313348
};
314349

315-
static void SetError(CommandReturnObject &result, Error err) {
316-
result.GetErrorStream().Printf("error: %s\n",
317-
toString(std::move(err)).c_str());
318-
result.SetStatus(eReturnStatusFailed);
319-
}
320-
321350
class CommandObjectReproducerDump : public CommandObjectParsed {
322351
public:
323352
CommandObjectReproducerDump(CommandInterpreter &interpreter)
@@ -382,29 +411,11 @@ class CommandObjectReproducerDump : public CommandObjectParsed {
382411
return false;
383412
}
384413

385-
// If no reproducer path is specified, use the loader currently used for
386-
// replay. Otherwise create a new loader just for dumping.
387414
llvm::Optional<Loader> loader_storage;
388-
Loader *loader = nullptr;
389-
if (!m_options.file) {
390-
loader = Reproducer::Instance().GetLoader();
391-
if (loader == nullptr) {
392-
result.SetError(
393-
"Not specifying a reproducer is only support during replay.");
394-
result.SetStatus(eReturnStatusSuccessFinishNoResult);
395-
return false;
396-
}
397-
} else {
398-
loader_storage.emplace(m_options.file);
399-
loader = &(*loader_storage);
400-
if (Error err = loader->LoadIndex()) {
401-
SetError(result, std::move(err));
402-
return false;
403-
}
404-
}
405-
406-
// If we get here we should have a valid loader.
407-
assert(loader);
415+
Loader *loader =
416+
GetLoaderFromPathOrCurrent(loader_storage, result, m_options.file);
417+
if (!loader)
418+
return false;
408419

409420
switch (m_options.provider) {
410421
case eReproducerProviderFiles: {
@@ -583,6 +594,101 @@ class CommandObjectReproducerDump : public CommandObjectParsed {
583594
CommandOptions m_options;
584595
};
585596

597+
class CommandObjectReproducerVerify : public CommandObjectParsed {
598+
public:
599+
CommandObjectReproducerVerify(CommandInterpreter &interpreter)
600+
: CommandObjectParsed(interpreter, "reproducer verify",
601+
"Verify the contents of a reproducer. "
602+
"If no reproducer is specified during replay, it "
603+
"verifies the content of the current reproducer.",
604+
nullptr) {}
605+
606+
~CommandObjectReproducerVerify() override = default;
607+
608+
Options *GetOptions() override { return &m_options; }
609+
610+
class CommandOptions : public Options {
611+
public:
612+
CommandOptions() : Options(), file() {}
613+
614+
~CommandOptions() override = default;
615+
616+
Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
617+
ExecutionContext *execution_context) override {
618+
Status error;
619+
const int short_option = m_getopt_table[option_idx].val;
620+
621+
switch (short_option) {
622+
case 'f':
623+
file.SetFile(option_arg, FileSpec::Style::native);
624+
FileSystem::Instance().Resolve(file);
625+
break;
626+
default:
627+
llvm_unreachable("Unimplemented option");
628+
}
629+
630+
return error;
631+
}
632+
633+
void OptionParsingStarting(ExecutionContext *execution_context) override {
634+
file.Clear();
635+
}
636+
637+
ArrayRef<OptionDefinition> GetDefinitions() override {
638+
return makeArrayRef(g_reproducer_verify_options);
639+
}
640+
641+
FileSpec file;
642+
};
643+
644+
protected:
645+
bool DoExecute(Args &command, CommandReturnObject &result) override {
646+
if (!command.empty()) {
647+
result.AppendErrorWithFormat("'%s' takes no arguments",
648+
m_cmd_name.c_str());
649+
return false;
650+
}
651+
652+
llvm::Optional<Loader> loader_storage;
653+
Loader *loader =
654+
GetLoaderFromPathOrCurrent(loader_storage, result, m_options.file);
655+
if (!loader)
656+
return false;
657+
658+
bool errors = false;
659+
auto error_callback = [&](llvm::StringRef error) {
660+
errors = true;
661+
result.AppendError(error);
662+
};
663+
664+
bool warnings = false;
665+
auto warning_callback = [&](llvm::StringRef warning) {
666+
warnings = true;
667+
result.AppendWarning(warning);
668+
};
669+
670+
auto note_callback = [&](llvm::StringRef warning) {
671+
result.AppendMessage(warning);
672+
};
673+
674+
Verifier verifier(loader);
675+
verifier.Verify(error_callback, warning_callback, note_callback);
676+
677+
if (warnings || errors) {
678+
result.AppendMessage("reproducer verification failed");
679+
result.SetStatus(eReturnStatusFailed);
680+
} else {
681+
result.AppendMessage("reproducer verification succeeded");
682+
result.SetStatus(eReturnStatusSuccessFinishResult);
683+
}
684+
685+
return result.Succeeded();
686+
}
687+
688+
private:
689+
CommandOptions m_options;
690+
};
691+
586692
CommandObjectReproducer::CommandObjectReproducer(
587693
CommandInterpreter &interpreter)
588694
: CommandObjectMultiword(
@@ -605,6 +711,8 @@ CommandObjectReproducer::CommandObjectReproducer(
605711
new CommandObjectReproducerStatus(interpreter)));
606712
LoadSubCommand("dump",
607713
CommandObjectSP(new CommandObjectReproducerDump(interpreter)));
714+
LoadSubCommand("verify", CommandObjectSP(
715+
new CommandObjectReproducerVerify(interpreter)));
608716
LoadSubCommand("xcrash", CommandObjectSP(
609717
new CommandObjectReproducerXCrash(interpreter)));
610718
}

lldb/source/Commands/Options.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,12 @@ let Command = "reproducer dump" in {
451451
"provided, that reproducer is dumped.">;
452452
}
453453

454+
let Command = "reproducer verify" in {
455+
def reproducer_verify_file : Option<"file", "f">, Group<1>, Arg<"Filename">,
456+
Desc<"The reproducer path. If a reproducer is replayed and no path is "
457+
"provided, that reproducer is dumped.">;
458+
}
459+
454460
let Command = "reproducer xcrash" in {
455461
def reproducer_signal : Option<"signal", "s">, Group<1>,
456462
EnumArg<"None", "ReproducerSignalType()">,

0 commit comments

Comments
 (0)