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 | // Appends number in a given Base to buffer. If its length is less than |
18 | // |MinNumberLength|, it is padded with leading zeroes or spaces, depending |
19 | // on the value of |PadWithZero|. |
20 | void ScopedString::appendNumber(u64 AbsoluteValue, u8 Base, u8 MinNumberLength, |
21 | bool PadWithZero, bool Negative, bool Upper) { |
22 | constexpr uptr MaxLen = 30; |
23 | RAW_CHECK(Base == 10 || Base == 16); |
24 | RAW_CHECK(Base == 10 || !Negative); |
25 | RAW_CHECK(AbsoluteValue || !Negative); |
26 | RAW_CHECK(MinNumberLength < MaxLen); |
27 | if (Negative && MinNumberLength) |
28 | --MinNumberLength; |
29 | if (Negative && PadWithZero) { |
30 | String.push_back(Element: '-'); |
31 | } |
32 | uptr NumBuffer[MaxLen]; |
33 | int Pos = 0; |
34 | do { |
35 | RAW_CHECK_MSG(static_cast<uptr>(Pos) < MaxLen, |
36 | "appendNumber buffer overflow" ); |
37 | NumBuffer[Pos++] = static_cast<uptr>(AbsoluteValue % Base); |
38 | AbsoluteValue /= Base; |
39 | } while (AbsoluteValue > 0); |
40 | if (Pos < MinNumberLength) { |
41 | memset(s: &NumBuffer[Pos], c: 0, |
42 | n: sizeof(NumBuffer[0]) * static_cast<uptr>(MinNumberLength - Pos)); |
43 | Pos = MinNumberLength; |
44 | } |
45 | RAW_CHECK(Pos > 0); |
46 | Pos--; |
47 | for (; Pos >= 0 && NumBuffer[Pos] == 0; Pos--) { |
48 | char c = (PadWithZero || Pos == 0) ? '0' : ' '; |
49 | String.push_back(Element: c); |
50 | } |
51 | if (Negative && !PadWithZero) |
52 | String.push_back(Element: '-'); |
53 | for (; Pos >= 0; Pos--) { |
54 | char Digit = static_cast<char>(NumBuffer[Pos]); |
55 | Digit = static_cast<char>((Digit < 10) ? '0' + Digit |
56 | : (Upper ? 'A' : 'a') + Digit - 10); |
57 | String.push_back(Element: Digit); |
58 | } |
59 | } |
60 | |
61 | void ScopedString::appendUnsigned(u64 Num, u8 Base, u8 MinNumberLength, |
62 | bool PadWithZero, bool Upper) { |
63 | appendNumber(AbsoluteValue: Num, Base, MinNumberLength, PadWithZero, /*Negative=*/false, |
64 | Upper); |
65 | } |
66 | |
67 | void ScopedString::appendSignedDecimal(s64 Num, u8 MinNumberLength, |
68 | bool PadWithZero) { |
69 | const bool Negative = (Num < 0); |
70 | const u64 UnsignedNum = (Num == INT64_MIN) |
71 | ? static_cast<u64>(INT64_MAX) + 1 |
72 | : static_cast<u64>(Negative ? -Num : Num); |
73 | appendNumber(AbsoluteValue: UnsignedNum, Base: 10, MinNumberLength, PadWithZero, Negative, |
74 | /*Upper=*/false); |
75 | } |
76 | |
77 | // Use the fact that explicitly requesting 0 Width (%0s) results in UB and |
78 | // interpret Width == 0 as "no Width requested": |
79 | // Width == 0 - no Width requested |
80 | // Width < 0 - left-justify S within and pad it to -Width chars, if necessary |
81 | // Width > 0 - right-justify S, not implemented yet |
82 | void ScopedString::appendString(int Width, int MaxChars, const char *S) { |
83 | if (!S) |
84 | S = "<null>" ; |
85 | int NumChars = 0; |
86 | for (; *S; S++) { |
87 | if (MaxChars >= 0 && NumChars >= MaxChars) |
88 | break; |
89 | String.push_back(Element: *S); |
90 | NumChars++; |
91 | } |
92 | if (Width < 0) { |
93 | // Only left justification supported. |
94 | Width = -Width - NumChars; |
95 | while (Width-- > 0) |
96 | String.push_back(Element: ' '); |
97 | } |
98 | } |
99 | |
100 | void ScopedString::appendPointer(u64 ptr_value) { |
101 | appendString(Width: 0, MaxChars: -1, S: "0x" ); |
102 | appendUnsigned(Num: ptr_value, Base: 16, SCUDO_POINTER_FORMAT_LENGTH, |
103 | /*PadWithZero=*/true, |
104 | /*Upper=*/false); |
105 | } |
106 | |
107 | void ScopedString::vappend(const char *Format, va_list &Args) { |
108 | // Since the string contains the '\0' terminator, put our size before it |
109 | // so that push_back calls work correctly. |
110 | DCHECK(String.size() > 0); |
111 | String.resize(NewSize: String.size() - 1); |
112 | |
113 | static const char *PrintfFormatsHelp = |
114 | "Supported formats: %([0-9]*)?(z|ll)?{d,u,x,X}; %p; " |
115 | "%[-]([0-9]*)?(\\.\\*)?s; %c\n" ; |
116 | RAW_CHECK(Format); |
117 | const char *Cur = Format; |
118 | for (; *Cur; Cur++) { |
119 | if (*Cur != '%') { |
120 | String.push_back(Element: *Cur); |
121 | continue; |
122 | } |
123 | Cur++; |
124 | const bool LeftJustified = *Cur == '-'; |
125 | if (LeftJustified) |
126 | Cur++; |
127 | bool HaveWidth = (*Cur >= '0' && *Cur <= '9'); |
128 | const bool PadWithZero = (*Cur == '0'); |
129 | u8 Width = 0; |
130 | if (HaveWidth) { |
131 | while (*Cur >= '0' && *Cur <= '9') |
132 | Width = static_cast<u8>(Width * 10 + *Cur++ - '0'); |
133 | } |
134 | const bool HavePrecision = (Cur[0] == '.' && Cur[1] == '*'); |
135 | int Precision = -1; |
136 | if (HavePrecision) { |
137 | Cur += 2; |
138 | Precision = va_arg(Args, int); |
139 | } |
140 | const bool HaveZ = (*Cur == 'z'); |
141 | Cur += HaveZ; |
142 | const bool HaveLL = !HaveZ && (Cur[0] == 'l' && Cur[1] == 'l'); |
143 | Cur += HaveLL * 2; |
144 | s64 DVal; |
145 | u64 UVal; |
146 | const bool HaveLength = HaveZ || HaveLL; |
147 | const bool HaveFlags = HaveWidth || HaveLength; |
148 | // At the moment only %s supports precision and left-justification. |
149 | CHECK(!((Precision >= 0 || LeftJustified) && *Cur != 's')); |
150 | switch (*Cur) { |
151 | case 'd': { |
152 | DVal = HaveLL ? va_arg(Args, s64) |
153 | : HaveZ ? va_arg(Args, sptr) |
154 | : va_arg(Args, int); |
155 | appendSignedDecimal(Num: DVal, MinNumberLength: Width, PadWithZero); |
156 | break; |
157 | } |
158 | case 'u': |
159 | case 'x': |
160 | case 'X': { |
161 | UVal = HaveLL ? va_arg(Args, u64) |
162 | : HaveZ ? va_arg(Args, uptr) |
163 | : va_arg(Args, unsigned); |
164 | const bool Upper = (*Cur == 'X'); |
165 | appendUnsigned(Num: UVal, Base: (*Cur == 'u') ? 10 : 16, MinNumberLength: Width, PadWithZero, Upper); |
166 | break; |
167 | } |
168 | case 'p': { |
169 | RAW_CHECK_MSG(!HaveFlags, PrintfFormatsHelp); |
170 | appendPointer(va_arg(Args, uptr)); |
171 | break; |
172 | } |
173 | case 's': { |
174 | RAW_CHECK_MSG(!HaveLength, PrintfFormatsHelp); |
175 | // Only left-justified Width is supported. |
176 | CHECK(!HaveWidth || LeftJustified); |
177 | appendString(Width: LeftJustified ? -Width : Width, MaxChars: Precision, |
178 | va_arg(Args, char *)); |
179 | break; |
180 | } |
181 | case 'c': { |
182 | RAW_CHECK_MSG(!HaveFlags, PrintfFormatsHelp); |
183 | String.push_back(Element: static_cast<char>(va_arg(Args, int))); |
184 | break; |
185 | } |
186 | // In Scudo, `s64`/`u64` are supposed to use `lld` and `llu` respectively. |
187 | // However, `-Wformat` doesn't know we have a different parser for those |
188 | // placeholders and it keeps complaining the type mismatch on 64-bit |
189 | // platform which uses `ld`/`lu` for `s64`/`u64`. Therefore, in order to |
190 | // silence the warning, we turn to use `PRId64`/`PRIu64` for printing |
191 | // `s64`/`u64` and handle the `ld`/`lu` here. |
192 | case 'l': { |
193 | ++Cur; |
194 | RAW_CHECK(*Cur == 'd' || *Cur == 'u'); |
195 | |
196 | if (*Cur == 'd') { |
197 | DVal = va_arg(Args, s64); |
198 | appendSignedDecimal(Num: DVal, MinNumberLength: Width, PadWithZero); |
199 | } else { |
200 | UVal = va_arg(Args, u64); |
201 | appendUnsigned(Num: UVal, Base: 10, MinNumberLength: Width, PadWithZero, Upper: false); |
202 | } |
203 | |
204 | break; |
205 | } |
206 | case '%': { |
207 | RAW_CHECK_MSG(!HaveFlags, PrintfFormatsHelp); |
208 | String.push_back(Element: '%'); |
209 | break; |
210 | } |
211 | default: { |
212 | RAW_CHECK_MSG(false, PrintfFormatsHelp); |
213 | } |
214 | } |
215 | } |
216 | String.push_back(Element: '\0'); |
217 | if (String.back() != '\0') { |
218 | // String truncated, make sure the string is terminated properly. |
219 | // This can happen if there is no more memory when trying to resize |
220 | // the string. |
221 | String.back() = '\0'; |
222 | } |
223 | } |
224 | |
225 | void ScopedString::append(const char *Format, ...) { |
226 | va_list Args; |
227 | va_start(Args, Format); |
228 | vappend(Format, Args); |
229 | va_end(Args); |
230 | } |
231 | |
232 | void Printf(const char *Format, ...) { |
233 | va_list Args; |
234 | va_start(Args, Format); |
235 | ScopedString Msg; |
236 | Msg.vappend(Format, Args); |
237 | outputRaw(Buffer: Msg.data()); |
238 | va_end(Args); |
239 | } |
240 | |
241 | } // namespace scudo |
242 | |