Index: lib/scudo/standalone/CMakeLists.txt =================================================================== --- lib/scudo/standalone/CMakeLists.txt +++ lib/scudo/standalone/CMakeLists.txt @@ -38,7 +38,8 @@ crc32_hw.cc common.cc fuchsia.cc - linux.cc) + linux.cc + string_utils.cc) # Enable the SSE 4.2 instruction set for crc32_hw.cc, if available. if (COMPILER_RT_HAS_MSSE4_2_FLAG) @@ -61,6 +62,7 @@ mutex.h platform.h stats.h + string_utils.h vector.h) if(COMPILER_RT_HAS_SCUDO_STANDALONE) Index: lib/scudo/standalone/fuchsia.cc =================================================================== --- lib/scudo/standalone/fuchsia.cc +++ lib/scudo/standalone/fuchsia.cc @@ -12,6 +12,7 @@ #include "common.h" #include "mutex.h" +#include "string_utils.h" #include // for PAGE_SIZE #include // for abort() @@ -110,10 +111,7 @@ dieOnMapUnmapError(Status == ZX_ERR_NO_MEMORY); return nullptr; } - uptr N = 0; - while (Name[N]) - N++; - _zx_object_set_property(Vmo, ZX_PROP_NAME, Name, N); + _zx_object_set_property(Vmo, ZX_PROP_NAME, Name, strlen(Name)); } uintptr_t P; @@ -203,10 +201,7 @@ } void outputRaw(const char *Buffer) { - uptr N = 0; - while (Buffer[N]) - N++; - __sanitizer_log_write(Buffer, N); + __sanitizer_log_write(Buffer, strlen(Buffer)); } void setAbortMessage(const char *Message) {} Index: lib/scudo/standalone/linux.cc =================================================================== --- lib/scudo/standalone/linux.cc +++ lib/scudo/standalone/linux.cc @@ -13,6 +13,7 @@ #include "common.h" #include "linux.h" #include "mutex.h" +#include "string_utils.h" #include #include @@ -133,10 +134,7 @@ void outputRaw(const char *Buffer) { static StaticSpinMutex Mutex; SpinMutexLock L(&Mutex); - uptr N = 0; - while (Buffer[N]) - N++; - write(2, Buffer, N); + write(2, Buffer, strlen(Buffer)); } extern "C" WEAK void android_set_abort_message(const char *); Index: lib/scudo/standalone/string_utils.h =================================================================== --- /dev/null +++ lib/scudo/standalone/string_utils.h @@ -0,0 +1,42 @@ +//===-- string_utils.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 SCUDO_STRING_UTILS_H_ +#define SCUDO_STRING_UTILS_H_ + +#include "internal_defs.h" +#include "vector.h" + +#include + +namespace scudo { + +class ScopedString { +public: + explicit ScopedString(uptr MaxLength) : String(MaxLength), Length(0) { + String[0] = '\0'; + } + uptr length() { return Length; } + const char *data() { return String.data(); } + void clear() { + String[0] = '\0'; + Length = 0; + } + void append(const char *Format, va_list Args); + void append(const char *Format, ...); + +private: + Vector String; + uptr Length; +}; + +void Printf(const char *Format, ...); + +} // namespace scudo + +#endif // SCUDO_STRING_UTILS_H_ Index: lib/scudo/standalone/string_utils.cc =================================================================== --- /dev/null +++ lib/scudo/standalone/string_utils.cc @@ -0,0 +1,236 @@ +//===-- string_utils.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 +// +//===----------------------------------------------------------------------===// + +#include "string_utils.h" +#include "common.h" + +#include +#include +#include + +namespace scudo { + +static int appendChar(char **Buffer, const char *BufferEnd, char C) { + if (*Buffer < BufferEnd) { + **Buffer = C; + (*Buffer)++; + } + return 1; +} + +// Appends number in a given Base to buffer. If its length is less than +// |MinNumberLength|, it is padded with leading zeroes or spaces, depending +// on the value of |PadWithZero|. +static int appendNumber(char **Buffer, const char *BufferEnd, u64 AbsoluteValue, + u8 Base, u8 MinNumberLength, bool PadWithZero, + bool Negative, bool Upper) { + constexpr uptr MaxLen = 30; + RAW_CHECK(Base == 10 || Base == 16); + RAW_CHECK(Base == 10 || !Negative); + RAW_CHECK(AbsoluteValue || !Negative); + RAW_CHECK(MinNumberLength < MaxLen); + int Res = 0; + if (Negative && MinNumberLength) + --MinNumberLength; + if (Negative && PadWithZero) + Res += appendChar(Buffer, BufferEnd, '-'); + uptr NumBuffer[MaxLen]; + int Pos = 0; + do { + RAW_CHECK_MSG(static_cast(Pos) < MaxLen, + "appendNumber buffer overflow"); + NumBuffer[Pos++] = AbsoluteValue % Base; + AbsoluteValue /= Base; + } while (AbsoluteValue > 0); + if (Pos < MinNumberLength) { + memset(&NumBuffer[Pos], 0, + sizeof(NumBuffer[0]) * static_cast(MinNumberLength - Pos)); + Pos = MinNumberLength; + } + RAW_CHECK(Pos > 0); + Pos--; + for (; Pos >= 0 && NumBuffer[Pos] == 0; Pos--) { + char c = (PadWithZero || Pos == 0) ? '0' : ' '; + Res += appendChar(Buffer, BufferEnd, c); + } + if (Negative && !PadWithZero) + Res += appendChar(Buffer, BufferEnd, '-'); + for (; Pos >= 0; Pos--) { + char Digit = static_cast(NumBuffer[Pos]); + Digit = static_cast((Digit < 10) ? '0' + Digit + : (Upper ? 'A' : 'a') + Digit - 10); + Res += appendChar(Buffer, BufferEnd, Digit); + } + return Res; +} + +static int appendUnsigned(char **Buffer, const char *BufferEnd, u64 Num, + u8 Base, u8 MinNumberLength, bool PadWithZero, + bool Upper) { + return appendNumber(Buffer, BufferEnd, Num, Base, MinNumberLength, + PadWithZero, /*Negative=*/false, Upper); +} + +static int appendSignedDecimal(char **Buffer, const char *BufferEnd, s64 Num, + u8 MinNumberLength, bool PadWithZero) { + const bool Negative = (Num < 0); + return appendNumber(Buffer, BufferEnd, + static_cast(Negative ? -Num : Num), 10, + MinNumberLength, PadWithZero, Negative, + /*Upper=*/false); +} + +// Use the fact that explicitly requesting 0 Width (%0s) results in UB and +// interpret Width == 0 as "no Width requested": +// Width == 0 - no Width requested +// Width < 0 - left-justify S within and pad it to -Width chars, if necessary +// Width > 0 - right-justify S, not implemented yet +static int appendString(char **Buffer, const char *BufferEnd, int Width, + int MaxChars, const char *S) { + if (!S) + S = ""; + int Res = 0; + for (; *S; S++) { + if (MaxChars >= 0 && Res >= MaxChars) + break; + Res += appendChar(Buffer, BufferEnd, *S); + } + // Only the left justified strings are supported. + while (Width < -Res) + Res += appendChar(Buffer, BufferEnd, ' '); + return Res; +} + +static int appendPointer(char **Buffer, const char *BufferEnd, u64 ptr_value) { + int Res = 0; + Res += appendString(Buffer, BufferEnd, 0, -1, "0x"); + Res += appendUnsigned(Buffer, BufferEnd, ptr_value, 16, + SCUDO_POINTER_FORMAT_LENGTH, /*PadWithZero=*/true, + /*Upper=*/false); + return Res; +} + +int formatString(char *Buffer, uptr BufferLength, const char *Format, + va_list Args) { + UNUSED static const char *PrintfFormatsHelp = + "Supported formatString formats: %([0-9]*)?(z|ll)?{d,u,x,X}; %p; " + "%[-]([0-9]*)?(\\.\\*)?s; %c\n"; + RAW_CHECK(Format); + RAW_CHECK(BufferLength > 0); + const char *BufferEnd = &Buffer[BufferLength - 1]; + const char *Cur = Format; + int Res = 0; + for (; *Cur; Cur++) { + if (*Cur != '%') { + Res += appendChar(&Buffer, BufferEnd, *Cur); + continue; + } + Cur++; + const bool LeftJustified = *Cur == '-'; + if (LeftJustified) + Cur++; + bool HaveWidth = (*Cur >= '0' && *Cur <= '9'); + const bool PadWithZero = (*Cur == '0'); + u8 Width = 0; + if (HaveWidth) { + while (*Cur >= '0' && *Cur <= '9') + Width = static_cast(Width * 10 + *Cur++ - '0'); + } + const bool HavePrecision = (Cur[0] == '.' && Cur[1] == '*'); + int Precision = -1; + if (HavePrecision) { + Cur += 2; + Precision = va_arg(Args, int); + } + const bool HaveZ = (*Cur == 'z'); + Cur += HaveZ; + const bool HaveLL = !HaveZ && (Cur[0] == 'l' && Cur[1] == 'l'); + Cur += HaveLL * 2; + s64 DVal; + u64 UVal; + const bool HaveLength = HaveZ || HaveLL; + const bool HaveFlags = HaveWidth || HaveLength; + // At the moment only %s supports precision and left-justification. + CHECK(!((Precision >= 0 || LeftJustified) && *Cur != 's')); + switch (*Cur) { + case 'd': { + DVal = HaveLL ? va_arg(Args, s64) + : HaveZ ? va_arg(Args, sptr) : va_arg(Args, int); + Res += appendSignedDecimal(&Buffer, BufferEnd, DVal, Width, PadWithZero); + break; + } + case 'u': + case 'x': + case 'X': { + UVal = HaveLL ? va_arg(Args, u64) + : HaveZ ? va_arg(Args, uptr) : va_arg(Args, unsigned); + const bool Upper = (*Cur == 'X'); + Res += appendUnsigned(&Buffer, BufferEnd, UVal, (*Cur == 'u') ? 10 : 16, + Width, PadWithZero, Upper); + break; + } + case 'p': { + RAW_CHECK_MSG(!HaveFlags, PrintfFormatsHelp); + Res += appendPointer(&Buffer, BufferEnd, va_arg(Args, uptr)); + break; + } + case 's': { + RAW_CHECK_MSG(!HaveLength, PrintfFormatsHelp); + // Only left-justified Width is supported. + CHECK(!HaveWidth || LeftJustified); + Res += appendString(&Buffer, BufferEnd, LeftJustified ? -Width : Width, + Precision, va_arg(Args, char *)); + break; + } + case 'c': { + RAW_CHECK_MSG(!HaveFlags, PrintfFormatsHelp); + Res += + appendChar(&Buffer, BufferEnd, static_cast(va_arg(Args, int))); + break; + } + case '%': { + RAW_CHECK_MSG(!HaveFlags, PrintfFormatsHelp); + Res += appendChar(&Buffer, BufferEnd, '%'); + break; + } + default: { + RAW_CHECK_MSG(false, PrintfFormatsHelp); + } + } + } + RAW_CHECK(Buffer <= BufferEnd); + appendChar(&Buffer, BufferEnd + 1, '\0'); + return Res; +} + +void ScopedString::append(const char *Format, va_list Args) { + CHECK_LT(Length, String.size()); + formatString(String.data() + Length, String.size() - Length, Format, Args); + Length += strlen(String.data() + Length); + CHECK_LT(Length, String.size()); +} + +FORMAT(2, 3) +void ScopedString::append(const char *Format, ...) { + va_list Args; + va_start(Args, Format); + append(Format, Args); + va_end(Args); +} + +FORMAT(1, 2) +void Printf(const char *Format, ...) { + va_list Args; + va_start(Args, Format); + ScopedString Msg(512); + Msg.append(Format, Args); + outputRaw(Msg.data()); + va_end(Args); +} + +} // namespace scudo Index: lib/scudo/standalone/tests/CMakeLists.txt =================================================================== --- lib/scudo/standalone/tests/CMakeLists.txt +++ lib/scudo/standalone/tests/CMakeLists.txt @@ -56,6 +56,7 @@ map_test.cc mutex_test.cc stats_test.cc + strings_test.cc vector_test.cc scudo_unit_test_main.cc) Index: lib/scudo/standalone/tests/strings_test.cc =================================================================== --- /dev/null +++ lib/scudo/standalone/tests/strings_test.cc @@ -0,0 +1,98 @@ +//===-- strings_test.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 +// +//===----------------------------------------------------------------------===// + +#include "scudo/standalone/string_utils.h" +#include "gtest/gtest.h" + +#include + +TEST(ScudoStringsTest, Basic) { + scudo::ScopedString Str(128); + Str.append("a%db%zdc%ue%zuf%xh%zxq%pe%sr", static_cast(-1), + static_cast(-2), static_cast(-4), + static_cast(5), static_cast(10), + static_cast(11), reinterpret_cast(0x123), + "_string_"); + EXPECT_EQ(Str.length(), strlen(Str.data())); + + std::string expectedString = "a-1b-2c4294967292e5fahbq0x"; + expectedString += std::string(SCUDO_POINTER_FORMAT_LENGTH - 3, '0'); + expectedString += "123e_string_r"; + EXPECT_EQ(Str.length(), strlen(Str.data())); + EXPECT_STREQ(expectedString.c_str(), Str.data()); +} + +TEST(ScudoStringsTest, Precision) { + scudo::ScopedString Str(128); + Str.append("%.*s", 3, "12345"); + EXPECT_EQ(Str.length(), strlen(Str.data())); + EXPECT_STREQ("123", Str.data()); + Str.clear(); + Str.append("%.*s", 6, "12345"); + EXPECT_EQ(Str.length(), strlen(Str.data())); + EXPECT_STREQ("12345", Str.data()); + Str.clear(); + Str.append("%-6s", "12345"); + EXPECT_EQ(Str.length(), strlen(Str.data())); + EXPECT_STREQ("12345 ", Str.data()); +} + +static void fillString(scudo::ScopedString &Str, scudo::uptr Size) { + for (scudo::uptr I = 0; I < Size; I++) + Str.append("A"); +} + +TEST(ScudoStringTest, PotentialOverflows) { + // Use a ScopedString that spans a page, and attempt to write past the end + // of it with variations of append. The expectation is for nothing to crash. + const scudo::uptr PageSize = scudo::getPageSizeCached(); + scudo::ScopedString Str(PageSize); + Str.clear(); + fillString(Str, 2 * PageSize); + Str.clear(); + fillString(Str, PageSize - 64); + Str.append("%-128s", "12345"); + Str.clear(); + fillString(Str, PageSize - 16); + Str.append("%024x", 12345); + Str.clear(); + fillString(Str, PageSize - 16); + Str.append("EEEEEEEEEEEEEEEEEEEEEEEE"); +} + +template +static void testAgainstLibc(const char *Format, T Arg1, T Arg2) { + scudo::ScopedString Str(128); + Str.append(Format, Arg1, Arg2); + char Buffer[128]; + snprintf(Buffer, sizeof(Buffer), Format, Arg1, Arg2); + EXPECT_EQ(Str.length(), strlen(Str.data())); + EXPECT_STREQ(Buffer, Str.data()); +} + +TEST(ScudoStringsTest, MinMax) { + testAgainstLibc("%d-%d", INT_MIN, INT_MAX); + testAgainstLibc("%u-%u", 0, UINT_MAX); + testAgainstLibc("%x-%x", 0, UINT_MAX); + testAgainstLibc("%zd-%zd", LONG_MIN, LONG_MAX); + testAgainstLibc("%zu-%zu", 0, ULONG_MAX); + testAgainstLibc("%zx-%zx", 0, ULONG_MAX); +} + +TEST(ScudoStringsTest, Padding) { + testAgainstLibc("%3d - %3d", 1, 0); + testAgainstLibc("%3d - %3d", -1, 123); + testAgainstLibc("%3d - %3d", -1, -123); + testAgainstLibc("%3d - %3d", 12, 1234); + testAgainstLibc("%3d - %3d", -12, -1234); + testAgainstLibc("%03d - %03d", 1, 0); + testAgainstLibc("%03d - %03d", -1, 123); + testAgainstLibc("%03d - %03d", -1, -123); + testAgainstLibc("%03d - %03d", 12, 1234); + testAgainstLibc("%03d - %03d", -12, -1234); +}