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
15namespace 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|.
20void 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
61void 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
67void 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
82void 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
100void 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
107void 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
225void ScopedString::append(const char *Format, ...) {
226 va_list Args;
227 va_start(Args, Format);
228 vappend(Format, Args);
229 va_end(Args);
230}
231
232void 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