• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 #include <algorithm>
17 #include <cstdarg>
18 #include <cstddef>
19 #include <cstring>
20 #include <span>
21 #include <string_view>
22 #include <type_traits>
23 #include <utility>
24 
25 #include "pw_preprocessor/compiler.h"
26 #include "pw_status/status.h"
27 #include "pw_status/status_with_size.h"
28 #include "pw_string/to_string.h"
29 
30 namespace pw {
31 
32 // StringBuilder facilitates building formatted strings in a fixed-size buffer.
33 // StringBuilders are always null terminated (unless they are constructed with
34 // an empty buffer) and never overflow. Status is tracked for each operation and
35 // an overall status is maintained, which reflects the most recent error.
36 //
37 // A StringBuilder does not own the buffer it writes to. It can be used to write
38 // strings to any buffer. The StringBuffer template class, defined below,
39 // allocates a buffer alongside a StringBuilder.
40 //
41 // StringBuilder supports C++-style << output, similar to std::ostringstream. It
42 // also supports std::string-like append functions and printf-style output.
43 //
44 // Support for custom types is added by overloading operator<< in the same
45 // namespace as the custom type. For example:
46 //
47 //   namespace my_project {
48 //
49 //   struct MyType {
50 //     int foo;
51 //     const char* bar;
52 //   };
53 //
54 //   pw::StringBuilder& operator<<(pw::StringBuilder& sb, const MyType& value) {
55 //     return sb << "MyType(" << value.foo << ", " << value.bar << ')';
56 //   }
57 //
58 //   }  // namespace my_project
59 //
60 // The ToString template function can be specialized to support custom types
61 // with StringBuilder, though overloading operator<< is generally preferred. For
62 // example:
63 //
64 //   namespace pw {
65 //
66 //   template <>
67 //   StatusWithSize ToString<MyStatus>(MyStatus value, std::span<char> buffer) {
68 //     return Copy(MyStatusString(value), buffer);
69 //   }
70 //
71 //   }  // namespace pw
72 //
73 class StringBuilder {
74  public:
75   // Creates an empty StringBuilder.
StringBuilder(std::span<char> buffer)76   constexpr StringBuilder(std::span<char> buffer) : buffer_(buffer), size_(0) {
77     NullTerminate();
78   }
StringBuilder(std::span<std::byte> buffer)79   StringBuilder(std::span<std::byte> buffer)
80       : StringBuilder(
81             {reinterpret_cast<char*>(buffer.data()), buffer.size_bytes()}) {}
82 
83   // Disallow copy/assign to avoid confusion about where the string is actually
84   // stored. StringBuffers may be copied into one another.
85   StringBuilder(const StringBuilder&) = delete;
86 
87   StringBuilder& operator=(const StringBuilder&) = delete;
88 
89   // Returns the contents of the string buffer. Always null-terminated.
data()90   const char* data() const { return buffer_.data(); }
c_str()91   const char* c_str() const { return data(); }
92 
93   // Returns a std::string_view of the contents of this StringBuilder. The
94   // std::string_view is invalidated if the StringBuilder contents change.
view()95   std::string_view view() const { return std::string_view(data(), size()); }
96 
97   // Allow implicit conversions to std::string_view so StringBuilders can be
98   // passed into functions that take a std::string_view.
string_view()99   operator std::string_view() const { return view(); }
100 
101   // Returns a std::span<const std::byte> representation of this StringBuffer.
as_bytes()102   std::span<const std::byte> as_bytes() const {
103     return std::span(reinterpret_cast<const std::byte*>(buffer_.data()), size_);
104   }
105 
106   // Returns the StringBuilder's status, which reflects the most recent error
107   // that occurred while updating the string. After an update fails, the status
108   // remains non-OK until it is cleared with clear() or clear_status(). Returns:
109   //
110   //     OK if no errors have occurred
111   //     RESOURCE_EXHAUSTED if output to the StringBuilder was truncated
112   //     INVALID_ARGUMENT if printf-style formatting failed
113   //     OUT_OF_RANGE if an operation outside the buffer was attempted
114   //
status()115   Status status() const { return status_; }
116 
117   // Returns status() and size() as a StatusWithSize.
status_with_size()118   StatusWithSize status_with_size() const {
119     return StatusWithSize(status_, size_);
120   }
121 
122   // The status from the last operation. May be OK while status() is not OK.
last_status()123   Status last_status() const { return last_status_; }
124 
125   // True if status() is OkStatus().
ok()126   bool ok() const { return status_.ok(); }
127 
128   // True if the string is empty.
empty()129   bool empty() const { return size() == 0u; }
130 
131   // Returns the current length of the string, excluding the null terminator.
size()132   size_t size() const { return size_; }
133 
134   // Returns the maximum length of the string, excluding the null terminator.
max_size()135   size_t max_size() const { return buffer_.empty() ? 0u : buffer_.size() - 1; }
136 
137   // Clears the string and resets its error state.
138   void clear();
139 
140   // Sets the statuses to OkStatus();
clear_status()141   void clear_status() {
142     status_ = OkStatus();
143     last_status_ = OkStatus();
144   }
145 
146   // Appends a single character. Stets the status to RESOURCE_EXHAUSTED if the
147   // character cannot be added because the buffer is full.
push_back(char ch)148   void push_back(char ch) { append(1, ch); }
149 
150   // Removes the last character. Sets the status to OUT_OF_RANGE if the buffer
151   // is empty (in which case the unsigned overflow is intentional).
pop_back()152   void pop_back() PW_NO_SANITIZE("unsigned-integer-overflow") {
153     resize(size() - 1);
154   }
155 
156   // Appends the provided character count times.
157   StringBuilder& append(size_t count, char ch);
158 
159   // Appends count characters from str to the end of the StringBuilder. If count
160   // exceeds the remaining space in the StringBuffer, max_size() - size()
161   // characters are appended and the status is set to RESOURCE_EXHAUSTED.
162   //
163   // str is not considered null-terminated and may contain null characters.
164   StringBuilder& append(const char* str, size_t count);
165 
166   // Appends characters from the null-terminated string to the end of the
167   // StringBuilder. If the string's length exceeds the remaining space in the
168   // buffer, max_size() - size() characters are copied and the status is set to
169   // RESOURCE_EXHAUSTED.
170   //
171   // This function uses string::Length instead of std::strlen to avoid unbounded
172   // reads if the string is not null terminated.
173   StringBuilder& append(const char* str);
174 
175   // Appends a std::string_view to the end of the StringBuilder.
176   StringBuilder& append(const std::string_view& str);
177 
178   // Appends a substring from the std::string_view to the StringBuilder. Copies
179   // up to count characters starting from pos to the end of the StringBuilder.
180   // If pos > str.size(), sets the status to OUT_OF_RANGE.
181   StringBuilder& append(const std::string_view& str,
182                         size_t pos,
183                         size_t count = std::string_view::npos);
184 
185   // Appends to the end of the StringBuilder using the << operator. This enables
186   // C++ stream-style formatted to StringBuilders.
187   template <typename T>
188   StringBuilder& operator<<(const T& value) {
189     // For std::string_view-compatible types, use the append function, which
190     // gives smaller code size.
191     if constexpr (std::is_convertible_v<T, std::string_view>) {
192       append(value);
193     } else if constexpr (std::is_convertible_v<T, std::span<const std::byte>>) {
194       WriteBytes(value);
195     } else {
196       HandleStatusWithSize(ToString(value, buffer_.subspan(size_)));
197     }
198     return *this;
199   }
200 
201   // Provide a few additional operator<< overloads that reduce code size.
202   StringBuilder& operator<<(bool value) {
203     return append(value ? "true" : "false");
204   }
205 
206   StringBuilder& operator<<(char value) {
207     push_back(value);
208     return *this;
209   }
210 
211   StringBuilder& operator<<(std::nullptr_t) {
212     return append(string::kNullPointerString);
213   }
214 
215   StringBuilder& operator<<(Status status) { return *this << status.str(); }
216 
217   // Appends a printf-style string to the end of the StringBuilder. If the
218   // formatted string does not fit, the results are truncated and the status is
219   // set to RESOURCE_EXHAUSTED.
220   //
221   // Internally, calls string::Format, which calls std::vsnprintf.
222   PW_PRINTF_FORMAT(2, 3) StringBuilder& Format(const char* format, ...);
223 
224   // Appends a vsnprintf-style string with va_list arguments to the end of the
225   // StringBuilder. If the formatted string does not fit, the results are
226   // truncated and the status is set to RESOURCE_EXHAUSTED.
227   //
228   // Internally, calls string::Format, which calls std::vsnprintf.
229   StringBuilder& FormatVaList(const char* format, va_list args);
230 
231   // Sets the StringBuilder's size. This function only truncates; if
232   // new_size > size(), it sets status to OUT_OF_RANGE and does nothing.
233   void resize(size_t new_size);
234 
235  protected:
236   // Functions to support StringBuffer copies.
StringBuilder(std::span<char> buffer,const StringBuilder & other)237   constexpr StringBuilder(std::span<char> buffer, const StringBuilder& other)
238       : buffer_(buffer),
239         size_(other.size_),
240         status_(other.status_),
241         last_status_(other.last_status_) {}
242 
243   void CopySizeAndStatus(const StringBuilder& other);
244 
245  private:
246   void WriteBytes(std::span<const std::byte> data);
247 
248   size_t ResizeAndTerminate(size_t chars_to_append);
249 
250   void HandleStatusWithSize(StatusWithSize written);
251 
NullTerminate()252   constexpr void NullTerminate() {
253     if (!buffer_.empty()) {
254       buffer_[size_] = '\0';
255     }
256   }
257 
258   void SetErrorStatus(Status status);
259 
260   const std::span<char> buffer_;
261 
262   size_t size_;
263   Status status_;
264   Status last_status_;
265 };
266 
267 // StringBuffers declare a buffer along with a StringBuilder. StringBuffer can
268 // be used as a statically allocated replacement for std::ostringstream or
269 // std::string. For example:
270 //
271 //   StringBuffer<32> str;
272 //   str << "The answer is " << number << "!";  // with number = 42
273 //   str.c_str();  // null terminated C string "The answer is 42."
274 //   str.view();   // std::string_view of "The answer is 42."
275 //
276 template <size_t kSizeBytes>
277 class StringBuffer : public StringBuilder {
278  public:
StringBuffer()279   StringBuffer() : StringBuilder(buffer_) {}
280 
281   // StringBuffers of the same size may be copied and assigned into one another.
StringBuffer(const StringBuffer & other)282   StringBuffer(const StringBuffer& other) : StringBuilder(buffer_, other) {
283     CopyContents(other);
284   }
285 
286   // A smaller StringBuffer may be copied or assigned into a larger one.
287   template <size_t kOtherSizeBytes>
StringBuffer(const StringBuffer<kOtherSizeBytes> & other)288   StringBuffer(const StringBuffer<kOtherSizeBytes>& other)
289       : StringBuilder(buffer_, other) {
290     static_assert(StringBuffer<kOtherSizeBytes>::max_size() <= max_size(),
291                   "A StringBuffer cannot be copied into a smaller buffer");
292     CopyContents(other);
293   }
294 
295   template <size_t kOtherSizeBytes>
296   StringBuffer& operator=(const StringBuffer<kOtherSizeBytes>& other) {
297     assign<kOtherSizeBytes>(other);
298     return *this;
299   }
300 
301   StringBuffer& operator=(const StringBuffer& other) {
302     assign<kSizeBytes>(other);
303     return *this;
304   }
305 
306   template <size_t kOtherSizeBytes>
assign(const StringBuffer<kOtherSizeBytes> & other)307   StringBuffer& assign(const StringBuffer<kOtherSizeBytes>& other) {
308     static_assert(StringBuffer<kOtherSizeBytes>::max_size() <= max_size(),
309                   "A StringBuffer cannot be copied into a smaller buffer");
310     CopySizeAndStatus(other);
311     CopyContents(other);
312     return *this;
313   }
314 
315   // Returns the maximum length of the string, excluding the null terminator.
max_size()316   static constexpr size_t max_size() { return kSizeBytes - 1; }
317 
318   // Returns a StringBuffer<kSizeBytes>& instead of a generic StringBuilder& for
319   // append calls and stream-style operations.
320   template <typename... Args>
append(Args &&...args)321   StringBuffer& append(Args&&... args) {
322     StringBuilder::append(std::forward<Args>(args)...);
323     return *this;
324   }
325 
326   template <typename T>
327   StringBuffer& operator<<(T&& value) {
328     static_cast<StringBuilder&>(*this) << std::forward<T>(value);
329     return *this;
330   }
331 
332  private:
333   template <size_t kOtherSize>
CopyContents(const StringBuffer<kOtherSize> & other)334   void CopyContents(const StringBuffer<kOtherSize>& other) {
335     std::memcpy(buffer_, other.data(), other.size() + 1);  // include the \0
336   }
337 
338   static_assert(kSizeBytes >= 1u, "StringBuffers must be at least 1 byte long");
339   char buffer_[kSizeBytes];
340 };
341 
342 namespace string_internal {
343 
344 // Internal code for determining the default size of StringBuffers created with
345 // MakeString.
346 //
347 // StringBuffers created with MakeString default to at least 24 bytes. This is
348 // large enough to fit the largest 64-bit integer (20 digits plus a \0), rounded
349 // up to the nearest multiple of 4.
350 inline constexpr size_t kDefaultMinimumStringBufferSize = 24;
351 
352 // By default, MakeString uses a buffer size large enough to fit all string
353 // literal arguments. ArgLength uses this value as an estimate of the number of
354 // characters needed to represent a non-string argument.
355 inline constexpr size_t kDefaultArgumentSize = 4;
356 
357 // Returns a string literal's length or kDefaultArgumentSize for non-strings.
358 template <typename T>
ArgLength()359 constexpr size_t ArgLength() {
360   using Arg = std::remove_reference_t<T>;
361 
362   // If the argument is an array of const char, assume it is a string literal.
363   if constexpr (std::is_array_v<Arg>) {
364     using Element = std::remove_reference_t<decltype(std::declval<Arg>()[0])>;
365 
366     if constexpr (std::is_same_v<Element, const char>) {
367       return std::extent_v<Arg> > 0u ? std::extent_v<Arg> - 1 : size_t(0);
368     }
369   }
370 
371   return kDefaultArgumentSize;
372 }
373 
374 // This function returns the default string buffer size used by MakeString.
375 template <typename... Args>
DefaultStringBufferSize()376 constexpr size_t DefaultStringBufferSize() {
377   return std::max((size_t(1) + ... + ArgLength<Args>()),
378                   kDefaultMinimumStringBufferSize);
379 }
380 
381 // Internal version of MakeString with const reference arguments instead of
382 // deduced types, which include the lengths of string literals. Having this
383 // function can reduce code size.
384 template <size_t kBufferSize, typename... Args>
InitializeStringBuffer(const Args &...args)385 auto InitializeStringBuffer(const Args&... args) {
386   return (StringBuffer<kBufferSize>() << ... << args);
387 }
388 
389 }  // namespace string_internal
390 
391 // Makes a StringBuffer with a string version of a series of values. This is
392 // useful for creating and initializing a StringBuffer or for conveniently
393 // getting a null-terminated string. For example:
394 //
395 //     LOG_INFO("The MAC address is %s", MakeString(mac_address).c_str());
396 //
397 // By default, the buffer size is 24 bytes, large enough to fit any 64-bit
398 // integer. If string literal arguments are provided, the default size will be
399 // large enough to fit them and a null terminator, plus 4 additional bytes for
400 // each argument. To use a fixed buffer size, set the kBufferSize template
401 // argument. For example:
402 //
403 //   // Creates a default-size StringBuffer (10 + 10 + 4 + 1 + 1 = 26 bytes).
404 //   auto sb = MakeString("1234567890", "1234567890", number, "!");
405 //
406 //   // Creates a 32-byte StringBuffer.
407 //   auto sb = MakeString<32>("1234567890", "1234567890", number, "!");
408 //
409 // Keep in mind that each argument to MakeString expands to a function call.
410 // MakeString may increase code size more than an equivalent pw::string::Format
411 // (or std::snprintf) call.
412 template <size_t kBufferSize = 0u, typename... Args>
MakeString(Args &&...args)413 auto MakeString(Args&&... args) {
414   constexpr size_t kSize =
415       kBufferSize == 0u ? string_internal::DefaultStringBufferSize<Args...>()
416                         : kBufferSize;
417   return string_internal::InitializeStringBuffer<kSize>(args...);
418 }
419 
420 }  // namespace pw
421