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.cc sanitizer_procmaps_mac.cc sanitizer_procmaps_solaris.cc + sanitizer_report_decorator.cc sanitizer_rtems.cc sanitizer_solaris.cc sanitizer_stoptheworld_mac.cc Index: compiler-rt/lib/sanitizer_common/sanitizer_printf.cc =================================================================== --- compiler-rt/lib/sanitizer_common/sanitizer_printf.cc +++ compiler-rt/lib/sanitizer_common/sanitizer_printf.cc @@ -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,46 @@ // 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 ansi_ ? ToStr(kBold) : ""; } + const char *Default() const { return ansi_ ? 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. + static void Compact(char *str); + 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 ansi_ ? ToStr(kBlack) : ""; } + const char* Red() const { return ansi_ ? ToStr(kRed) : ""; } + const char* Green() const { return ansi_ ? ToStr(kGreen) : ""; } + const char* Yellow() const { return ansi_ ? ToStr(kYellow) : ""; } + const char* Blue() const { return ansi_ ? ToStr(kBlue) : ""; } + const char* Magenta() const { return ansi_ ? ToStr(kMagenta) : ""; } + const char* Cyan() const { return ansi_ ? ToStr(kCyan) : ""; } + const char* White() const { return ansi_ ? ToStr(kWhite) : ""; } + private: - bool ansi_; + // The currently supported ANSI tags. + enum DecorationKind { + kUnknown, + kDefault, + kBold, + kBlack, + kRed, + kGreen, + kYellow, + kBlue, + kMagenta, + kCyan, + kWhite, + }; + static DecorationKind ToKind(const char *str); + static const char *ToStr(DecorationKind kind); + static uptr StrLen(DecorationKind kind); + + const bool ansi_; }; } // namespace __sanitizer Index: compiler-rt/lib/sanitizer_common/sanitizer_report_decorator.cc =================================================================== --- /dev/null +++ compiler-rt/lib/sanitizer_common/sanitizer_report_decorator.cc @@ -0,0 +1,153 @@ +//===-- 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_libc.h" +#include "sanitizer_report_decorator.h" + +namespace __sanitizer { + +SanitizerCommonDecorator::DecorationKind +SanitizerCommonDecorator::ToKind(const char *s) { + if (internal_strncmp(s, "\033[", 2) != 0 || s[2] == '\0' || s[3] != 'm') + return kUnknown; + switch (s[2]) { + case '0': + return kDefault; + case '1': + break; + default: + return kUnknown; + } + if (internal_strncmp(&s[4], "\033[3", 3) != 0 || s[7] == '\0' || s[8] != 'm') + return kBold; + switch(s[7]) { + case '0': + return kBlack; + case '1': + return kRed; + case '2': + return kGreen; + case '3': + return kYellow; + case '4': + return kBlue; + case '5': + return kMagenta; + case '6': + return kCyan; + case '7': + return kWhite; + default: + return kUnknown; + } +} + +const char *SanitizerCommonDecorator::ToStr(DecorationKind kind) { + 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 ""; + } +} + +uptr SanitizerCommonDecorator::StrLen(DecorationKind kind) { + switch(kind) { + case kDefault: + case kBold: + return 4; + case kBlack: + case kRed: + case kGreen: + case kYellow: + case kBlue: + case kMagenta: + case kCyan: + case kWhite: + return 9; + default: + return 0; + } +} + +void SanitizerCommonDecorator::Compact(char* str) { + if (!str) + return; + char* s = str; + char* z = 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; + DecorationKind next = kUnknown; + uptr n = 0; + + while (true) { + // 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. + for (auto kind = ToKind(s); kind != kUnknown; kind = ToKind(s)) { + next = kind; + s += SanitizerCommonDecorator::StrLen(kind); + } + + if (*s == '\0') + break; + + // The active tag(s) has changed since the last printable, + // non-whitespace character. Print the tag(s) before the character. + if (0x20 < *s && *s < 0x7f && prev != next) { + n = SanitizerCommonDecorator::StrLen(next); + internal_memcpy(z, ToStr(next), n); + prev = next; + z += n; + continue; + } + + // We have actual characters; print them. + if (z != s) + *z = *s; + z++; + s++; + } + // Make sure we leave the output stream in the right state before + // terminating the string. + if (prev != next) { + n = SanitizerCommonDecorator::StrLen(next); + internal_strncpy(z, ToStr(next), n + 1); + } else { + *z = '\0'; + } +} + +} // namespace __sanitizer Index: compiler-rt/lib/sanitizer_common/tests/sanitizer_printf_test.cc =================================================================== --- compiler-rt/lib/sanitizer_common/tests/sanitizer_printf_test.cc +++ compiler-rt/lib/sanitizer_common/tests/sanitizer_printf_test.cc @@ -157,4 +157,91 @@ EXPECT_STREQ("12345 ", buf); } +// The array below is used to test that Printf correctly collapses unneeded +// decorations. Each element is a pair of strings that represent equivalent +// decorated strings. The strings can be interpreted as follows: +// 'R' and 'B': Print the ANSI escape sequences for "Red" and "Blue" +// 'N', 'S', 'T': Print newline, space, and tab, respectively. +// 'x', 'y': Print the character as-is. +// '-', others: Ignore. +struct DecorationTest { + char verbose[10]; + char compact[7]; +} tests[] = { + {"S-xRRBy-", "S-xBy-"}, + {"RSxRRBy-", "SRxBy-"}, + {"BxSRRBy-", "BxS-y-"}, + {"RxRSRByR", "RxSByR"}, + {"BxRRSByR", "BxS-yR"}, + {"RxRRBSyB", "RxSBy-"}, + {"BxRRBySB", "Bx-yS-"}, + {"-xRBRy-S", "-xRyS-"}, + {"NRxRBRy-", "NRx-y-"}, + {"BNxRBRy-", "NBxRy-"}, + {"RxNRBRyR", "RxN-y-"}, + {"BxRNBRyR", "BxNRy-"}, + {"RxRBNRyB", "RxN-yB"}, + {"BxRBRNyB", "BxNRyB"}, + {"-xBRRyN-", "-xRyN-"}, + {"RxBRRy-N", "Rx-yN-"}, + {"TBxBRRy-", "TBxRy-"}, + {"RTxBRRyR", "TRx-y-"}, + {"BxTBRRyR", "BxTRy-"}, + {"RxBTRRyB", "RxT-yB"}, + {"BxBRTRyB", "BxTRyB"}, +}; + +class TestDecorator : public SanitizerCommonDecorator { + public: + // Returns an ASNI escape sequence for 'R' or 'B' as described above. + const char* ToStr(char c) { + switch (c) { + case 'R': + return SanitizerCommonDecorator::Red(); + case 'B': + return SanitizerCommonDecorator::Blue(); + case 'N': + return "\n"; + case 'S': + return " "; + case 'T': + return "\t"; + case 'x': + return "x"; + case 'y': + return "y"; + case '-': + default: + return ""; + } + } +}; + +TEST(Printf, Decorations) { + TestDecorator d; + char verbose[64]; + char compact[64]; + int n; + for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i) { + const char* v = tests[i].verbose; + size_t off = 0; + for (size_t j=0; j < strlen(v); j++) { + n = snprintf(verbose + off, sizeof(verbose) - off, "%s", d.ToStr(v[j])); + ASSERT_GE(n, 0); + off += static_cast(n); + ASSERT_LT(off, sizeof(verbose)); + } + SanitizerCommonDecorator::Compact(verbose); + const char* c = tests[i].compact; + off = 0; + for (size_t j=0; j < strlen(c); j++) { + n = snprintf(compact + off, sizeof(compact) - off, "%s", d.ToStr(c[j])); + ASSERT_GE(n, 0); + off += static_cast(n); + ASSERT_LT(off, sizeof(compact)); + } + EXPECT_STREQ(verbose, compact); + } +} + } // namespace __sanitizer