Index: compiler-rt/lib/sanitizer_common/CMakeLists.txt =================================================================== --- compiler-rt/lib/sanitizer_common/CMakeLists.txt +++ compiler-rt/lib/sanitizer_common/CMakeLists.txt @@ -32,6 +32,7 @@ sanitizer_procmaps_linux.cpp sanitizer_procmaps_mac.cpp sanitizer_procmaps_solaris.cpp + sanitizer_report_decorator.cpp sanitizer_rtems.cpp sanitizer_solaris.cpp sanitizer_stoptheworld_mac.cpp Index: compiler-rt/lib/sanitizer_common/sanitizer_common.cpp =================================================================== --- compiler-rt/lib/sanitizer_common/sanitizer_common.cpp +++ compiler-rt/lib/sanitizer_common/sanitizer_common.cpp @@ -15,6 +15,7 @@ #include "sanitizer_allocator_internal.h" #include "sanitizer_atomic.h" #include "sanitizer_flags.h" +#include "sanitizer_file.h" #include "sanitizer_libc.h" #include "sanitizer_placement_new.h" @@ -93,6 +94,30 @@ __sanitizer_report_error_summary(buff.data()); } +#if !SANITIZER_FUCHSIA + +static INLINE bool ReportSupportsColors() { + return report_file.SupportsColors(); +} + +#else // SANITIZER_FUCHSIA + +// Fuchsia's logs always go through post-processing that handles colorization. +static INLINE bool ReportSupportsColors() { return true; } + +#endif // !SANITIZER_FUCHSIA + +bool ColorizeReports() { + // FIXME: Add proper Windows support to AnsiColorDecorator and re-enable color + // printing on Windows. + if (SANITIZER_WINDOWS) + return false; + + const char *flag = common_flags()->color; + return internal_strcmp(flag, "always") == 0 || + (internal_strcmp(flag, "auto") == 0 && ReportSupportsColors()); +} + // Removes the ANSI escape sequences from the input string (in-place). void RemoveANSIEscapeSequencesFromString(char *str) { if (!str) Index: compiler-rt/lib/sanitizer_common/sanitizer_printf.cpp =================================================================== --- compiler-rt/lib/sanitizer_common/sanitizer_printf.cpp +++ compiler-rt/lib/sanitizer_common/sanitizer_printf.cpp @@ -16,6 +16,7 @@ #include "sanitizer_common.h" #include "sanitizer_flags.h" #include "sanitizer_libc.h" +#include "sanitizer_report_decorator.h" #include #include @@ -290,6 +291,7 @@ break; # undef CHECK_NEEDED_LENGTH } + SanitizerCommonDecorator().Compact(buffer); RawWrite(buffer); // Remove color sequences from the message. Index: compiler-rt/lib/sanitizer_common/sanitizer_report_decorator.h =================================================================== --- compiler-rt/lib/sanitizer_common/sanitizer_report_decorator.h +++ compiler-rt/lib/sanitizer_common/sanitizer_report_decorator.h @@ -24,23 +24,45 @@ // stdout, which is not the case on Windows (see SetConsoleTextAttribute()). public: SanitizerCommonDecorator() : ansi_(ColorizeReports()) {} - const char *Bold() const { return ansi_ ? "\033[1m" : ""; } - const char *Default() const { return ansi_ ? "\033[1m\033[0m" : ""; } + const char *Bold() const { return ToStr(kBold); } + const char *Default() const { return ToStr(kDefault); } const char *Warning() const { return Red(); } const char *Error() const { return Red(); } const char *MemoryByte() const { return Magenta(); } + // Coalesce sequences of special strings that would overwrite each other. + // The string is modified in place. + void Compact(char* str) const; + protected: - const char *Black() const { return ansi_ ? "\033[1m\033[30m" : ""; } - const char *Red() const { return ansi_ ? "\033[1m\033[31m" : ""; } - const char *Green() const { return ansi_ ? "\033[1m\033[32m" : ""; } - const char *Yellow() const { return ansi_ ? "\033[1m\033[33m" : ""; } - const char *Blue() const { return ansi_ ? "\033[1m\033[34m" : ""; } - const char *Magenta() const { return ansi_ ? "\033[1m\033[35m" : ""; } - const char *Cyan() const { return ansi_ ? "\033[1m\033[36m" : ""; } - const char *White() const { return ansi_ ? "\033[1m\033[37m" : ""; } + const char *Black() const { return ToStr(kBlack); } + const char *Red() const { return ToStr(kRed); } + const char *Green() const { return ToStr(kGreen); } + const char *Yellow() const { return ToStr(kYellow); } + const char *Blue() const { return ToStr(kBlue); } + const char *Magenta() const { return ToStr(kMagenta); } + const char *Cyan() const { return ToStr(kCyan); } + const char *White() const { return ToStr(kWhite); } + private: - bool ansi_; + // The currently supported ANSI tags. + enum DecorationKind { + kUnknown = -1, + kDefault = 0, + kBold = 1, + kBlack = 30, + kRed = 31, + kGreen = 32, + kYellow = 33, + kBlue = 34, + kMagenta = 35, + kCyan = 36, + kWhite = 37, + }; + + const char* ToStr(DecorationKind kind) const; + + const bool ansi_; }; } // namespace __sanitizer Index: compiler-rt/lib/sanitizer_common/sanitizer_report_decorator.cpp =================================================================== --- /dev/null +++ compiler-rt/lib/sanitizer_common/sanitizer_report_decorator.cpp @@ -0,0 +1,107 @@ +//===-- sanitizer_report_decorator.cc ---------------------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// This file is shared between AddressSanitizer and ThreadSanitizer. +// +// Methods to manipulate supported ANSI tags. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_report_decorator.h" + +#include "sanitizer_libc.h" + +namespace __sanitizer { + +const char* SanitizerCommonDecorator::ToStr(DecorationKind kind) const { + if (!ansi_) + return ""; + switch (kind) { + case kDefault: + return "\033[0m"; + case kBold: + return "\033[1m"; + case kBlack: + return "\033[1m\033[30m"; + case kRed: + return "\033[1m\033[31m"; + case kGreen: + return "\033[1m\033[32m"; + case kYellow: + return "\033[1m\033[33m"; + case kBlue: + return "\033[1m\033[34m"; + case kMagenta: + return "\033[1m\033[35m"; + case kCyan: + return "\033[1m\033[36m"; + case kWhite: + return "\033[1m\033[37m"; + default: + return ""; + } +} + +void SanitizerCommonDecorator::Compact(char* str) const { + if (!str) + return; + const char *in = str; + char* out = str; + + // We need to keep track of whatever decoration tag we last printed with, as + // well as whatever one is currently-active and where the string to print it + // is located in the buffer. + DecorationKind prev = kUnknown, next = kUnknown; + do { + // Consume recognized tags in a loop. Tags are not immediately-printed, + // and each successive one replaces the previous, so that only-the last + // in a sequence is active. + while (true) { + s64 code = kUnknown; + const char* endptr = in; + if (internal_strncmp(in, "\033[", 2) == 0) + code = internal_simple_strtoll(in + 2, &endptr, 10); + if (endptr <= in + 2 || *endptr != 'm') + code = kUnknown; + switch (code) { + case kDefault: + case kBold: + break; + case kBlack: + case kRed: + case kGreen: + case kYellow: + case kBlue: + case kMagenta: + case kCyan: + case kWhite: + if (next != kBold) + code = kUnknown; + break; + default: + code = kUnknown; + } + if (code == kUnknown) + break; + in = endptr + 1; + next = static_cast(code); + } + + // If the current character is printable or the end of the string, and the + // active tag has changed, print the tag. + if (((0x20 < *in && *in < 0x7f) || *in == '\0') && prev != next) { + auto tag = ToStr(next); + auto tag_len = internal_strlen(tag); // Not null-terminated! + internal_strncpy(out, tag, tag_len); + prev = next; + out += tag_len; + } + } while((*out++ = *in++) != '\0'); +} + +} // namespace __sanitizer Index: compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_report.cpp =================================================================== --- compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_report.cpp +++ compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_report.cpp @@ -39,36 +39,6 @@ } #endif -#if !SANITIZER_FUCHSIA - -bool ReportFile::SupportsColors() { - SpinMutexLock l(mu); - ReopenIfNecessary(); - return SupportsColoredOutput(fd); -} - -static INLINE bool ReportSupportsColors() { - return report_file.SupportsColors(); -} - -#else // SANITIZER_FUCHSIA - -// Fuchsia's logs always go through post-processing that handles colorization. -static INLINE bool ReportSupportsColors() { return true; } - -#endif // !SANITIZER_FUCHSIA - -bool ColorizeReports() { - // FIXME: Add proper Windows support to AnsiColorDecorator and re-enable color - // printing on Windows. - if (SANITIZER_WINDOWS) - return false; - - const char *flag = common_flags()->color; - return internal_strcmp(flag, "always") == 0 || - (internal_strcmp(flag, "auto") == 0 && ReportSupportsColors()); -} - void ReportErrorSummary(const char *error_type, const StackTrace *stack, const char *alt_tool_name) { #if !SANITIZER_GO Index: compiler-rt/lib/sanitizer_common/tests/sanitizer_printf_test.cpp =================================================================== --- compiler-rt/lib/sanitizer_common/tests/sanitizer_printf_test.cpp +++ compiler-rt/lib/sanitizer_common/tests/sanitizer_printf_test.cpp @@ -157,4 +157,165 @@ EXPECT_STREQ("12345 ", buf); } +// Value parameterized test for compacting ANSI color sequences. +struct CompactParam { + const char *Input; + const char *WithoutColor; + const char *WithColor; +}; + +class CompactTest : public testing::TestWithParam {}; + +// Derived decorator that allows setting color support manualy. +class TestDecorator : public SanitizerCommonDecorator { + TestDecorator(bool ansi) { ansi_ = ansi; } +}; + +TEST_P(CompactTest, WithoutColor) { + auto Param = GetParam(); + char *Actual = strdup(Param.Input); + ASSERT_NOT_NULL(Actual); + TestDecorator(false).Compact(Actual); + EXPECT_STREQ(Actual, Param.WithoutColor); + free(Actual); +}; + +TEST_P(CompactTest, WithColor) { + auto Param = GetParam(); + char *Actual = strdup(Param.Input); + ASSERT_NOT_NULL(Actual); + TestDecorator(true).Compact(Actual); + EXPECT_STREQ(Actual, Param.WithColor); + free(Actual); +} + +INSTANTIATE_TEST_SUITE_P( + WithSpaces, CompactTest, + testing::Values( + { + " x\033[1m\033[31m\033[1m\033[31m\033[1m\033[34my", + " xy", + " x\033[1m\033[34my", + }, + { + "\033[1m\033[31m x\033[1m\033[31m\033[1m\033[31m\033[1m\033[34my", + " xy", + " \033[1m\033[31mx\033[1m\033[34my", + }, + { + "\033[1m\033[34mx \033[1m\033[31m\033[1m\033[31m\033[1m\033[34my", + "x y", + "\033[1m\033[34mx y", + }, + { + "\033[1m\033[31mx\033[1m\033[31m " + "\033[1m\033[31m\033[1m\033[34my\033[1m\033[31m", + "x y", + "\033[1m\033[31mx \033[1m\033[34my\033[1m\033[31m", + }, + { + "\033[1m\033[34mx\033[1m\033[31m\033[1m\033[31m " + "\033[1m\033[34my\033[1m\033[31m", + "x y", + "\033[1m\033[34mx y\033[1m\033[31m", + }, + { + "\033[1m\033[31mx\033[1m\033[31m\033[1m\033[31m\033[1m\033[34m " + "y\033[1m\033[34m", + "x y", + "\033[1m\033[31mx \033[1m\033[34my", + }, + { + "\033[1m\033[34mx\033[1m\033[31m\033[1m\033[31m\033[1m\033[34my " + "\033[1m\033[34m", + "xy ", + "\033[1m\033[34mxy ", + }, + { + "x\033[1m\033[31m\033[1m\033[34m\033[1m\033[31my ", + "xy ", + "x\033[1m\033[31my ", + })); + +INSTANTIATE_TEST_SUITE_P( + WithNewlines, CompactTest, + testing::Values( + { + "\n\033[1m\033[31mx\033[1m\033[31m\033[1m\033[34m\033[1m\033[31my", + "\nxy", + "\n\033[1m\033[31mxy", + }, + { + "\033[1m\033[34m\nx\033[1m\033[31m\033[1m\033[34m\033[1m\033[31my", + "\nxy", + "\n\033[1m\033[34mx\033[1m\033[31my", + }, + { + "\033[1m\033[31mx\n\033[1m\033[31m\033[1m\033[34m\033[1m\033[" + "31my\033[1m\033[31m", + "x\ny", + "\033[1m\033[31mx\ny", + }, + { + "\033[1m\033[34mx\033[1m\033[31m\n\033[1m\033[34m\033[1m\033[" + "31my\033[1m\033[31m", + "x\ny", + "\033[1m\033[34mx\n\033[1m\033[31my", + }, + { + "\033[1m\033[31mx\033[1m\033[31m\033[1m\033[34m\n\033[1m\033[" + "31my\033[1m\033[34m", + "x\ny", + "\033[1m\033[31mx\ny\033[1m\033[34m", + }, + { + "\033[1m\033[34mx\033[1m\033[31m\033[1m\033[34m\033[1m\033[" + "31m\ny\033[1m\033[34m", + "x\ny", + "\033[1m\033[34mx\n\033[1m\033[31my\033[1m\033[34m", + }, + { + "x\033[1m\033[34m\033[1m\033[31m\033[1m\033[31my\n", + "xy\n", + "x\033[1m\033[31my\n", + }, + { + "\033[1m\033[31mx\033[1m\033[34m\033[1m\033[31m\033[1m\033[31my\n", + "xy\n", + "\033[1m\033[31mxy\n", + })); + +INSTANTIATE_TEST_SUITE_P( + WithTabs, CompactTest, + testing::Values( + { + "\t\033[1m\033[34mx\033[1m\033[34m\033[1m\033[31m\033[1m\033[31my", + "\txy", + "\t\033[1m\033[34mx\033[1m\033[31my", + }, + { + "\033[1m\033[31m\tx\033[1m\033[34m\033[1m\033[31m\033[1m\033[" + "31my\033[1m\033[31m", + "\txy", + "\t\033[1m\033[31mxy", + }, + { + "\033[1m\033[34mx\t\033[1m\033[34m\033[1m\033[31m\033[1m\033[" + "31my\033[1m\033[31m", + "x\ty", + "\033[1m\033[34mx\t\033[1m\033[31my", + }, + { + "\033[1m\033[31mx\033[1m\033[34m\t\033[1m\033[31m\033[1m\033[" + "31my\033[1m\033[34m", + "x\ty", + "\033[1m\033[31mx\ty\033[1m\033[34m", + }, + { + "\033[1m\033[34mx\033[1m\033[34m\033[1m\033[31m\t\033[1m\033[" + "31my\033[1m\033[34m", + "x\ty", + "\033[1m\033[34mx\t\033[1m\033[31my\033[1m\033[34m", + })); + } // namespace __sanitizer