Skip to content

Commit cdaa545

Browse files
author
tim_one
committed
mysnprintf.c: Massive rewrite of PyOS_snprintf and PyOS_vsnprintf, to
use wrappers on all platforms, to make this as consistent as possible x- platform (in particular, make sure there's at least one \0 byte in the output buffer). Also document more of the truth about what these do. getargs.c, seterror(): Three computations of remaining buffer size were backwards, thus telling PyOS_snprintf the buffer is larger than it actually is. This matters a lot now that PyOS_snprintf ensures there's a trailing \0 byte (because it didn't get the truth about the buffer size, it was storing \0 beyond the true end of the buffer). sysmodule.c, mywrite(): Simplify, now that PyOS_vsnprintf guarantees to produce a \0 byte. git-svn-id: http://svn.python.org/projects/python/trunk@24458 6015fed2-1504-0410-9fe1-9d1591cc4771
1 parent 742dbe4 commit cdaa545

File tree

4 files changed

+78
-94
lines changed

4 files changed

+78
-94
lines changed

Include/pyerrors.h

-5
Original file line numberDiff line numberDiff line change
@@ -123,16 +123,11 @@ extern DL_IMPORT(PyObject *) PyErr_ProgramText(char *, int);
123123
# define vsnprintf _vsnprintf
124124
#endif
125125

126-
#ifndef HAVE_SNPRINTF
127126
#include <stdarg.h>
128127
extern DL_IMPORT(int) PyOS_snprintf(char *str, size_t size, const char *format, ...)
129128
__attribute__((format(printf, 3, 4)));
130129
extern DL_IMPORT(int) PyOS_vsnprintf(char *str, size_t size, const char *format, va_list va)
131130
__attribute__((format(printf, 3, 0)));
132-
#else
133-
# define PyOS_vsnprintf vsnprintf
134-
# define PyOS_snprintf snprintf
135-
#endif
136131

137132
#ifdef __cplusplus
138133
}

Python/getargs.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ seterror(int iarg, char *msg, int *levels, char *fname, char *message)
231231
p += strlen(p);
232232
}
233233
if (iarg != 0) {
234-
PyOS_snprintf(p, sizeof(buf) - (buf - p),
234+
PyOS_snprintf(p, sizeof(buf) - (p - buf),
235235
"argument %d", iarg);
236236
i = 0;
237237
p += strlen(p);
@@ -243,10 +243,10 @@ seterror(int iarg, char *msg, int *levels, char *fname, char *message)
243243
}
244244
}
245245
else {
246-
PyOS_snprintf(p, sizeof(buf) - (buf - p), "argument");
246+
PyOS_snprintf(p, sizeof(buf) - (p - buf), "argument");
247247
p += strlen(p);
248248
}
249-
PyOS_snprintf(p, sizeof(buf) - (buf - p), " %.256s", msg);
249+
PyOS_snprintf(p, sizeof(buf) - (p - buf), " %.256s", msg);
250250
message = buf;
251251
}
252252
PyErr_SetString(PyExc_TypeError, message);

Python/mysnprintf.c

+74-78
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,93 @@
1-
21
#include "Python.h"
2+
#include <ctype.h>
33

4-
/* snprintf() emulation for platforms which don't have it (yet).
5-
6-
Return value
4+
/* snprintf() wrappers. If the platform has vsnprintf, we use it, else we
5+
emulate it in a half-hearted way. Even if the platform has it, we wrap
6+
it because platforms differ in what vsnprintf does in case the buffer
7+
is too small: C99 behavior is to return the number of characters that
8+
would have been written had the buffer not been too small, and to set
9+
the last byte of the buffer to \0. At least MS _vsnprintf returns a
10+
negative value instead, and fills the entire buffer with non-\0 data.
711
8-
The number of characters printed (not including the trailing
9-
`\0' used to end output to strings) or a negative number in
10-
case of an error.
12+
The wrappers ensure that str[size-1] is always \0 upon return.
1113
12-
PyOS_snprintf and PyOS_vsnprintf do not write more than size
13-
bytes (including the trailing '\0').
14+
PyOS_snprintf and PyOS_vsnprintf never write more than size bytes
15+
(including the trailing '\0') into str.
1416
15-
If the output would have been truncated, they return the number
16-
of characters (excluding the trailing '\0') which would have
17-
been written to the final string if enough space had been
18-
available. This is inline with the C99 standard.
17+
If the platform doesn't have vsnprintf, and the buffer size needed to
18+
avoid truncation exceeds size by more than 512, Python aborts with a
19+
Py_FatalError.
1920
20-
*/
21+
Return value (rv):
2122
22-
#include <ctype.h>
23+
When 0 <= rv < size, the output conversion was unexceptional, and
24+
rv characters were written to str (excluding a trailing \0 byte at
25+
str[rv]).
2326
24-
#ifndef HAVE_SNPRINTF
27+
When rv >= size, output conversion was truncated, and a buffer of
28+
size rv+1 would have been needed to avoid truncation. str[size-1]
29+
is \0 in this case.
2530
26-
static
27-
int myvsnprintf(char *str, size_t size, const char *format, va_list va)
28-
{
29-
char *buffer = PyMem_Malloc(size + 512);
30-
int len;
31-
32-
if (buffer == NULL)
33-
return -1;
34-
len = vsprintf(buffer, format, va);
35-
if (len < 0) {
36-
PyMem_Free(buffer);
37-
return len;
38-
}
39-
len++;
40-
assert(len >= 0);
41-
if ((size_t)len > size + 512)
42-
Py_FatalError("Buffer overflow in PyOS_snprintf/PyOS_vsnprintf");
43-
if ((size_t)len > size)
44-
buffer[size-1] = '\0';
45-
else
46-
size = len;
47-
memcpy(str, buffer, size);
48-
PyMem_Free(buffer);
49-
return len - 1;
50-
}
31+
When rv < 0, "something bad happened". str[size-1] is \0 in this
32+
case too, but the rest of str is unreliable. It could be that
33+
an error in format codes was detected by libc, or on platforms
34+
with a non-C99 vsnprintf simply that the buffer wasn't big enough
35+
to avoid truncation, or on platforms without any vsnprintf that
36+
PyMem_Malloc couldn't obtain space for a temp buffer.
37+
38+
CAUTION: Unlike C99, str != NULL and size > 0 are required.
39+
*/
5140

