Skip to content

Commit 5583aa5

Browse files
committed
optimize format_ssl_error_message
1 parent 689568f commit 5583aa5

File tree

1 file changed

+92
-56
lines changed

1 file changed

+92
-56
lines changed

Modules/_ssl.c

Lines changed: 92 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,40 @@ ssl_error_fetch_lib_and_reason(_sslmodulestate *state, py_ssl_errcode errcode,
529529
*reason = Py_XNewRef(val);
530530
}
531531

532+
static inline size_t
533+
ssl_error_filename_width(const char *Py_UNUSED(filename))
534+
{
535+
/*
536+
* Sometimes, __FILE__ is an absolute path, so we hardcode "_ssl.c".
537+
* In the future, we might want to use the "filename" parameter but
538+
* for now (and for backward compatibility), we ignore it.
539+
*/
540+
return 6 /* strlen("_ssl.c") */;
541+
}
542+
543+
static inline size_t
544+
ssl_error_lineno_width(int lineno)
545+
{
546+
if (lineno < 0) {
547+
return 1 + ssl_error_lineno_width(-lineno);
548+
}
549+
#define FAST_PATH(E, N) \
550+
do { \
551+
assert((size_t)(1e ## E) == N); \
552+
if (lineno < (N)) { \
553+
return (E); \
554+
} \
555+
} while (0)
556+
FAST_PATH(2, 10);
557+
FAST_PATH(3, 100);
558+
FAST_PATH(4, 1000);
559+
FAST_PATH(5, 10000);
560+
FAST_PATH(6, 100000);
561+
FAST_PATH(7, 1000000);
562+
#undef FAST_PATH
563+
return (size_t)ceil(log10(lineno));
564+
}
565+
532566
/*
533567
* Construct a Unicode object containing the formatted SSL error message.
534568
*
@@ -537,7 +571,7 @@ ssl_error_fetch_lib_and_reason(_sslmodulestate *state, py_ssl_errcode errcode,
537571
static PyObject *
538572
format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify,
539573
const char *errstr,
540-
const char *Py_UNUSED(filename), int lineno)
574+
const char *filename, int lineno)
541575
{
542576
assert(errstr != NULL);
543577

@@ -551,71 +585,71 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify,
551585
CHECK_OBJECT(verify);
552586
#undef CHECK_OBJECT
553587

554-
#define OPTIONAL_UTF8(x) ((x) == NULL ? NULL : PyUnicode_AsUTF8((x)))
555-
const char *libstr = OPTIONAL_UTF8(lib);
556-
const char *reastr = OPTIONAL_UTF8(reason);
557-
const char *verstr = OPTIONAL_UTF8(verify);
558-
#undef OPTIONAL_UTF8
559-
560-
const size_t errstr_len = strlen(errstr);
561-
const size_t libstr_len = libstr == NULL ? 0 : strlen(libstr);
562-
const size_t reastr_len = reastr == NULL ? 0 : strlen(reastr);
563-
const size_t verstr_len = verstr == NULL ? 0 : strlen(verstr);
564-
/*
565-
* Sometimes, __FILE__ is an absolute path, so we hardcode "_ssl.c".
566-
* In the future, we might want to use the "filename" parameter but
567-
* for now (and for backward compatibility), we ignore it.
568-
*/
569-
const size_t filename_len = 6; /* strlen("_ssl.c") */
570-
/*
571-
* Crude upper bound on the number of characters taken by the line number.
572-
* We expect -1 <= lineno < 1e8. More lines are unlikely to happen.
573-
*/
574-
assert(lineno >= -1);
575-
assert(lineno < 1e8);
576-
const size_t lineno_len = 8;
577-
const size_t base_alloc = (
578-
libstr_len + reastr_len + verstr_len
579-
+ errstr_len + filename_len + lineno_len
580-
);
588+
const size_t filename_len = ssl_error_filename_width(filename);
589+
const size_t lineno_len = ssl_error_lineno_width(lineno);
590+
/* exact length of "(_ssl.c:LINENO)" */
591+
const size_t suffix_len = 1 + filename_len + 1 + lineno_len + 1;
581592

582593
int rc;
583-
char *buf;
584-
594+
PyObject *res = NULL;
595+
#define CSTRBUF(x) ((const char *)PyUnicode_DATA((x)))
596+
#define CHARBUF(x) ((char *)PyUnicode_DATA((x)))
585597
if (lib && reason && verify) {
586598
/* [LIB: REASON] ERROR: VERIFY (_ssl.c:LINENO) */
587-
const size_t alloc = base_alloc + (4 + 3 + 4);
588-
/* PyMem_Malloc() is optimized for small buffers */
589-
buf = PyMem_New(char, alloc);
590-
rc = PyOS_snprintf(buf, alloc, "[%s: %s] %s: %s (_ssl.c:%d)",
591-
libstr, reastr, errstr, verstr, lineno);
599+
const char *lib_cstr = CSTRBUF(lib);
600+
const char *reason_cstr = CSTRBUF(reason);
601+
const char *verify_cstr = CSTRBUF(verify);
602+
const size_t ressize = /* excludes final null byte */ (
603+
1 + strlen(lib_cstr) + 2 + strlen(reason_cstr) + 1
604+
+ 1 + strlen(errstr) + 2 + strlen(verify_cstr)
605+
+ 1 + suffix_len
606+
);
607+
res = PyUnicode_New(ressize, 127);
608+
rc = snprintf(CHARBUF(res), ressize + 1, "[%s: %s] %s: %s (_ssl.c:%d)",
609+
lib_cstr, reason_cstr, errstr, verify_cstr, lineno);
592610
}
593611
else if (lib && reason) {
594612
/* [LIB: REASON] ERROR (_ssl.c:LINENO) */
595-
const size_t alloc = base_alloc + (3 + 2 + 4);
596-
buf = PyMem_New(char, alloc);
597-
rc = PyOS_snprintf(buf, alloc, "[%s: %s] %s (_ssl.c:%d)",
598-
libstr, reastr, errstr, lineno);
613+
const char *lib_cstr = CSTRBUF(lib);
614+
const char *reason_cstr = CSTRBUF(reason);
615+
const size_t ressize = /* excludes final null byte */ (
616+
1 + strlen(lib_cstr) + 2 + strlen(reason_cstr) + 1
617+
+ 1 + strlen(errstr)
618+
+ 1 + suffix_len
619+
);
620+
res = PyUnicode_New(ressize, 127);
621+
rc = snprintf(CHARBUF(res), ressize + 1, "[%s: %s] %s (_ssl.c:%d)",
622+
lib_cstr, reason_cstr, errstr, lineno);
599623
}
600624
else if (lib) {
601625
/* [LIB] ERROR (_ssl.c:LINENO) */
602-
const size_t alloc = base_alloc + (2 + 1 + 4);
603-
buf = PyMem_New(char, alloc);
604-
rc = PyOS_snprintf(buf, alloc, "[%s] %s (_ssl.c:%d)",
605-
libstr, errstr, lineno);
626+
const char *lib_cstr = CSTRBUF(lib);
627+
const size_t ressize = /* excludes final null byte */ (
628+
1 + strlen(lib_cstr) + 1
629+
+ 1 + strlen(errstr)
630+
+ 1 + suffix_len
631+
);
632+
res = PyUnicode_New(ressize, 127);
633+
rc = snprintf(CHARBUF(res), ressize + 1, "[%s] %s (_ssl.c:%d)",
634+
lib_cstr, errstr, lineno);
606635
}
607636
else {
608637
/* ERROR (_ssl.c:LINENO) */
609-
const size_t alloc = base_alloc + (1 + 1 + 2);
610-
buf = PyMem_New(char, alloc);
611-
rc = PyOS_snprintf(buf, alloc, "%s (_ssl.c:%d)",
612-
errstr, lineno);
638+
const size_t ressize = /* excludes final null byte */ (
639+
strlen(errstr)
640+
+ 1 + suffix_len
641+
);
642+
res = PyUnicode_New(ressize, 127);
643+
rc = snprintf(CHARBUF(res), ressize + 1, "%s (_ssl.c:%d)",
644+
errstr, lineno);
645+
}
646+
#undef CHARBUF
647+
#undef CSTRBUF
648+
if (rc < 0) {
649+
Py_XDECREF(res);
650+
/* fallback to slow path if snprintf() failed */
651+
return PyUnicode_FromFormat("%s (_ssl.c:%d)", errstr, lineno);
613652
}
614-
615-
PyObject *res = rc < 0 /* fallback to slow path if snprintf() failed */
616-
? PyUnicode_FromFormat("%s (_ssl.c:%d)", errstr, lineno)
617-
: PyUnicode_FromString(buf) /* uses the ASCII fast path */;
618-
PyMem_Free(buf);
619653
return res;
620654
}
621655

@@ -628,7 +662,7 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify,
628662
* ssl_errno The SSL error number to pass to the exception constructor.
629663
* lib The ASCII-encoded library obtained from a packed error code.
630664
* reason The ASCII-encoded reason obtained from a packed error code.
631-
* errstr The error message to use.
665+
* errstr The non-NULL error message to use.
632666
*
633667
* A non-NULL library or reason is stored in the final exception object.
634668
*/
@@ -778,10 +812,12 @@ fill_and_set_sslerror(_sslmodulestate *state,
778812
if (ssl_use_verbose_error(state, exc_type, ssl_errno, errcode)) {
779813
ssl_error_fetch_lib_and_reason(state, errcode, &lib, &reason);
780814
}
815+
if (errstr == NULL && errcode) {
816+
errstr = ERR_reason_error_string(errcode);
817+
}
781818
if (errstr == NULL) {
782-
errstr = errcode
783-
? ERR_reason_error_string(errcode)
784-
: "unknown error";
819+
// ERR_reason_error_string() may return NULL
820+
errstr = "unknown error";
785821
}
786822
PyObject *exc;
787823
if (sslsock != NULL && exc_type == state->PySSLCertVerificationErrorObject) {

0 commit comments

Comments
 (0)