Skip to content

Commit e5006eb

Browse files
committed
This patch turns the Python API mismatch notice into a standard
Python warning which can be catched by means of the Python warning framework. It also adds two new APIs which hopefully make it easier for Python to switch to buffer overflow safe [v]snprintf() APIs for error reporting et al. The two new APIs are PyOS_snprintf() and PyOS_vsnprintf() and work just like the standard ones in many C libs. On platforms which have snprintf(), the native APIs are used, on all other an emulation with snprintf() tries to do its best.
1 parent b9d07b5 commit e5006eb

File tree

4 files changed

+124
-5
lines changed

4 files changed

+124
-5
lines changed

Include/pyerrors.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,22 @@ extern DL_IMPORT(void) PyErr_SetInterrupt(void);
106106
extern DL_IMPORT(void) PyErr_SyntaxLocation(char *, int);
107107
extern DL_IMPORT(PyObject *) PyErr_ProgramText(char *, int);
108108

109+
/* These APIs aren't really part of the error implementation, but
110+
often needed to format error messages; the native C lib APIs are
111+
not available on all platforms, which is why we provide emulations
112+
for those platforms in Python/mysnprintf.c */
113+
#if defined(MS_WIN32) && !defined(HAVE_SNPRINTF)
114+
# define HAVE_SNPRINTF
115+
# define snprintf _snprintf
116+
# define vsnprintf _vsnprintf
117+
#endif
118+
#ifndef HAVE_SNPRINTF
119+
extern DL_IMPORT(int) PyOS_snprintf(char *str, size_t size, const char *format, ...);
120+
extern DL_IMPORT(int) PyOS_vsnprintf(char *str, size_t size, const char *format, va_list va);
121+
#else
122+
# define PyOS_vsnprintf vsnprintf
123+
# define PyOS_snprintf snprintf
124+
#endif
109125

110126
#ifdef __cplusplus
111127
}

Makefile.pre.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ PYTHON_OBJS= \
214214
Python/marshal.o \
215215
Python/modsupport.o \
216216
Python/mystrtoul.o \
217+
Python/mysnprintf.o \
217218
Python/pyfpe.o \
218219
Python/pystate.o \
219220
Python/pythonrun.o \

Python/modsupport.c

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ char *_Py_PackageContext = NULL;
2626
*/
2727

2828
static char api_version_warning[] =
29-
"WARNING: Python C API version mismatch for module %s:\n\
30-
This Python has API version %d, module %s has version %d.\n";
29+
"Python C API version mismatch for module %.100s:\
30+
This Python has API version %d, module %.100s has version %d.";
3131

3232
PyObject *
3333
Py_InitModule4(char *name, PyMethodDef *methods, char *doc,
@@ -37,9 +37,15 @@ Py_InitModule4(char *name, PyMethodDef *methods, char *doc,
3737
PyMethodDef *ml;
3838
if (!Py_IsInitialized())
3939
Py_FatalError("Interpreter not initialized (version mismatch?)");
40-
if (module_api_version != PYTHON_API_VERSION)
41-
fprintf(stderr, api_version_warning,
42-
name, PYTHON_API_VERSION, name, module_api_version);
40+
if (module_api_version != PYTHON_API_VERSION) {
41+
char message[512];
42+
PyOS_snprintf(message, sizeof(message),
43+
api_version_warning, name,
44+
PYTHON_API_VERSION, name,
45+
module_api_version);
46+
if (PyErr_Warn(PyExc_RuntimeWarning, message))
47+
return NULL;
48+
}
4349
if (_Py_PackageContext != NULL) {
4450
char *p = strrchr(_Py_PackageContext, '.');
4551
if (p != NULL && strcmp(name, p+1) == 0) {

Python/mysnprintf.c

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
2+
#include "Python.h"
3+
4+
/* snprintf() emulation for platforms which don't have it (yet).
5+
6+
Return value
7+
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.
11+
12+
PyOS_snprintf and PyOS_vsnprintf do not write more than size
13+
bytes (including the trailing '\0').
14+
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.
19+
20+
*/
21+
22+
#include <ctype.h>
23+
24+
#ifndef HAVE_SNPRINTF
25+
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+
if (len > size + 512)
41+
Py_FatalError("Buffer overflow in PyOS_snprintf/PyOS_vsnprintf");
42+
if (len > size) {
43+
PyMem_Free(buffer);
44+
return len - 1;
45+
}
46+
memcpy(str, buffer, len);
47+
PyMem_Free(buffer);
48+
return len - 1;
49+
}
50+
51+
int PyOS_snprintf(char *str, size_t size, const char *format, ...)
52+
{
53+
int rc;
54+
va_list va;
55+
56+
va_start(va, format);
57+
rc = myvsnprintf(str, size, format, va);
58+
va_end(va);
59+
return rc;
60+
}
61+
62+
int PyOS_vsnprintf(char *str, size_t size, const char *format, va_list va)
63+
{
64+
return myvsnprintf(str, size, format, va);
65+
}
66+
67+
#else
68+
69+
/* Make sure that a C API is included in the lib */
70+
71+
#ifdef PyOS_snprintf
72+
# undef PyOS_snprintf
73+
#endif
74+
75+
int PyOS_snprintf(char *str, size_t size, const char *format, ...)
76+
{
77+
int rc;
78+
va_list va;
79+
80+
va_start(va, format);
81+
rc = vsnprintf(str, size, format, va);
82+
va_end(va);
83+
return rc;
84+
}
85+
86+
#ifdef PyOS_vsnprintf
87+
# undef PyOS_vsnprintf
88+
#endif
89+
90+
int PyOS_vsnprintf(char *str, size_t size, const char *format, va_list va)
91+
{
92+
return vsnprintf(str, size, format, va);
93+
}
94+
95+
#endif
96+

0 commit comments

Comments
 (0)