52-
int PyOS_snprintf(char *str, size_t size, const char *format, ...)
41+
int
42+
PyOS_snprintf(char *str, size_t size, const char *format, ...)
5343
{
54-
int rc;
55-
va_list va;
44+
int rc;
45+
va_list va;
5646

57-
va_start(va, format);
58-
rc = myvsnprintf(str, size, format, va);
59-
va_end(va);
60-
return rc;
47+
va_start(va, format);
48+
rc = PyOS_vsnprintf(str, size, format, va);
49+
va_end(va);
50+
return rc;
6151
}
6252

63-
int PyOS_vsnprintf(char *str, size_t size, const char *format, va_list va)
53+
int
54+
PyOS_vsnprintf(char *str, size_t size, const char *format, va_list va)
6455
{
65-
return myvsnprintf(str, size, format, va);
66-
}
67-
68-
#else
69-
70-
/* Make sure that a C API is included in the lib */
71-
72-
#ifdef PyOS_snprintf
73-
# undef PyOS_snprintf
56+
int len; /* # bytes written, excluding \0 */
57+
#ifndef HAVE_SNPRINTF
58+
char *buffer;
7459
#endif
60+
assert(str != NULL);
61+
assert(size > 0);
62+
assert(format != NULL);
7563

76-
int PyOS_snprintf(char *str, size_t size, const char *format, ...)
77-
{
78-
int rc;
79-
va_list va;
80-
81-
va_start(va, format);
82-
rc = vsnprintf(str, size, format, va);
83-
va_end(va);
84-
return rc;
85-
}
86-
87-
#ifdef PyOS_vsnprintf
88-
# undef PyOS_vsnprintf
64+
#ifdef HAVE_SNPRINTF
65+
len = vsnprintf(str, size, format, va);
66+
#else
67+
/* Emulate it. */
68+
buffer = PyMem_Malloc(size + 512);
69+
if (buffer == NULL) {
70+
len = -666;
71+
goto Done;
72+
}
73+
74+
len = vsprintf(buffer, format, va);
75+
if (len < 0)
76+
/* ignore the error */;
77+
78+
else if ((size_t)len >= size + 512)
79+
Py_FatalError("Buffer overflow in PyOS_snprintf/PyOS_vsnprintf");
80+
81+
else {
82+
const size_t to_copy = (size_t)len < size ?
83+
(size_t)len : size - 1;
84+
assert(to_copy < size);
85+
memcpy(str, buffer, to_copy);
86+
str[to_copy] = '\0';
87+
}
88+
PyMem_Free(buffer);
89+
Done:
8990
#endif
90-
91-
int PyOS_vsnprintf(char *str, size_t size, const char *format, va_list va)
92-
{
93-
return vsnprintf(str, size, format, va);
91+
str[size-1] = '\0';
92+
return len;
9493
}
95-
96-
#endif
97-

Python/sysmodule.c

+1-8
Original file line numberDiff line numberDiff line change
@@ -1025,18 +1025,11 @@ mywrite(char *name, FILE *fp, const char *format, va_list va)
10251025
char buffer[1001];
10261026
const int written = PyOS_vsnprintf(buffer, sizeof(buffer),
10271027
format, va);
1028-
const int trouble = written < 0 || written >= sizeof(buffer);
1029-
if (trouble) {
1030-
/* Ensure there's a trailing null byte -- MS
1031-
vsnprintf fills the buffer to the very end
1032-
if it's not big enough. */
1033-
buffer[sizeof(buffer) - 1] = '\0';
1034-
}
10351028
if (PyFile_WriteString(buffer, file) != 0) {
10361029
PyErr_Clear();
10371030
fputs(buffer, fp);
10381031
}
1039-
if (trouble) {
1032+
if (written < 0 || written >= sizeof(buffer)) {
10401033
const char *truncated = "... truncated";
10411034
if (PyFile_WriteString(truncated, file) != 0) {
10421035
PyErr_Clear();

0 commit comments

Comments
 (0)