diff --git a/compiler-rt/lib/msan/msan.cpp b/compiler-rt/lib/msan/msan.cpp --- a/compiler-rt/lib/msan/msan.cpp +++ b/compiler-rt/lib/msan/msan.cpp @@ -122,6 +122,11 @@ *halt_on_error_ = !tmp; return true; } + bool Format(char *buffer, uptr size) final { + const char *keep_going_str = (*halt_on_error) ? "false" : "true"; + uptr keep_going_str_len = internal_strlcpy(buffer, keep_going_str, size); + return keep_going_str_len < size; + } }; static void RegisterMsanFlags(FlagParser *parser, Flags *f) { diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.h b/compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.h --- a/compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.h @@ -22,6 +22,11 @@ class FlagHandlerBase { public: virtual bool Parse(const char *value) { return false; } + virtual bool Format(char *buffer, uptr size) { + if (size > 0) + buffer[0] = '\0'; + return false; + } protected: ~FlagHandlerBase() {} @@ -34,6 +39,10 @@ public: explicit FlagHandler(T *t) : t_(t) {} bool Parse(const char *value) final; + // Write the current value into the buffer of size `size` as a C-string. + // Returns true if value was written without truncation, returns false + // otherwise. + bool Format(char *buffer, uptr size) final; }; inline bool ParseBool(const char *value, bool *b) { @@ -59,6 +68,13 @@ return false; } +template <> +inline bool FlagHandler::Format(char *buffer, uptr size) { + const char *bool_str = *t_ ? "true" : "false"; + uptr bool_str_len = internal_strlcpy(buffer, bool_str, size); + return bool_str_len < size; +} + template <> inline bool FlagHandler::Parse(const char *value) { bool b; @@ -75,12 +91,27 @@ return false; } +template <> +inline bool FlagHandler::Format(char *buffer, uptr size) { + uptr num_symbols_should_write = internal_snprintf(buffer, size, "%d", *t_); + // Not `<=` because `num_symbols_should_write` does not include null + // terminator. + return num_symbols_should_write < size; +} + template <> inline bool FlagHandler::Parse(const char *value) { *t_ = value; return true; } +template <> +inline bool FlagHandler::Format(char *buffer, uptr size) { + const char *str_to_use = *t_ ? *t_ : "(nullptr)"; + uptr str_len = internal_strlcpy(buffer, str_to_use, size); + return str_len < size; +} + template <> inline bool FlagHandler::Parse(const char *value) { const char *value_end; @@ -90,6 +121,14 @@ return ok; } +template <> +inline bool FlagHandler::Format(char *buffer, uptr size) { + uptr num_symbols_should_write = internal_snprintf(buffer, size, "%d", *t_); + // Not `<=` because `num_symbols_should_write` does not include null + // terminator. + return num_symbols_should_write < size; +} + template <> inline bool FlagHandler::Parse(const char *value) { const char *value_end; @@ -99,6 +138,14 @@ return ok; } +template <> +inline bool FlagHandler::Format(char *buffer, uptr size) { + uptr num_symbols_should_write = internal_snprintf(buffer, size, "%p", *t_); + // Not `<=` because `num_symbols_should_write` does not include null + // terminator. + return num_symbols_should_write < size; +} + template <> inline bool FlagHandler::Parse(const char *value) { const char *value_end; @@ -108,6 +155,14 @@ return ok; } +template <> +inline bool FlagHandler::Format(char *buffer, uptr size) { + uptr num_symbols_should_write = internal_snprintf(buffer, size, "%lld", *t_); + // Not `<=` because `num_symbols_should_write` does not include null + // terminator. + return num_symbols_should_write < size; +} + class FlagParser { static const int kMaxFlags = 200; struct Flag { diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.cpp --- a/compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.cpp @@ -56,9 +56,16 @@ } void FlagParser::PrintFlagDescriptions() { + char buffer[128]; + buffer[sizeof(buffer) - 1] = '\0'; Printf("Available flags for %s:\n", SanitizerToolName); - for (int i = 0; i < n_flags_; ++i) - Printf("\t%s\n\t\t- %s\n", flags_[i].name, flags_[i].desc); + for (int i = 0; i < n_flags_; ++i) { + bool truncated = !(flags_[i].handler->Format(buffer, sizeof(buffer))); + CHECK_EQ(buffer[sizeof(buffer) - 1], '\0'); + const char *truncation_str = truncated ? " Truncated" : ""; + Printf("\t%s\n\t\t- %s (Current Value%s: %s)\n", flags_[i].name, + flags_[i].desc, truncation_str, buffer); + } } void FlagParser::fatal_error(const char *err) { diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_flags.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_flags.cpp --- a/compiler-rt/lib/sanitizer_common/sanitizer_flags.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_flags.cpp @@ -75,11 +75,13 @@ class FlagHandlerInclude : public FlagHandlerBase { FlagParser *parser_; bool ignore_missing_; + const char *original_path_; public: explicit FlagHandlerInclude(FlagParser *parser, bool ignore_missing) - : parser_(parser), ignore_missing_(ignore_missing) {} + : parser_(parser), ignore_missing_(ignore_missing), original_path_("") {} bool Parse(const char *value) final { + original_path_ = value; if (internal_strchr(value, '%')) { char *buf = (char *)MmapOrDie(kMaxPathLength, "FlagHandlerInclude"); SubstituteForFlagValue(value, buf, kMaxPathLength); @@ -89,6 +91,13 @@ } return parser_->ParseFile(value, ignore_missing_); } + bool Format(char *buffer, uptr size) { + // FIXME(dliew): `original_path_` isn't actually what's parsed due to `%` + // substitutions. Printing the substituted path would require holding onto + // mmap'ed memory. + uptr str_len = internal_strlcpy(buffer, original_path_, size); + return str_len < size; + } }; void RegisterIncludeFlags(FlagParser *parser, CommonFlags *cf) { diff --git a/compiler-rt/test/sanitizer_common/TestCases/options-help.cpp b/compiler-rt/test/sanitizer_common/TestCases/options-help.cpp --- a/compiler-rt/test/sanitizer_common/TestCases/options-help.cpp +++ b/compiler-rt/test/sanitizer_common/TestCases/options-help.cpp @@ -1,8 +1,61 @@ // RUN: %clangxx -O0 %s -o %t -// RUN: %env_tool_opts=help=1 %run %t 2>&1 | FileCheck %s +// RUN: %env_tool_opts=help=1,include_if_exists=___some_path_that_does_not_exist___ %run %t 2>&1 | FileCheck %s +// RUN: %env_tool_opts=help=1,symbolize=0 %run %t 2>&1 | FileCheck --check-prefix=CHECK-CV %s +// RUN: %clangxx -DSANITIZER_TOOL_NAME=%tool_name -DDO_TRUNCATION=1 -O0 %s -o %t_with_truncation +// RUN: %env_tool_opts=help=1,symbolize=0 %run %t_with_truncation 2>&1 | FileCheck --check-prefix=CHECK-TRUNCATION %s + +#ifdef DO_TRUNCATION + +#define PATH_PATTERN "/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/v/w/x/y/z/aa/ab/ac/add/" +static const char* option_with_big_path = + "sancov_path=" + PATH_PATTERN PATH_PATTERN PATH_PATTERN PATH_PATTERN + "/more/stuff" ; +#undef PATH_PATTERN + +#ifndef SANITIZER_TOOL_NAME +#error SANITIZER_TOOL_NAME must be defined +#endif + +// We have to disable TSan instrumentation here because otherwise it triggers a call to log events into the runtime before +// the runtime is ready. +#define DEFAULT_OPTIONS_FUNC_(X) \ + extern "C" const char *__##X##_default_options(void) __attribute__((no_sanitize("thread"))) { return option_with_big_path; } +#define DEFAULT_OPTIONS_FUNC(X) DEFAULT_OPTIONS_FUNC_(X) +DEFAULT_OPTIONS_FUNC(SANITIZER_TOOL_NAME) +#undef DEFAULT_OPTIONS_FUNC +#undef DEFAULT_OPTIONS_FUNC_ +#endif int main() { } // CHECK: Available flags for {{.*}}Sanitizer: -// CHECK: handle_segv + +// Test bool option assuming the default. +// CHECK: {{^[ \t]+symbolize$}} +// CHECK-NEXT: (Current Value: true) +// +// Test int option +// CHECK: {{^[ \t]+verbosity$}} +// CHECK-NEXT: (Current Value: {{-?[0-9]+}}) + +// Test HandleSignalMode option +// CHECK: {{^[ \t]+handle_segv$}} +// CHECK-NEXT: (Current Value: {{0|1|2}}) + +// Test uptr option +// CHECK: {{^[ \t]+mmap_limit_mb$}} +// CHECK-NEXT: (Current Value: 0x{{[0-9a-fA-F]+}}) + +// Test FlagHandlerInclude option +// CHECK: include_if_exists +// CHECK-NEXT: (Current Value: ___some_path_that_does_not_exist___) + +// Test we show the current value and not the default. +// CHECK-CV: {{^[ \t]+symbolize$}} +// CHECK-CV-NEXT: (Current Value: false) + +// Test that `sancov_path` gets truncated. +// CHECK-TRUNCATION: sancov_path +// CHECK-TRUNCATION-NEXT: (Current Value Truncated: {{.+}})