xref: /freebsd/contrib/llvm-project/compiler-rt/lib/scudo/standalone/string_utils.cpp (revision 2e3507c25e42292b45a5482e116d278f5515d04d)
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);
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);
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 
118 static int formatString(char *Buffer, uptr BufferLength, const char *Format,
119                         va_list Args) {
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': {
162       DVal = HaveLL  ? va_arg(Args, s64)
163              : HaveZ ? va_arg(Args, sptr)
164                      : va_arg(Args, int);
165       Res += appendSignedDecimal(&Buffer, BufferEnd, DVal, Width, PadWithZero);
166       break;
167     }
168     case 'u':
169     case 'x':
170     case 'X': {
171       UVal = HaveLL  ? va_arg(Args, u64)
172              : HaveZ ? va_arg(Args, uptr)
173                      : va_arg(Args, unsigned);
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     // In Scudo, `s64`/`u64` are supposed to use `lld` and `llu` respectively.
199     // However, `-Wformat` doesn't know we have a different parser for those
200     // placeholders and it keeps complaining the type mismatch on 64-bit
201     // platform which uses `ld`/`lu` for `s64`/`u64`. Therefore, in order to
202     // silence the warning, we turn to use `PRId64`/`PRIu64` for printing
203     // `s64`/`u64` and handle the `ld`/`lu` here.
204     case 'l': {
205       ++Cur;
206       RAW_CHECK(*Cur == 'd' || *Cur == 'u');
207 
208       if (*Cur == 'd') {
209         DVal = va_arg(Args, s64);
210         Res +=
211             appendSignedDecimal(&Buffer, BufferEnd, DVal, Width, PadWithZero);
212       } else {
213         UVal = va_arg(Args, u64);
214         Res += appendUnsigned(&Buffer, BufferEnd, UVal, 10, Width, PadWithZero,
215                               false);
216       }
217 
218       break;
219     }
220     case '%': {
221       RAW_CHECK_MSG(!HaveFlags, PrintfFormatsHelp);
222       Res += appendChar(&Buffer, BufferEnd, '%');
223       break;
224     }
225     default: {
226       RAW_CHECK_MSG(false, PrintfFormatsHelp);
227     }
228     }
229   }
230   RAW_CHECK(Buffer <= BufferEnd);
231   appendChar(&Buffer, BufferEnd + 1, '\0');
232   return Res;
233 }
234 
235 int formatString(char *Buffer, uptr BufferLength, const char *Format, ...) {
236   va_list Args;
237   va_start(Args, Format);
238   int Res = formatString(Buffer, BufferLength, Format, Args);
239   va_end(Args);
240   return Res;
241 }
242 
243 void ScopedString::vappend(const char *Format, va_list Args) {
244   va_list ArgsCopy;
245   va_copy(ArgsCopy, Args);
246   // formatString doesn't currently support a null buffer or zero buffer length,
247   // so in order to get the resulting formatted string length, we use a one-char
248   // buffer.
249   char C[1];
250   const uptr AdditionalLength =
251       static_cast<uptr>(formatString(C, sizeof(C), Format, Args)) + 1;
252   const uptr Length = length();
253   String.resize(Length + AdditionalLength);
254   const uptr FormattedLength = static_cast<uptr>(formatString(
255       String.data() + Length, String.size() - Length, Format, ArgsCopy));
256   RAW_CHECK(data()[length()] == '\0');
257   RAW_CHECK(FormattedLength + 1 == AdditionalLength);
258   va_end(ArgsCopy);
259 }
260 
261 void ScopedString::append(const char *Format, ...) {
262   va_list Args;
263   va_start(Args, Format);
264   vappend(Format, Args);
265   va_end(Args);
266 }
267 
268 void Printf(const char *Format, ...) {
269   va_list Args;
270   va_start(Args, Format);
271   ScopedString Msg;
272   Msg.vappend(Format, Args);
273   outputRaw(Msg.data());
274   va_end(Args);
275 }
276 
277 } // namespace scudo
278