Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 1 | //===-- string_utils.cpp ----------------------------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "string_utils.h" |
| 10 | #include "common.h" |
| 11 | |
| 12 | #include <stdarg.h> |
| 13 | #include <string.h> |
| 14 | |
| 15 | namespace scudo { |
| 16 | |
| 17 | static int appendChar(char **Buffer, const char *BufferEnd, char C) { |
| 18 | if (*Buffer < BufferEnd) { |
| 19 | **Buffer = C; |
| 20 | (*Buffer)++; |
| 21 | } |
| 22 | return 1; |
| 23 | } |
| 24 | |
| 25 | // Appends number in a given Base to buffer. If its length is less than |
| 26 | // |MinNumberLength|, it is padded with leading zeroes or spaces, depending |
| 27 | // on the value of |PadWithZero|. |
| 28 | static int appendNumber(char **Buffer, const char *BufferEnd, u64 AbsoluteValue, |
| 29 | u8 Base, u8 MinNumberLength, bool PadWithZero, |
| 30 | bool Negative, bool Upper) { |
| 31 | constexpr uptr MaxLen = 30; |
| 32 | RAW_CHECK(Base == 10 || Base == 16); |
| 33 | RAW_CHECK(Base == 10 || !Negative); |
| 34 | RAW_CHECK(AbsoluteValue || !Negative); |
| 35 | RAW_CHECK(MinNumberLength < MaxLen); |
| 36 | int Res = 0; |
| 37 | if (Negative && MinNumberLength) |
| 38 | --MinNumberLength; |
| 39 | if (Negative && PadWithZero) |
| 40 | Res += appendChar(Buffer, BufferEnd, '-'); |
| 41 | uptr NumBuffer[MaxLen]; |
| 42 | int Pos = 0; |
| 43 | do { |
| 44 | RAW_CHECK_MSG(static_cast<uptr>(Pos) < MaxLen, |
| 45 | "appendNumber buffer overflow"); |
| 46 | NumBuffer[Pos++] = static_cast<uptr>(AbsoluteValue % Base); |
| 47 | AbsoluteValue /= Base; |
| 48 | } while (AbsoluteValue > 0); |
| 49 | if (Pos < MinNumberLength) { |
| 50 | memset(&NumBuffer[Pos], 0, |
| 51 | sizeof(NumBuffer[0]) * static_cast<uptr>(MinNumberLength - Pos)); |
| 52 | Pos = MinNumberLength; |
| 53 | } |
| 54 | RAW_CHECK(Pos > 0); |
| 55 | Pos--; |
| 56 | for (; Pos >= 0 && NumBuffer[Pos] == 0; Pos--) { |
| 57 | char c = (PadWithZero || Pos == 0) ? '0' : ' '; |
| 58 | Res += appendChar(Buffer, BufferEnd, c); |
| 59 | } |
| 60 | if (Negative && !PadWithZero) |
| 61 | Res += appendChar(Buffer, BufferEnd, '-'); |
| 62 | for (; Pos >= 0; Pos--) { |
| 63 | char Digit = static_cast<char>(NumBuffer[Pos]); |
| 64 | Digit = static_cast<char>((Digit < 10) ? '0' + Digit |
| 65 | : (Upper ? 'A' : 'a') + Digit - 10); |
| 66 | Res += appendChar(Buffer, BufferEnd, Digit); |
| 67 | } |
| 68 | return Res; |
| 69 | } |
| 70 | |
| 71 | static int appendUnsigned(char **Buffer, const char *BufferEnd, u64 Num, |
| 72 | u8 Base, u8 MinNumberLength, bool PadWithZero, |
| 73 | bool Upper) { |
| 74 | return appendNumber(Buffer, BufferEnd, Num, Base, MinNumberLength, |
| 75 | PadWithZero, /*Negative=*/false, Upper); |
| 76 | } |
| 77 | |
| 78 | static int appendSignedDecimal(char **Buffer, const char *BufferEnd, s64 Num, |
| 79 | u8 MinNumberLength, bool PadWithZero) { |
| 80 | const bool Negative = (Num < 0); |
Kostya Kortchinsky | 241449c | 2020-09-23 10:22:15 -0700 | [diff] [blame] | 81 | const u64 UnsignedNum = (Num == INT64_MIN) |
| 82 | ? static_cast<u64>(INT64_MAX) + 1 |
| 83 | : static_cast<u64>(Negative ? -Num : Num); |
| 84 | return appendNumber(Buffer, BufferEnd, UnsignedNum, 10, MinNumberLength, |
| 85 | PadWithZero, Negative, /*Upper=*/false); |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 86 | } |
| 87 | |
| 88 | // Use the fact that explicitly requesting 0 Width (%0s) results in UB and |
| 89 | // interpret Width == 0 as "no Width requested": |
| 90 | // Width == 0 - no Width requested |
| 91 | // Width < 0 - left-justify S within and pad it to -Width chars, if necessary |
| 92 | // Width > 0 - right-justify S, not implemented yet |
| 93 | static int appendString(char **Buffer, const char *BufferEnd, int Width, |
| 94 | int MaxChars, const char *S) { |
| 95 | if (!S) |
| 96 | S = "<null>"; |
| 97 | int Res = 0; |
| 98 | for (; *S; S++) { |
| 99 | if (MaxChars >= 0 && Res >= MaxChars) |
| 100 | break; |
| 101 | Res += appendChar(Buffer, BufferEnd, *S); |
| 102 | } |
| 103 | // Only the left justified strings are supported. |
| 104 | while (Width < -Res) |
| 105 | Res += appendChar(Buffer, BufferEnd, ' '); |
| 106 | return Res; |
| 107 | } |
| 108 | |
| 109 | static int appendPointer(char **Buffer, const char *BufferEnd, u64 ptr_value) { |
| 110 | int Res = 0; |
| 111 | Res += appendString(Buffer, BufferEnd, 0, -1, "0x"); |
| 112 | Res += appendUnsigned(Buffer, BufferEnd, ptr_value, 16, |
| 113 | SCUDO_POINTER_FORMAT_LENGTH, /*PadWithZero=*/true, |
| 114 | /*Upper=*/false); |
| 115 | return Res; |
| 116 | } |
| 117 | |
Kostya Kortchinsky | c79ab1b | 2021-05-24 09:26:21 -0700 | [diff] [blame] | 118 | static int formatString(char *Buffer, uptr BufferLength, const char *Format, |
| 119 | va_list Args) { |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 120 | static const char *PrintfFormatsHelp = |
| 121 | "Supported formatString formats: %([0-9]*)?(z|ll)?{d,u,x,X}; %p; " |
| 122 | "%[-]([0-9]*)?(\\.\\*)?s; %c\n"; |
| 123 | RAW_CHECK(Format); |
| 124 | RAW_CHECK(BufferLength > 0); |
| 125 | const char *BufferEnd = &Buffer[BufferLength - 1]; |
| 126 | const char *Cur = Format; |
| 127 | int Res = 0; |
| 128 | for (; *Cur; Cur++) { |
| 129 | if (*Cur != '%') { |
| 130 | Res += appendChar(&Buffer, BufferEnd, *Cur); |
| 131 | continue; |
| 132 | } |
| 133 | Cur++; |
| 134 | const bool LeftJustified = *Cur == '-'; |
| 135 | if (LeftJustified) |
| 136 | Cur++; |
| 137 | bool HaveWidth = (*Cur >= '0' && *Cur <= '9'); |
| 138 | const bool PadWithZero = (*Cur == '0'); |
| 139 | u8 Width = 0; |
| 140 | if (HaveWidth) { |
| 141 | while (*Cur >= '0' && *Cur <= '9') |
| 142 | Width = static_cast<u8>(Width * 10 + *Cur++ - '0'); |
| 143 | } |
| 144 | const bool HavePrecision = (Cur[0] == '.' && Cur[1] == '*'); |
| 145 | int Precision = -1; |
| 146 | if (HavePrecision) { |
| 147 | Cur += 2; |
| 148 | Precision = va_arg(Args, int); |
| 149 | } |
| 150 | const bool HaveZ = (*Cur == 'z'); |
| 151 | Cur += HaveZ; |
| 152 | const bool HaveLL = !HaveZ && (Cur[0] == 'l' && Cur[1] == 'l'); |
| 153 | Cur += HaveLL * 2; |
| 154 | s64 DVal; |
| 155 | u64 UVal; |
| 156 | const bool HaveLength = HaveZ || HaveLL; |
| 157 | const bool HaveFlags = HaveWidth || HaveLength; |
| 158 | // At the moment only %s supports precision and left-justification. |
| 159 | CHECK(!((Precision >= 0 || LeftJustified) && *Cur != 's')); |
| 160 | switch (*Cur) { |
| 161 | case 'd': { |
Kostya Kortchinsky | 241449c | 2020-09-23 10:22:15 -0700 | [diff] [blame] | 162 | DVal = HaveLL ? va_arg(Args, s64) |
| 163 | : HaveZ ? va_arg(Args, sptr) |
| 164 | : va_arg(Args, int); |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 165 | Res += appendSignedDecimal(&Buffer, BufferEnd, DVal, Width, PadWithZero); |
| 166 | break; |
| 167 | } |
| 168 | case 'u': |
| 169 | case 'x': |
| 170 | case 'X': { |
Kostya Kortchinsky | 241449c | 2020-09-23 10:22:15 -0700 | [diff] [blame] | 171 | UVal = HaveLL ? va_arg(Args, u64) |
| 172 | : HaveZ ? va_arg(Args, uptr) |
| 173 | : va_arg(Args, unsigned); |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 174 | const bool Upper = (*Cur == 'X'); |
| 175 | Res += appendUnsigned(&Buffer, BufferEnd, UVal, (*Cur == 'u') ? 10 : 16, |
| 176 | Width, PadWithZero, Upper); |
| 177 | break; |
| 178 | } |
| 179 | case 'p': { |
| 180 | RAW_CHECK_MSG(!HaveFlags, PrintfFormatsHelp); |
| 181 | Res += appendPointer(&Buffer, BufferEnd, va_arg(Args, uptr)); |
| 182 | break; |
| 183 | } |
| 184 | case 's': { |
| 185 | RAW_CHECK_MSG(!HaveLength, PrintfFormatsHelp); |
| 186 | // Only left-justified Width is supported. |
| 187 | CHECK(!HaveWidth || LeftJustified); |
| 188 | Res += appendString(&Buffer, BufferEnd, LeftJustified ? -Width : Width, |
| 189 | Precision, va_arg(Args, char *)); |
| 190 | break; |
| 191 | } |
| 192 | case 'c': { |
| 193 | RAW_CHECK_MSG(!HaveFlags, PrintfFormatsHelp); |
| 194 | Res += |
| 195 | appendChar(&Buffer, BufferEnd, static_cast<char>(va_arg(Args, int))); |
| 196 | break; |
| 197 | } |
| 198 | case '%': { |
| 199 | RAW_CHECK_MSG(!HaveFlags, PrintfFormatsHelp); |
| 200 | Res += appendChar(&Buffer, BufferEnd, '%'); |
| 201 | break; |
| 202 | } |
| 203 | default: { |
| 204 | RAW_CHECK_MSG(false, PrintfFormatsHelp); |
| 205 | } |
| 206 | } |
| 207 | } |
| 208 | RAW_CHECK(Buffer <= BufferEnd); |
| 209 | appendChar(&Buffer, BufferEnd + 1, '\0'); |
| 210 | return Res; |
| 211 | } |
| 212 | |
Kostya Kortchinsky | c79ab1b | 2021-05-24 09:26:21 -0700 | [diff] [blame] | 213 | int formatString(char *Buffer, uptr BufferLength, const char *Format, ...) { |
| 214 | va_list Args; |
| 215 | va_start(Args, Format); |
| 216 | int Res = formatString(Buffer, BufferLength, Format, Args); |
| 217 | va_end(Args); |
| 218 | return Res; |
| 219 | } |
| 220 | |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 221 | void ScopedString::append(const char *Format, va_list Args) { |
Dynamic Tools Team | 3e8c65b | 2019-10-18 20:00:32 +0000 | [diff] [blame] | 222 | va_list ArgsCopy; |
| 223 | va_copy(ArgsCopy, Args); |
| 224 | // formatString doesn't currently support a null buffer or zero buffer length, |
| 225 | // so in order to get the resulting formatted string length, we use a one-char |
| 226 | // buffer. |
| 227 | char C[1]; |
| 228 | const uptr AdditionalLength = |
| 229 | static_cast<uptr>(formatString(C, sizeof(C), Format, Args)) + 1; |
Vitaly Buka | 6eb890a | 2021-06-04 15:56:13 -0700 | [diff] [blame] | 230 | const uptr Length = length(); |
Dynamic Tools Team | 3e8c65b | 2019-10-18 20:00:32 +0000 | [diff] [blame] | 231 | String.resize(Length + AdditionalLength); |
Vitaly Buka | 6eb890a | 2021-06-04 15:56:13 -0700 | [diff] [blame] | 232 | const uptr FormattedLength = static_cast<uptr>(formatString( |
| 233 | String.data() + Length, String.size() - Length, Format, ArgsCopy)); |
| 234 | RAW_CHECK(data()[length()] == '\0'); |
| 235 | RAW_CHECK(FormattedLength + 1 == AdditionalLength); |
Kostya Kortchinsky | 95ae7c8 | 2020-12-02 15:19:42 -0800 | [diff] [blame] | 236 | va_end(ArgsCopy); |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 237 | } |
| 238 | |
| 239 | FORMAT(2, 3) |
| 240 | void ScopedString::append(const char *Format, ...) { |
| 241 | va_list Args; |
| 242 | va_start(Args, Format); |
| 243 | append(Format, Args); |
| 244 | va_end(Args); |
| 245 | } |
| 246 | |
| 247 | FORMAT(1, 2) |
| 248 | void Printf(const char *Format, ...) { |
| 249 | va_list Args; |
| 250 | va_start(Args, Format); |
Kostya Kortchinsky | 53aea5c | 2021-06-03 12:11:05 -0700 | [diff] [blame] | 251 | ScopedString Msg; |
Dynamic Tools Team | 517193e | 2019-09-11 14:48:41 +0000 | [diff] [blame] | 252 | Msg.append(Format, Args); |
| 253 | outputRaw(Msg.data()); |
| 254 | va_end(Args); |
| 255 | } |
| 256 | |
| 257 | } // namespace scudo |