Fix printf precision/field width being limited by internal buffer size (#799)
The C standard, when defining field width and precision, never gives
any limit on the values used for them (except, I believe, that they
fit within an int). In other words, if the user gives a field width of
32145 and a precision of 9218, the implementation has to handle these
values correctly. However, when such kinds of high numbers are used
with integer conversions, cosmopolitan is limited by an internal
buffer size of 144, which means precisions and field widths have to
fit within this, which violates the standard.
This means that for example, the following program:
#include <stdio.h>
#include <string.h>
int main()
{
char buf2[512] = {};
int i = snprintf(buf2, sizeof(buf2), "%.9999u", 10);
printf("%d %zu\n", i, strlen(buf2));
}
would, instead of printing "9999 511" (the correct output), instead
print "144 144" under cosmopolitan.
This patch fixes this.
2023-04-04 18:16:34 +00:00
|
|
|
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
|
|
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
|
|
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
|
|
│ Copyright 2023 Gabriel Ravier │
|
|
|
|
│ │
|
|
|
|
│ Permission to use, copy, modify, and/or distribute this software for │
|
|
|
|
│ any purpose with or without fee is hereby granted, provided that the │
|
|
|
|
│ above copyright notice and this permission notice appear in all copies. │
|
|
|
|
│ │
|
|
|
|
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
|
|
|
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
|
|
|
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
|
|
|
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
|
|
|
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
|
|
|
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
|
|
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
|
|
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
|
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
2023-09-07 23:03:19 +00:00
|
|
|
#include "libc/stdio/stdio.h"
|
Fix printf precision/field width being limited by internal buffer size (#799)
The C standard, when defining field width and precision, never gives
any limit on the values used for them (except, I believe, that they
fit within an int). In other words, if the user gives a field width of
32145 and a precision of 9218, the implementation has to handle these
values correctly. However, when such kinds of high numbers are used
with integer conversions, cosmopolitan is limited by an internal
buffer size of 144, which means precisions and field widths have to
fit within this, which violates the standard.
This means that for example, the following program:
#include <stdio.h>
#include <string.h>
int main()
{
char buf2[512] = {};
int i = snprintf(buf2, sizeof(buf2), "%.9999u", 10);
printf("%d %zu\n", i, strlen(buf2));
}
would, instead of printing "9999 511" (the correct output), instead
print "144 144" under cosmopolitan.
This patch fixes this.
2023-04-04 18:16:34 +00:00
|
|
|
#include "libc/str/str.h"
|
|
|
|
#include "libc/testlib/testlib.h"
|
|
|
|
|
|
|
|
TEST(snprintf, testVeryLargePrecision) {
|
|
|
|
char buf[512] = {};
|
|
|
|
int i = snprintf(buf, sizeof(buf), "%.9999u", 10);
|
|
|
|
|
|
|
|
ASSERT_EQ(i, 9999);
|
|
|
|
ASSERT_EQ(strlen(buf), 511);
|
|
|
|
}
|
2024-08-30 02:07:05 +00:00
|
|
|
|
|
|
|
TEST(snprintf, testPlusFlagOnChar) {
|
|
|
|
char buf[10] = {};
|
|
|
|
int i = snprintf(buf, sizeof(buf), "%+c", '=');
|
|
|
|
|
|
|
|
ASSERT_EQ(i, 1);
|
|
|
|
ASSERT_STREQ(buf, "=");
|
|
|
|
}
|
2024-09-01 20:10:48 +00:00
|
|
|
|
|
|
|
TEST(snprintf, testInf) {
|
|
|
|
char buf[10] = {};
|
|
|
|
int i = snprintf(buf, sizeof(buf), "%f", 1.0 / 0.0);
|
|
|
|
|
|
|
|
ASSERT_EQ(i, 3);
|
|
|
|
ASSERT_STREQ(buf, "inf");
|
|
|
|
|
|
|
|
memset(buf, 0, 4);
|
|
|
|
i = snprintf(buf, sizeof(buf), "%Lf", 1.0L / 0.0L);
|
|
|
|
ASSERT_EQ(i, 3);
|
|
|
|
ASSERT_STREQ(buf, "inf");
|
|
|
|
|
|
|
|
memset(buf, 0, 4);
|
|
|
|
i = snprintf(buf, sizeof(buf), "%e", 1.0 / 0.0);
|
|
|
|
ASSERT_EQ(i, 3);
|
|
|
|
ASSERT_STREQ(buf, "inf");
|
|
|
|
|
|
|
|
memset(buf, 0, 4);
|
|
|
|
i = snprintf(buf, sizeof(buf), "%Le", 1.0L / 0.0L);
|
|
|
|
ASSERT_EQ(i, 3);
|
|
|
|
ASSERT_STREQ(buf, "inf");
|
|
|
|
|
|
|
|
memset(buf, 0, 4);
|
|
|
|
i = snprintf(buf, sizeof(buf), "%g", 1.0 / 0.0);
|
|
|
|
ASSERT_EQ(i, 3);
|
|
|
|
ASSERT_STREQ(buf, "inf");
|
|
|
|
|
|
|
|
memset(buf, 0, 4);
|
|
|
|
i = snprintf(buf, sizeof(buf), "%Lg", 1.0L / 0.0L);
|
|
|
|
ASSERT_EQ(i, 3);
|
|
|
|
ASSERT_STREQ(buf, "inf");
|
|
|
|
|
|
|
|
for (i = 4; i < 10; ++i)
|
|
|
|
ASSERT_EQ(buf[i], '\0');
|
|
|
|
}
|
Add POSIX's C conversion specifier to printf funcs (#1276)
POSIX specifies the C conversion specifier as being "equivalent to %lc",
i.e. printf("%C", arg) is equivalent in behaviour to printf("%lc", arg).
This patch implements this conversion specifier, and adds a test for it,
alongside another test, which ensures that va_arg uses the correct size,
even though we set signbit to 63 in the code (which one might think will
result in the wrong size of argument being va_arg-ed, but having signbit
set to 63 is in fact what __fmt_stoa expects and is a requirement for it
properly formatting the wchar_t argument - this does not result in wrong
usage of va_arg because the implementation of the c conversion specifier
(which the implementation of the C conversion specifier fallsthrough to)
always calls va_arg with an argument type of int, to avoid the very same
bug occuring with %lc, as the l length modifier also sets signbit to 63)
2024-09-03 07:33:55 +00:00
|
|
|
|
|
|
|
TEST(snprintf, testUppercaseCConversionSpecifier) {
|
|
|
|
char buf[10] = {};
|
|
|
|
int i = snprintf(buf, sizeof(buf), "%C", L'a');
|
|
|
|
|
|
|
|
ASSERT_EQ(i, 1);
|
|
|
|
ASSERT_STREQ(buf, "a");
|
|
|
|
|
|
|
|
i = snprintf(buf, sizeof(buf), "%C", L'☺');
|
|
|
|
ASSERT_EQ(i, 3);
|
|
|
|
ASSERT_STREQ(buf, "☺");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure we don't va_arg the wrong argument size on wide character
|
|
|
|
// conversion specifiers
|
|
|
|
TEST(snprintf,
|
|
|
|
testWideCConversionSpecifierWithLotsOfArgumentsBeforeAndOneAfter) {
|
|
|
|
char buf[20] = {};
|
|
|
|
int i = snprintf(buf, sizeof(buf), "%d%d%d%d%d%d%d%d%lc%d", 0, 0, 0, 0, 0, 0,
|
|
|
|
0, 0, L'x', 1);
|
|
|
|
|
|
|
|
ASSERT_EQ(i, 10);
|
|
|
|
ASSERT_STREQ(buf, "00000000x1");
|
|
|
|
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
i = snprintf(buf, sizeof(buf), "%d%d%d%d%d%d%d%d%C%d", 0, 0, 0, 0, 0, 0, 0, 0,
|
|
|
|
L'x', 1);
|
|
|
|
ASSERT_EQ(i, 10);
|
|
|
|
ASSERT_STREQ(buf, "00000000x1");
|
|
|
|
}
|