From aabb868ca6863cb2ac4867f97eb03a854f5c09c1 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Wed, 2 Jan 2019 11:04:22 +0100 Subject: [PATCH] Try to annotate a function as raising on returning null It would be the same semantics of the PyObject* returning functions but this returns a char* for success. Currently the annotated function is validated alright but can't validate the caller just yet, hence the need of a FAKE_RAISE(). --- psycopg/config.h | 7 +++++++ psycopg/cursor_type.c | 5 +++++ psycopg/utils.c | 18 ++++++++++++++++-- psycopg/utils.h | 6 +++++- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/psycopg/config.h b/psycopg/config.h index f98ee408..177782e8 100644 --- a/psycopg/config.h +++ b/psycopg/config.h @@ -195,6 +195,13 @@ static double round(double num) #define RAISES_NEG #endif +#if defined(WITH_CPYCHECKER_NULL_RESULT_SETS_EXCEPTION_ATTRIBUTE) +#define RAISES_NULL \ + __attribute__((cpychecker_null_result_sets_exception)) +#else +#define RAISES_NULL +#endif + #if defined(WITH_CPYCHECKER_SETS_EXCEPTION_ATTRIBUTE) #define RAISES \ __attribute__((cpychecker_sets_exception)) diff --git a/psycopg/cursor_type.c b/psycopg/cursor_type.c index 7c3284c2..19b1dc10 100644 --- a/psycopg/cursor_type.c +++ b/psycopg/cursor_type.c @@ -88,6 +88,7 @@ psyco_curs_close(cursorObject *self, PyObject *dummy) if (!self->query && self->conn->server_version >= 80200) { if (!(lname = psycopg_escape_string( self->conn, self->name, -1, NULL, NULL))) { + FAKE_RAISE(); goto exit; } PyOS_snprintf(buffer, sizeof(buffer), @@ -1423,11 +1424,13 @@ psyco_curs_copy_from(cursorObject *self, PyObject *args, PyObject *kwargs) if (!(quoted_delimiter = psycopg_escape_string( self->conn, sep, -1, NULL, NULL))) { + FAKE_RAISE(); goto exit; } if (!(quoted_null = psycopg_escape_string( self->conn, null, -1, NULL, NULL))) { + FAKE_RAISE(); goto exit; } @@ -1515,11 +1518,13 @@ psyco_curs_copy_to(cursorObject *self, PyObject *args, PyObject *kwargs) if (!(quoted_delimiter = psycopg_escape_string( self->conn, sep, -1, NULL, NULL))) { + FAKE_RAISE(); goto exit; } if (!(quoted_null = psycopg_escape_string( self->conn, null, -1, NULL, NULL))) { + FAKE_RAISE(); goto exit; } diff --git a/psycopg/utils.c b/psycopg/utils.c index 01ddf570..995ae55f 100644 --- a/psycopg/utils.c +++ b/psycopg/utils.c @@ -47,7 +47,7 @@ * If tolen is set, it will contain the length of the escaped string, * including quotes. */ -char * +RAISES_NULL char * psycopg_escape_string(connectionObject *conn, const char *from, Py_ssize_t len, char *to, Py_ssize_t *tolen) { @@ -467,6 +467,8 @@ psyco_GetDecimalType(void) } +#ifdef WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE + /* Transfer ownership of an object to another object's state. * * Work around what seems a bug to the cpychecker which doesn't recognise @@ -474,10 +476,22 @@ psyco_GetDecimalType(void) * * See davidmalcolm/gcc-python-plugin#109 */ -#ifdef WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE STEALS(1) IGNORE_REFCOUNT BORROWED PyObject * TO_STATE(PyObject* obj) { return obj; } + +/* Fake a function raising an exception. + * + * Useful to play together with functions with peculiar semantics such as + * psycopg_escape_string() which doesn't return a PyObject*, but raises an + * exception on NULL. I'm trying to implement a function attribute to raise + * on NULL, similar to raise on negative, but haven't finished it yet. + */ +RAISES IGNORE_REFCOUNT void +FAKE_RAISE() +{ +} + #endif diff --git a/psycopg/utils.h b/psycopg/utils.h index 0aa5a10d..c4a0cad3 100644 --- a/psycopg/utils.h +++ b/psycopg/utils.h @@ -31,7 +31,7 @@ typedef struct cursorObject cursorObject; typedef struct connectionObject connectionObject; typedef struct replicationMessageObject replicationMessageObject; -HIDDEN char *psycopg_escape_string( +HIDDEN RAISES_NULL char *psycopg_escape_string( connectionObject *conn, const char *from, Py_ssize_t len, char *to, Py_ssize_t *tolen); @@ -58,10 +58,14 @@ HIDDEN RAISES BORROWED PyObject *psyco_set_error( HIDDEN PyObject *psyco_GetDecimalType(void); +/* Using this macro arbitrarily to check if we are under cpychecker. + * Otherwise don't compile this function. */ #ifdef WITH_CPYCHECKER_RETURNS_BORROWED_REF_ATTRIBUTE HIDDEN STEALS(1) IGNORE_REFCOUNT BORROWED PyObject *TO_STATE(PyObject* obj); +HIDDEN RAISES HIDDEN void FAKE_RAISE(void); #else #define TO_STATE(x) x +#define FAKE_RAISE() do {} while (0) #endif #endif /* !defined(UTILS_H) */