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
17static 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|.
28static 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
71static 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
78static int appendSignedDecimal(char **Buffer, const char *BufferEnd, s64 Num,
79                               u8 MinNumberLength, bool PadWithZero) {
80  const bool Negative = (Num < 0);
81  return appendNumber(Buffer, BufferEnd,
82                      static_cast<u64>(Negative ? -Num : Num), 10,
83                      MinNumberLength, PadWithZero, Negative,
84                      /*Upper=*/false);
85}
86
87// Use the fact that explicitly requesting 0 Width (%0s) results in UB and
88// interpret Width == 0 as "no Width requested":
89// Width == 0 - no Width requested
90// Width  < 0 - left-justify S within and pad it to -Width chars, if necessary
91// Width  > 0 - right-justify S, not implemented yet
92static int appendString(char **Buffer, const char *BufferEnd, int Width,
93                        int MaxChars, const char *S) {
94  if (!S)
95    S = "<null>";
96  int Res = 0;
97  for (; *S; S++) {
98    if (MaxChars >= 0 && Res >= MaxChars)
99      break;
100    Res += appendChar(Buffer, BufferEnd, *S);
101  }
102  // Only the left justified strings are supported.
103  while (Width < -Res)
104    Res += appendChar(Buffer, BufferEnd, ' ');
105  return Res;
106}
107
108static int appendPointer(char **Buffer, const char *BufferEnd, u64 ptr_value) {
109  int Res = 0;
110  Res += appendString(Buffer, BufferEnd, 0, -1, "0x");
111  Res += appendUnsigned(Buffer, BufferEnd, ptr_value, 16,
112                        SCUDO_POINTER_FORMAT_LENGTH, /*PadWithZero=*/true,
113                        /*Upper=*/false);
114  return Res;
115}
116
117int formatString(char *Buffer, uptr BufferLength, const char *Format,
118                 va_list Args) {
119  static const char *PrintfFormatsHelp =
120      "Supported formatString formats: %([0-9]*)?(z|ll)?{d,u,x,X}; %p; "
121      "%[-]([0-9]*)?(\\.\\*)?s; %c\n";
122  RAW_CHECK(Format);
123  RAW_CHECK(BufferLength > 0);
124  const char *BufferEnd = &Buffer[BufferLength - 1];
125  const char *Cur = Format;
126  int Res = 0;
127  for (; *Cur; Cur++) {
128    if (*Cur != '%') {
129      Res += appendChar(&Buffer, BufferEnd, *Cur);
130      continue;
131    }
132    Cur++;
133    const bool LeftJustified = *Cur == '-';
134    if (LeftJustified)
135      Cur++;
136    bool HaveWidth = (*Cur >= '0' && *Cur <= '9');
137    const bool PadWithZero = (*Cur == '0');
138    u8 Width = 0;
139    if (HaveWidth) {
140      while (*Cur >= '0' && *Cur <= '9')
141        Width = static_cast<u8>(Width * 10 + *Cur++ - '0');
142    }
143    const bool HavePrecision = (Cur[0] == '.' && Cur[1] == '*');
144    int Precision = -1;
145    if (HavePrecision) {
146      Cur += 2;
147      Precision = va_arg(Args, int);
148    }
149    const bool HaveZ = (*Cur == 'z');
150    Cur += HaveZ;
151    const bool HaveLL = !HaveZ && (Cur[0] == 'l' && Cur[1] == 'l');
152    Cur += HaveLL * 2;
153    s64 DVal;
154    u64 UVal;
155    const bool HaveLength = HaveZ || HaveLL;
156    const bool HaveFlags = HaveWidth || HaveLength;
157    // At the moment only %s supports precision and left-justification.
158    CHECK(!((Precision >= 0 || LeftJustified) && *Cur != 's'));
159    switch (*Cur) {
160    case 'd': {
161      DVal = HaveLL ? va_arg(Args, s64)
162                    : HaveZ ? va_arg(Args, sptr) : va_arg(Args, int);
163      Res += appendSignedDecimal(&Buffer, BufferEnd, DVal, Width, PadWithZero);
164      break;
165    }
166    case 'u':
167    case 'x':
168    case 'X': {
169      UVal = HaveLL ? va_arg(Args, u64)
170                    : HaveZ ? va_arg(Args, uptr) : va_arg(Args, unsigned);
171      const bool Upper = (*Cur == 'X');
172      Res += appendUnsigned(&Buffer, BufferEnd, UVal, (*Cur == 'u') ? 10 : 16,
173                            Width, PadWithZero, Upper);
174      break;
175    }
176    case 'p': {
177      RAW_CHECK_MSG(!HaveFlags, PrintfFormatsHelp);
178      Res += appendPointer(&Buffer, BufferEnd, va_arg(Args, uptr));
179      break;
180    }
181    case 's': {
182      RAW_CHECK_MSG(!HaveLength, PrintfFormatsHelp);
183      // Only left-justified Width is supported.
184      CHECK(!HaveWidth || LeftJustified);
185      Res += appendString(&Buffer, BufferEnd, LeftJustified ? -Width : Width,
186                          Precision, va_arg(Args, char *));
187      break;
188    }
189    case 'c': {
190      RAW_CHECK_MSG(!HaveFlags, PrintfFormatsHelp);
191      Res +=
192          appendChar(&Buffer, BufferEnd, static_cast<char>(va_arg(Args, int)));
193      break;
194    }
195    case '%': {
196      RAW_CHECK_MSG(!HaveFlags, PrintfFormatsHelp);
197      Res += appendChar(&Buffer, BufferEnd, '%');
198      break;
199    }
200    default: {
201      RAW_CHECK_MSG(false, PrintfFormatsHelp);
202    }
203    }
204  }
205  RAW_CHECK(Buffer <= BufferEnd);
206  appendChar(&Buffer, BufferEnd + 1, '\0');
207  return Res;
208}
209
210void ScopedString::append(const char *Format, va_list Args) {
211  DCHECK_LT(Length, String.size());
212  va_list ArgsCopy;
213  va_copy(ArgsCopy, Args);
214  // formatString doesn't currently support a null buffer or zero buffer length,
215  // so in order to get the resulting formatted string length, we use a one-char
216  // buffer.
217  char C[1];
218  const uptr AdditionalLength =
219      static_cast<uptr>(formatString(C, sizeof(C), Format, Args)) + 1;
220  String.resize(Length + AdditionalLength);
221  formatString(String.data() + Length, AdditionalLength, Format, ArgsCopy);
222  Length = strlen(String.data());
223  CHECK_LT(Length, String.size());
224}
225
226FORMAT(2, 3)
227void ScopedString::append(const char *Format, ...) {
228  va_list Args;
229  va_start(Args, Format);
230  append(Format, Args);
231  va_end(Args);
232}
233
234FORMAT(1, 2)
235void Printf(const char *Format, ...) {
236  va_list Args;
237  va_start(Args, Format);
238  ScopedString Msg(1024);
239  Msg.append(Format, Args);
240  outputRaw(Msg.data());
241  va_end(Args);
242}
243
244} // namespace scudo
245