1// Copyright 2016 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <fbl/string_printf.h>
6
7#include <stdarg.h>
8#include <stddef.h>
9#include <stdio.h>
10
11#include <zircon/assert.h>
12#include <fbl/unique_ptr.h>
13
14namespace fbl {
15
16String StringPrintf(const char* format, ...) {
17    va_list ap;
18    va_start(ap, format);
19    String rv = StringVPrintf(format, ap);
20    va_end(ap);
21    return rv;
22}
23
24String StringVPrintf(const char* format, va_list ap) {
25    // Size of the small stack buffer to use first. This should be kept in sync
26    // with the numbers in StringPrintfTest.StringPrintf_Boundary.
27    constexpr size_t kStackBufferSize = 1024u;
28
29    // First, try with a small buffer on the stack.
30    char stack_buf[kStackBufferSize];
31    // Copy |ap| (which can only be used once), in case we need to retry.
32    va_list ap_copy;
33    va_copy(ap_copy, ap);
34    int result = vsnprintf(stack_buf, kStackBufferSize, format, ap_copy);
35    va_end(ap_copy);
36    if (result < 0) {
37        // As far as I can tell, we'd only get |EOVERFLOW| if the result is so large
38        // that it can't be represented by an |int| (in which case retrying would be
39        // futile), so Chromium's implementation is wrong.
40        return String();
41    }
42    // |result| should be the number of characters we need, not including the
43    // terminating null. However, |vsnprintf()| always null-terminates!
44    size_t output_size = static_cast<size_t>(result);
45    // Check if the output fit into our stack buffer. This is "<" not "<=", since
46    // |vsnprintf()| will null-terminate.
47    if (output_size < kStackBufferSize) {
48        // It fit.
49        return String(stack_buf, static_cast<size_t>(result));
50    }
51
52    // Since we have the required output size, we can just heap allocate that.
53    // (Add 1 because |vsnprintf()| will always null-terminate.)
54    size_t heap_buf_size = output_size + 1u;
55    fbl::unique_ptr<char[]> heap_buf(new char[heap_buf_size]);
56    result = vsnprintf(heap_buf.get(), heap_buf_size, format, ap);
57    ZX_ASSERT(result >= 0 && static_cast<size_t>(result) == output_size);
58    return String(heap_buf.get(), static_cast<size_t>(result));
59}
60
61} // namespace fbl
62