Skip to content

Commit bff2f0b

Browse files
committed
Correct the behavior and return value of snprintf()
According to C99 7.19.6.5, it explicitly defines the behavior and return value of snprintf(). However, consider the following description in that section: * If n is zero, nothing is written. * The snprintf function returns the number of characters that would have been written had n been sufficiently large. While validating snprintf() in the built-in C library, it was found that the behavior and/or the return value did not match the above description. Therefore, these changes fix the implementation of snprintf() to comply with the specification, and the related test cases are also adjusted to verify the behavior and return value of snprintf().
1 parent ff83f01 commit bff2f0b

File tree

6 files changed

+113
-41
lines changed

6 files changed

+113
-41
lines changed

lib/c.c

Lines changed: 88 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -210,16 +210,55 @@ void __str_base16(char *pb, int val)
210210
}
211211
}
212212

213-
int __format(char *buffer,
213+
typedef struct {
214+
char *buf;
215+
int n;
216+
} fmtbuf_t;
217+
218+
void __fmtbuf_write_char(fmtbuf_t *fmtbuf, int val)
219+
{
220+
/*
221+
* Write the given character when n is greater than 1.
222+
* This means preserving one position for the null character.
223+
*/
224+
if (fmtbuf->n <= 1)
225+
return;
226+
227+
char ch = val & 0xFF;
228+
fmtbuf->buf[0] = ch;
229+
fmtbuf->buf += 1;
230+
fmtbuf->n -= 1;
231+
}
232+
233+
void __fmtbuf_write_str(fmtbuf_t *fmtbuf, char *str, int l)
234+
{
235+
/*
236+
* Write the given string when n is greater than 1.
237+
* This means preserving one position for the null character.
238+
*/
239+
if (fmtbuf->n <= 1)
240+
return;
241+
242+
/*
243+
* If the remaining space is less than the length of the string,
244+
* write only n - 1 bytes.
245+
*/
246+
int sz = fmtbuf->n - 1;
247+
l = l <= sz ? l : sz;
248+
strncpy(fmtbuf->buf, str, l);
249+
fmtbuf->buf += l;
250+
fmtbuf->n -= l;
251+
}
252+
253+
int __format(fmtbuf_t *fmtbuf,
214254
int val,
215255
int width,
216256
int zeropad,
217257
int base,
218258
int alternate_form)
219259
{
220-
int bi = 0;
221-
char pb[INT_BUF_LEN];
222-
int pbi;
260+
char pb[INT_BUF_LEN], ch;
261+
int pbi, len = 0;
223262

224263
/* set to zeroes */
225264
for (pbi = 0; pbi < INT_BUF_LEN; pbi++)
@@ -249,24 +288,27 @@ int __format(char *buffer,
249288
case 8:
250289
if (alternate_form) {
251290
if (width && zeropad && pb[pbi] != '0') {
252-
buffer[bi++] = '0';
291+
__fmtbuf_write_char(fmtbuf, '0');
292+
len++;
253293
width -= 1;
254294
} else if (pb[pbi] != '0')
255295
pb[--pbi] = '0';
256296
}
257297
break;
258298
case 10:
259299
if (width && zeropad && pb[pbi] == '-') {
260-
buffer[bi++] = '-';
300+
__fmtbuf_write_char(fmtbuf, '-');
301+
len++;
261302
pbi++;
262303
width--;
263304
}
264305
break;
265306
case 16:
266307
if (alternate_form) {
267308
if (width && zeropad && pb[pbi] != '0') {
268-
buffer[bi++] = '0';
269-
buffer[bi++] = 'x';
309+
__fmtbuf_write_char(fmtbuf, '0');
310+
__fmtbuf_write_char(fmtbuf, 'x');
311+
len += 2;
270312
width -= 2;
271313
} else if (pb[pbi] != '0') {
272314
pb[--pbi] = 'x';
@@ -280,28 +322,27 @@ int __format(char *buffer,
280322
if (width < 0)
281323
width = 0;
282324

325+
ch = zeropad ? '0' : ' ';
283326
while (width) {
284-
buffer[bi++] = zeropad ? '0' : ' ';
327+
__fmtbuf_write_char(fmtbuf, ch);
328+
len++;
285329
width--;
286330
}
287331

288-
for (; pbi < INT_BUF_LEN; pbi++)
289-
buffer[bi++] = pb[pbi];
332+
__fmtbuf_write_str(fmtbuf, pb + pbi, INT_BUF_LEN - pbi);
333+
len += INT_BUF_LEN - pbi;
290334

291-
return bi;
335+
return len;
292336
}
293337

294-
int __format_to_buf(char *buffer, char *format, int *var_args, int size)
338+
int __format_to_buf(fmtbuf_t *fmtbuf, char *format, int *var_args)
295339
{
296-
int si = 0, bi = 0, pi = 0;
340+
int si = 0, pi = 0, len = 0;
297341

298-
if (size == 0)
299-
return 0;
300-
301-
while (format[si] && bi < size - 1) {
342+
while (format[si]) {
302343
if (format[si] != '%') {
303-
buffer[bi] = format[si];
304-
bi++;
344+
__fmtbuf_write_char(fmtbuf, format[si]);
345+
len++;
305346
si++;
306347
} else {
307348
int w = 0, zp = 0, pp = 0, v = var_args[pi], l;
@@ -328,31 +369,30 @@ int __format_to_buf(char *buffer, char *format, int *var_args, int size)
328369
case 's':
329370
/* append param pi as string */
330371
l = strlen(v);
331-
l = l < size - bi ? l : size - bi;
332-
strncpy(buffer + bi, v, l);
333-
bi += l;
372+
__fmtbuf_write_str(fmtbuf, v, l);
373+
len += l;
334374
break;
335375
case 'c':
336376
/* append param pi as char */
337-
buffer[bi] = v;
338-
bi += 1;
377+
__fmtbuf_write_char(fmtbuf, v);
378+
len++;
339379
break;
340380
case 'o':
341381
/* append param as octal */
342-
bi += __format(buffer + bi, v, w, zp, 8, pp);
382+
len += __format(fmtbuf, v, w, zp, 8, pp);
343383
break;
344384
case 'd':
345385
/* append param as decimal */
346-
bi += __format(buffer + bi, v, w, zp, 10, 0);
386+
len += __format(fmtbuf, v, w, zp, 10, 0);
347387
break;
348388
case 'x':
349389
/* append param as hex */
350-
bi += __format(buffer + bi, v, w, zp, 16, pp);
390+
len += __format(fmtbuf, v, w, zp, 16, pp);
351391
break;
352392
case '%':
353393
/* append literal '%' character */
354-
buffer[bi] = '%';
355-
bi++;
394+
__fmtbuf_write_char(fmtbuf, '%');
395+
len++;
356396
si++;
357397
continue;
358398
}
@@ -361,26 +401,39 @@ int __format_to_buf(char *buffer, char *format, int *var_args, int size)
361401
}
362402
}
363403

364-
int len = size - 1 > bi ? bi : size - 1;
365-
buffer[len] = 0;
404+
/* If n is still greater than 0, set the null character. */
405+
if (fmtbuf->n)
406+
fmtbuf->buf[0] = 0;
366407
return len;
367408
}
368409

369410
int printf(char *str, ...)
370411
{
371412
char buffer[200];
372-
int len = __format_to_buf(buffer, str, &str + 4, INT_MAX);
413+
fmtbuf_t fmtbuf;
414+
415+
fmtbuf.buf = buffer;
416+
fmtbuf.n = INT_MAX;
417+
int len = __format_to_buf(&fmtbuf, str, &str + 4);
373418
return __syscall(__syscall_write, 1, buffer, len);
374419
}
375420

376421
int sprintf(char *buffer, char *str, ...)
377422
{
378-
return __format_to_buf(buffer, str, &str + 4, INT_MAX);
423+
fmtbuf_t fmtbuf;
424+
425+
fmtbuf.buf = buffer;
426+
fmtbuf.n = INT_MAX;
427+
return __format_to_buf(&fmtbuf, str, &str + 4);
379428
}
380429

381430
int snprintf(char *buffer, int n, char *str, ...)
382431
{
383-
return __format_to_buf(buffer, str, &str + 4, n);
432+
fmtbuf_t fmtbuf;
433+
434+
fmtbuf.buf = buffer;
435+
fmtbuf.n = n;
436+
return __format_to_buf(&fmtbuf, str, &str + 4);
384437
}
385438

386439
int __free_all();

tests/driver.sh

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1647,6 +1647,12 @@ int main() {
16471647
}
16481648
EOF
16491649

1650+
# The following cases validate the behavior and return value of
1651+
# snprintf(). Notice that you should refer to the specification,
1652+
# such as C99 7.19.6.5, to understand the behavior of snprintf().
1653+
#
1654+
# This case is a normal case and outputs the complete string
1655+
# because the given buffer size is large enough.
16501656
try_output 16 "Hello World 1123" << EOF
16511657
int main() {
16521658
char buffer[50];
@@ -1656,18 +1662,31 @@ int main() {
16561662
}
16571663
EOF
16581664

1659-
try_output 0 "" << EOF
1665+
# If n is zero, nothing is written.
1666+
#
1667+
# Thus, the output should be the string containing 19 characters
1668+
# for this test case.
1669+
try_output 11 "0000000000000000000" << EOF
16601670
int main() {
16611671
char buffer[20];
1672+
for (int i = 0; i < 19; i++)
1673+
buffer[i] = '0';
1674+
buffer[19] = 0;
16621675
int written = snprintf(buffer, 0, "Number: %d", -37);
16631676
printf("%s", buffer);
16641677
return written;
16651678
}
16661679
EOF
16671680

1668-
try_output 9 "Number: -" << EOF
1681+
# In this case, snprintf() only writes at most 10 bytes (including '\0'),
1682+
# but the return value is 11, which corresponds to the length of
1683+
# "Number: -37".
1684+
try_output 11 "Number: -" << EOF
16691685
int main() {
16701686
char buffer[10];
1687+
for (int i = 0; i < 9; i++)
1688+
buffer[i] = '0';
1689+
buffer[9] = 0;
16711690
int written = snprintf(buffer, 10, "Number: %d", -37);
16721691
printf("%s", buffer);
16731692
return written;

tests/snapshots/fib-arm.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/snapshots/fib-riscv.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/snapshots/hello-arm.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/snapshots/hello-riscv.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)