Fix issues 774, 782 and 789 (printf precision bugs) (#790)

The C standard states that, within the context of a printf-family
function, when specifying the precision of a conversion specification:

> A negative precision argument is taken as if the precision were
> omitted.
- Quoth the C Standard, 7.23.6.1. The fprintf function

Cosmopolitan instead treated negative precision arguments as
though they had a value of 0, which was non-conforming. This
change fixes that. Another issue we found relates to:

> For o conversion, it increases the precision, if and only if
> necessary, to force the first digit of the result to be a zero (if
> the value and precision are both 0, a single 0 is printed).
- Quoth the C standard, 7.23.6.1.6. The fprintf function

When printing numbers in their alternative form, with a precision and
with a conversion specifier of o (octal), Cosmopolitan wasn't following
the standard in two ways:

1. When printing a value with a precision that results in 0-padding,
   cosmopolitan would still add an extra 0 even though this should be
   done "if and only if necessary"
2. When printing a value of 0 with a precision of 0, nothing is
   printed, even though the standard specifically states that a single
   0 is printed in this case

This change fixes those issues too. Furthermore, regression tests have
been introduced to ensure Cosmopolitan continues to be conformant
going forward.

Fixes #774 
Fixes #782 
Fixes #789
This commit is contained in:
Gabriel Ravier 2023-03-29 10:11:48 +02:00 committed by GitHub
parent 2f4335e081
commit 7f925e6be9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 33 additions and 4 deletions

View file

@ -16,10 +16,11 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/fmt/fmt.h"
#include "libc/assert.h" #include "libc/assert.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/fmt/conv.h" #include "libc/fmt/conv.h"
#include "libc/fmt/fmt.h"
#include "libc/fmt/fmt.internal.h" #include "libc/fmt/fmt.internal.h"
#include "libc/fmt/internal.h" #include "libc/fmt/internal.h"
#include "libc/fmt/itoa.h" #include "libc/fmt/itoa.h"
@ -255,6 +256,7 @@ _Hide int __fmt(void *fn, void *arg, const char *format, va_list va) {
} }
if (prec < 0) { if (prec < 0) {
prec = 0; prec = 0;
flags &= ~FLAGS_PRECISION;
} }
// evaluate length field // evaluate length field

View file

@ -46,8 +46,8 @@ static int __fmt_ntoa_format(int out(const char *, void *, size_t), void *arg,
} }
/* handle hash */ /* handle hash */
if (flags & FLAGS_HASH) { if (flags & FLAGS_HASH) {
if (!(flags & FLAGS_PRECISION) && len && if ((!(flags & FLAGS_PRECISION) || log2base == 3) && len &&
((len == prec) || (len == width)) && buf[len - 1] == '0') { ((len >= prec) || (len >= width)) && buf[len - 1] == '0') {
len--; len--;
if (len && (log2base == 4 || log2base == 1) && buf[len - 1] == '0') { if (len && (log2base == 4 || log2base == 1) && buf[len - 1] == '0') {
len--; len--;
@ -94,7 +94,9 @@ int __fmt_ntoa2(int out(const char *, void *, size_t), void *arg,
unsigned len, count, digit; unsigned len, count, digit;
char buf[BUFFER_SIZE]; char buf[BUFFER_SIZE];
len = 0; len = 0;
if (!value) flags &= ~FLAGS_HASH; /* we check for log2base != 3 because otherwise we'll print nothing for a value of 0 with precision 0 when # mandates that one be printed */
if (!value && log2base != 3)
flags &= ~FLAGS_HASH;
if (value || !(flags & FLAGS_PRECISION)) { if (value || !(flags & FLAGS_PRECISION)) {
count = 0; count = 0;
do { do {

View file

@ -100,11 +100,36 @@ TEST(fmt, x) {
_gc(xasprintf("%#020hX", (short)0xABCDEF))); _gc(xasprintf("%#020hX", (short)0xABCDEF)));
} }
TEST(fmt, o) {
EXPECT_STREQ("0000000000000037777777634", _gc(xasprintf("%#.25o", -100)));
EXPECT_STREQ("0001777777777777777777634", _gc(xasprintf("%#.25lo", -100L)));
EXPECT_STREQ("0001777777777777777777634", _gc(xasprintf("%#.25llo", -100LL)));
EXPECT_STREQ("0", _gc(xasprintf("%#.o", 0)));
}
TEST(fmt, b) { TEST(fmt, b) {
EXPECT_STREQ("000010100 ", _gc(xasprintf("%-14.9b", 20))); EXPECT_STREQ("000010100 ", _gc(xasprintf("%-14.9b", 20)));
} }
TEST(fmt, s) { TEST(fmt, s) {
EXPECT_STREQ("123456", _gc(xasprintf("%4.*s", -5, "123456")));
EXPECT_STREQ("123456789", _gc(xasprintf("%4.*s", -5, "123456789")));
EXPECT_STREQ("12345678901234567890",
_gc(xasprintf("%4.*s", -5, "12345678901234567890")));
EXPECT_STREQ("123456789012345678901234567890",
_gc(xasprintf("%4.*s", -5, "123456789012345678901234567890")));
EXPECT_STREQ(
"1234567890123456789012345678901234567890",
_gc(xasprintf("%4.*s", -5, "1234567890123456789012345678901234567890")));
EXPECT_STREQ(
"12345678901234567890123456789012345678901234567890",
_gc(xasprintf("%4.*s", -5,
"12345678901234567890123456789012345678901234567890")));
EXPECT_STREQ(
"123456789012345678901234567890123456789012345678901234567890",
_gc(xasprintf(
"%4.*s", -5,
"123456789012345678901234567890123456789012345678901234567890")));
EXPECT_STREQ("Wide character output test", EXPECT_STREQ("Wide character output test",
_gc(xasprintf("%S", L"Wide character output test"))); _gc(xasprintf("%S", L"Wide character output test")));
} }