1 /*
2  * reporting Python exceptions as PostgreSQL errors
3  *
4  * src/pl/plpython/plpy_elog.c
5  */
6 
7 #include "postgres.h"
8 
9 #include "lib/stringinfo.h"
10 
11 #include "plpython.h"
12 
13 #include "plpy_elog.h"
14 
15 #include "plpy_main.h"
16 #include "plpy_procedure.h"
17 
18 
19 PyObject   *PLy_exc_error = NULL;
20 PyObject   *PLy_exc_fatal = NULL;
21 PyObject   *PLy_exc_spi_error = NULL;
22 
23 
24 static void PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
25 						  char **xmsg, char **tbmsg, int *tb_depth);
26 static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
27 								   char **hint, char **query, int *position,
28 								   char **schema_name, char **table_name, char **column_name,
29 								   char **datatype_name, char **constraint_name);
30 static void PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail,
31 							   char **hint, char **schema_name, char **table_name, char **column_name,
32 							   char **datatype_name, char **constraint_name);
33 static char *get_source_line(const char *src, int lineno);
34 
35 static void get_string_attr(PyObject *obj, char *attrname, char **str);
36 static bool set_string_attr(PyObject *obj, char *attrname, char *str);
37 
38 /*
39  * Emit a PG error or notice, together with any available info about
40  * the current Python error, previously set by PLy_exception_set().
41  * This should be used to propagate Python errors into PG.  If fmt is
42  * NULL, the Python error becomes the primary error message, otherwise
43  * it becomes the detail.  If there is a Python traceback, it is put
44  * in the context.
45  */
46 void
PLy_elog_impl(int elevel,const char * fmt,...)47 PLy_elog_impl(int elevel, const char *fmt,...)
48 {
49 	int			save_errno = errno;
50 	char	   *xmsg;
51 	char	   *tbmsg;
52 	int			tb_depth;
53 	StringInfoData emsg;
54 	PyObject   *exc,
55 			   *val,
56 			   *tb;
57 	const char *primary = NULL;
58 	int			sqlerrcode = 0;
59 	char	   *detail = NULL;
60 	char	   *hint = NULL;
61 	char	   *query = NULL;
62 	int			position = 0;
63 	char	   *schema_name = NULL;
64 	char	   *table_name = NULL;
65 	char	   *column_name = NULL;
66 	char	   *datatype_name = NULL;
67 	char	   *constraint_name = NULL;
68 
69 	PyErr_Fetch(&exc, &val, &tb);
70 
71 	if (exc != NULL)
72 	{
73 		PyErr_NormalizeException(&exc, &val, &tb);
74 
75 		if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
76 			PLy_get_spi_error_data(val, &sqlerrcode,
77 								   &detail, &hint, &query, &position,
78 								   &schema_name, &table_name, &column_name,
79 								   &datatype_name, &constraint_name);
80 		else if (PyErr_GivenExceptionMatches(val, PLy_exc_error))
81 			PLy_get_error_data(val, &sqlerrcode, &detail, &hint,
82 							   &schema_name, &table_name, &column_name,
83 							   &datatype_name, &constraint_name);
84 		else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
85 			elevel = FATAL;
86 	}
87 
88 	/* this releases our refcount on tb! */
89 	PLy_traceback(exc, val, tb,
90 				  &xmsg, &tbmsg, &tb_depth);
91 
92 	if (fmt)
93 	{
94 		initStringInfo(&emsg);
95 		for (;;)
96 		{
97 			va_list		ap;
98 			int			needed;
99 
100 			errno = save_errno;
101 			va_start(ap, fmt);
102 			needed = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap);
103 			va_end(ap);
104 			if (needed == 0)
105 				break;
106 			enlargeStringInfo(&emsg, needed);
107 		}
108 		primary = emsg.data;
109 
110 		/* Since we have a format string, we cannot have a SPI detail. */
111 		Assert(detail == NULL);
112 
113 		/* If there's an exception message, it goes in the detail. */
114 		if (xmsg)
115 			detail = xmsg;
116 	}
117 	else
118 	{
119 		if (xmsg)
120 			primary = xmsg;
121 	}
122 
123 	PG_TRY();
124 	{
125 		ereport(elevel,
126 				(errcode(sqlerrcode ? sqlerrcode : ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
127 				 errmsg_internal("%s", primary ? primary : "no exception data"),
128 				 (detail) ? errdetail_internal("%s", detail) : 0,
129 				 (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0,
130 				 (hint) ? errhint("%s", hint) : 0,
131 				 (query) ? internalerrquery(query) : 0,
132 				 (position) ? internalerrposition(position) : 0,
133 				 (schema_name) ? err_generic_string(PG_DIAG_SCHEMA_NAME,
134 													schema_name) : 0,
135 				 (table_name) ? err_generic_string(PG_DIAG_TABLE_NAME,
136 												   table_name) : 0,
137 				 (column_name) ? err_generic_string(PG_DIAG_COLUMN_NAME,
138 													column_name) : 0,
139 				 (datatype_name) ? err_generic_string(PG_DIAG_DATATYPE_NAME,
140 													  datatype_name) : 0,
141 				 (constraint_name) ? err_generic_string(PG_DIAG_CONSTRAINT_NAME,
142 														constraint_name) : 0));
143 	}
144 	PG_CATCH();
145 	{
146 		if (fmt)
147 			pfree(emsg.data);
148 		if (xmsg)
149 			pfree(xmsg);
150 		if (tbmsg)
151 			pfree(tbmsg);
152 		Py_XDECREF(exc);
153 		Py_XDECREF(val);
154 
155 		PG_RE_THROW();
156 	}
157 	PG_END_TRY();
158 
159 	if (fmt)
160 		pfree(emsg.data);
161 	if (xmsg)
162 		pfree(xmsg);
163 	if (tbmsg)
164 		pfree(tbmsg);
165 	Py_XDECREF(exc);
166 	Py_XDECREF(val);
167 }
168 
169 /*
170  * Extract a Python traceback from the given exception data.
171  *
172  * The exception error message is returned in xmsg, the traceback in
173  * tbmsg (both as palloc'd strings) and the traceback depth in
174  * tb_depth.
175  *
176  * We release refcounts on all the Python objects in the traceback stack,
177  * but not on e or v.
178  */
179 static void
PLy_traceback(PyObject * e,PyObject * v,PyObject * tb,char ** xmsg,char ** tbmsg,int * tb_depth)180 PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
181 			  char **xmsg, char **tbmsg, int *tb_depth)
182 {
183 	PyObject   *e_type_o;
184 	PyObject   *e_module_o;
185 	char	   *e_type_s = NULL;
186 	char	   *e_module_s = NULL;
187 	PyObject   *vob = NULL;
188 	char	   *vstr;
189 	StringInfoData xstr;
190 	StringInfoData tbstr;
191 
192 	/*
193 	 * if no exception, return nulls
194 	 */
195 	if (e == NULL)
196 	{
197 		*xmsg = NULL;
198 		*tbmsg = NULL;
199 		*tb_depth = 0;
200 
201 		return;
202 	}
203 
204 	/*
205 	 * Format the exception and its value and put it in xmsg.
206 	 */
207 
208 	e_type_o = PyObject_GetAttrString(e, "__name__");
209 	e_module_o = PyObject_GetAttrString(e, "__module__");
210 	if (e_type_o)
211 		e_type_s = PyString_AsString(e_type_o);
212 	if (e_type_s)
213 		e_module_s = PyString_AsString(e_module_o);
214 
215 	if (v && ((vob = PyObject_Str(v)) != NULL))
216 		vstr = PyString_AsString(vob);
217 	else
218 		vstr = "unknown";
219 
220 	initStringInfo(&xstr);
221 	if (!e_type_s || !e_module_s)
222 	{
223 		if (PyString_Check(e))
224 			/* deprecated string exceptions */
225 			appendStringInfoString(&xstr, PyString_AsString(e));
226 		else
227 			/* shouldn't happen */
228 			appendStringInfoString(&xstr, "unrecognized exception");
229 	}
230 	/* mimics behavior of traceback.format_exception_only */
231 	else if (strcmp(e_module_s, "builtins") == 0
232 			 || strcmp(e_module_s, "__main__") == 0
233 			 || strcmp(e_module_s, "exceptions") == 0)
234 		appendStringInfo(&xstr, "%s", e_type_s);
235 	else
236 		appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s);
237 	appendStringInfo(&xstr, ": %s", vstr);
238 
239 	*xmsg = xstr.data;
240 
241 	/*
242 	 * Now format the traceback and put it in tbmsg.
243 	 */
244 
245 	*tb_depth = 0;
246 	initStringInfo(&tbstr);
247 	/* Mimic Python traceback reporting as close as possible. */
248 	appendStringInfoString(&tbstr, "Traceback (most recent call last):");
249 	while (tb != NULL && tb != Py_None)
250 	{
251 		PyObject   *volatile tb_prev = NULL;
252 		PyObject   *volatile frame = NULL;
253 		PyObject   *volatile code = NULL;
254 		PyObject   *volatile name = NULL;
255 		PyObject   *volatile lineno = NULL;
256 		PyObject   *volatile filename = NULL;
257 
258 		PG_TRY();
259 		{
260 			/*
261 			 * Ancient versions of Python (circa 2.3) contain a bug whereby
262 			 * the fetches below can fail if the error indicator is set.
263 			 */
264 			PyErr_Clear();
265 
266 			lineno = PyObject_GetAttrString(tb, "tb_lineno");
267 			if (lineno == NULL)
268 				elog(ERROR, "could not get line number from Python traceback");
269 
270 			frame = PyObject_GetAttrString(tb, "tb_frame");
271 			if (frame == NULL)
272 				elog(ERROR, "could not get frame from Python traceback");
273 
274 			code = PyObject_GetAttrString(frame, "f_code");
275 			if (code == NULL)
276 				elog(ERROR, "could not get code object from Python frame");
277 
278 			name = PyObject_GetAttrString(code, "co_name");
279 			if (name == NULL)
280 				elog(ERROR, "could not get function name from Python code object");
281 
282 			filename = PyObject_GetAttrString(code, "co_filename");
283 			if (filename == NULL)
284 				elog(ERROR, "could not get file name from Python code object");
285 		}
286 		PG_CATCH();
287 		{
288 			Py_XDECREF(frame);
289 			Py_XDECREF(code);
290 			Py_XDECREF(name);
291 			Py_XDECREF(lineno);
292 			Py_XDECREF(filename);
293 			PG_RE_THROW();
294 		}
295 		PG_END_TRY();
296 
297 		/* The first frame always points at <module>, skip it. */
298 		if (*tb_depth > 0)
299 		{
300 			PLyExecutionContext *exec_ctx = PLy_current_execution_context();
301 			char	   *proname;
302 			char	   *fname;
303 			char	   *line;
304 			char	   *plain_filename;
305 			long		plain_lineno;
306 
307 			/*
308 			 * The second frame points at the internal function, but to mimic
309 			 * Python error reporting we want to say <module>.
310 			 */
311 			if (*tb_depth == 1)
312 				fname = "<module>";
313 			else
314 				fname = PyString_AsString(name);
315 
316 			proname = PLy_procedure_name(exec_ctx->curr_proc);
317 			plain_filename = PyString_AsString(filename);
318 			plain_lineno = PyInt_AsLong(lineno);
319 
320 			if (proname == NULL)
321 				appendStringInfo(
322 								 &tbstr, "\n  PL/Python anonymous code block, line %ld, in %s",
323 								 plain_lineno - 1, fname);
324 			else
325 				appendStringInfo(
326 								 &tbstr, "\n  PL/Python function \"%s\", line %ld, in %s",
327 								 proname, plain_lineno - 1, fname);
328 
329 			/*
330 			 * function code object was compiled with "<string>" as the
331 			 * filename
332 			 */
333 			if (exec_ctx->curr_proc && plain_filename != NULL &&
334 				strcmp(plain_filename, "<string>") == 0)
335 			{
336 				/*
337 				 * If we know the current procedure, append the exact line
338 				 * from the source, again mimicking Python's traceback.py
339 				 * module behavior.  We could store the already line-split
340 				 * source to avoid splitting it every time, but producing a
341 				 * traceback is not the most important scenario to optimize
342 				 * for.  But we do not go as far as traceback.py in reading
343 				 * the source of imported modules.
344 				 */
345 				line = get_source_line(exec_ctx->curr_proc->src, plain_lineno);
346 				if (line)
347 				{
348 					appendStringInfo(&tbstr, "\n    %s", line);
349 					pfree(line);
350 				}
351 			}
352 		}
353 
354 		Py_DECREF(frame);
355 		Py_DECREF(code);
356 		Py_DECREF(name);
357 		Py_DECREF(lineno);
358 		Py_DECREF(filename);
359 
360 		/* Release the current frame and go to the next one. */
361 		tb_prev = tb;
362 		tb = PyObject_GetAttrString(tb, "tb_next");
363 		Assert(tb_prev != Py_None);
364 		Py_DECREF(tb_prev);
365 		if (tb == NULL)
366 			elog(ERROR, "could not traverse Python traceback");
367 		(*tb_depth)++;
368 	}
369 
370 	/* Return the traceback. */
371 	*tbmsg = tbstr.data;
372 
373 	Py_XDECREF(e_type_o);
374 	Py_XDECREF(e_module_o);
375 	Py_XDECREF(vob);
376 }
377 
378 /*
379  * Extract error code from SPIError's sqlstate attribute.
380  */
381 static void
PLy_get_sqlerrcode(PyObject * exc,int * sqlerrcode)382 PLy_get_sqlerrcode(PyObject *exc, int *sqlerrcode)
383 {
384 	PyObject   *sqlstate;
385 	char	   *buffer;
386 
387 	sqlstate = PyObject_GetAttrString(exc, "sqlstate");
388 	if (sqlstate == NULL)
389 		return;
390 
391 	buffer = PyString_AsString(sqlstate);
392 	if (strlen(buffer) == 5 &&
393 		strspn(buffer, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
394 	{
395 		*sqlerrcode = MAKE_SQLSTATE(buffer[0], buffer[1], buffer[2],
396 									buffer[3], buffer[4]);
397 	}
398 
399 	Py_DECREF(sqlstate);
400 }
401 
402 /*
403  * Extract the error data from a SPIError
404  */
405 static void
PLy_get_spi_error_data(PyObject * exc,int * sqlerrcode,char ** detail,char ** hint,char ** query,int * position,char ** schema_name,char ** table_name,char ** column_name,char ** datatype_name,char ** constraint_name)406 PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
407 					   char **hint, char **query, int *position,
408 					   char **schema_name, char **table_name,
409 					   char **column_name,
410 					   char **datatype_name, char **constraint_name)
411 {
412 	PyObject   *spidata;
413 
414 	spidata = PyObject_GetAttrString(exc, "spidata");
415 
416 	if (spidata != NULL)
417 	{
418 		PyArg_ParseTuple(spidata, "izzzizzzzz",
419 						 sqlerrcode, detail, hint, query, position,
420 						 schema_name, table_name, column_name,
421 						 datatype_name, constraint_name);
422 	}
423 	else
424 	{
425 		/*
426 		 * If there's no spidata, at least set the sqlerrcode. This can happen
427 		 * if someone explicitly raises a SPI exception from Python code.
428 		 */
429 		PLy_get_sqlerrcode(exc, sqlerrcode);
430 	}
431 
432 	Py_XDECREF(spidata);
433 }
434 
435 /*
436  * Extract the error data from an Error.
437  *
438  * Note: position and query attributes are never set for Error so, unlike
439  * PLy_get_spi_error_data, this function doesn't return them.
440  */
441 static void
PLy_get_error_data(PyObject * exc,int * sqlerrcode,char ** detail,char ** hint,char ** schema_name,char ** table_name,char ** column_name,char ** datatype_name,char ** constraint_name)442 PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint,
443 				   char **schema_name, char **table_name, char **column_name,
444 				   char **datatype_name, char **constraint_name)
445 {
446 	PLy_get_sqlerrcode(exc, sqlerrcode);
447 	get_string_attr(exc, "detail", detail);
448 	get_string_attr(exc, "hint", hint);
449 	get_string_attr(exc, "schema_name", schema_name);
450 	get_string_attr(exc, "table_name", table_name);
451 	get_string_attr(exc, "column_name", column_name);
452 	get_string_attr(exc, "datatype_name", datatype_name);
453 	get_string_attr(exc, "constraint_name", constraint_name);
454 }
455 
456 /*
457  * Get the given source line as a palloc'd string
458  */
459 static char *
get_source_line(const char * src,int lineno)460 get_source_line(const char *src, int lineno)
461 {
462 	const char *s = NULL;
463 	const char *next = src;
464 	int			current = 0;
465 
466 	/* sanity check */
467 	if (lineno <= 0)
468 		return NULL;
469 
470 	while (current < lineno)
471 	{
472 		s = next;
473 		next = strchr(s + 1, '\n');
474 		current++;
475 		if (next == NULL)
476 			break;
477 	}
478 
479 	if (current != lineno)
480 		return NULL;
481 
482 	while (*s && isspace((unsigned char) *s))
483 		s++;
484 
485 	if (next == NULL)
486 		return pstrdup(s);
487 
488 	/*
489 	 * Sanity check, next < s if the line was all-whitespace, which should
490 	 * never happen if Python reported a frame created on that line, but check
491 	 * anyway.
492 	 */
493 	if (next < s)
494 		return NULL;
495 
496 	return pnstrdup(s, next - s);
497 }
498 
499 
500 /* call PyErr_SetString with a vprint interface and translation support */
501 void
PLy_exception_set(PyObject * exc,const char * fmt,...)502 PLy_exception_set(PyObject *exc, const char *fmt,...)
503 {
504 	char		buf[1024];
505 	va_list		ap;
506 
507 	va_start(ap, fmt);
508 	vsnprintf(buf, sizeof(buf), dgettext(TEXTDOMAIN, fmt), ap);
509 	va_end(ap);
510 
511 	PyErr_SetString(exc, buf);
512 }
513 
514 /* same, with pluralized message */
515 void
PLy_exception_set_plural(PyObject * exc,const char * fmt_singular,const char * fmt_plural,unsigned long n,...)516 PLy_exception_set_plural(PyObject *exc,
517 						 const char *fmt_singular, const char *fmt_plural,
518 						 unsigned long n,...)
519 {
520 	char		buf[1024];
521 	va_list		ap;
522 
523 	va_start(ap, n);
524 	vsnprintf(buf, sizeof(buf),
525 			  dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n),
526 			  ap);
527 	va_end(ap);
528 
529 	PyErr_SetString(exc, buf);
530 }
531 
532 /* set attributes of the given exception to details from ErrorData */
533 void
PLy_exception_set_with_details(PyObject * excclass,ErrorData * edata)534 PLy_exception_set_with_details(PyObject *excclass, ErrorData *edata)
535 {
536 	PyObject   *args = NULL;
537 	PyObject   *error = NULL;
538 
539 	args = Py_BuildValue("(s)", edata->message);
540 	if (!args)
541 		goto failure;
542 
543 	/* create a new exception with the error message as the parameter */
544 	error = PyObject_CallObject(excclass, args);
545 	if (!error)
546 		goto failure;
547 
548 	if (!set_string_attr(error, "sqlstate",
549 						 unpack_sql_state(edata->sqlerrcode)))
550 		goto failure;
551 
552 	if (!set_string_attr(error, "detail", edata->detail))
553 		goto failure;
554 
555 	if (!set_string_attr(error, "hint", edata->hint))
556 		goto failure;
557 
558 	if (!set_string_attr(error, "query", edata->internalquery))
559 		goto failure;
560 
561 	if (!set_string_attr(error, "schema_name", edata->schema_name))
562 		goto failure;
563 
564 	if (!set_string_attr(error, "table_name", edata->table_name))
565 		goto failure;
566 
567 	if (!set_string_attr(error, "column_name", edata->column_name))
568 		goto failure;
569 
570 	if (!set_string_attr(error, "datatype_name", edata->datatype_name))
571 		goto failure;
572 
573 	if (!set_string_attr(error, "constraint_name", edata->constraint_name))
574 		goto failure;
575 
576 	PyErr_SetObject(excclass, error);
577 
578 	Py_DECREF(args);
579 	Py_DECREF(error);
580 
581 	return;
582 
583 failure:
584 	Py_XDECREF(args);
585 	Py_XDECREF(error);
586 
587 	elog(ERROR, "could not convert error to Python exception");
588 }
589 
590 /* get string value of an object attribute */
591 static void
get_string_attr(PyObject * obj,char * attrname,char ** str)592 get_string_attr(PyObject *obj, char *attrname, char **str)
593 {
594 	PyObject   *val;
595 
596 	val = PyObject_GetAttrString(obj, attrname);
597 	if (val != NULL && val != Py_None)
598 	{
599 		*str = pstrdup(PyString_AsString(val));
600 	}
601 	Py_XDECREF(val);
602 }
603 
604 /* set an object attribute to a string value, returns true when the set was
605  * successful
606  */
607 static bool
set_string_attr(PyObject * obj,char * attrname,char * str)608 set_string_attr(PyObject *obj, char *attrname, char *str)
609 {
610 	int			result;
611 	PyObject   *val;
612 
613 	if (str != NULL)
614 	{
615 		val = PyString_FromString(str);
616 		if (!val)
617 			return false;
618 	}
619 	else
620 	{
621 		val = Py_None;
622 		Py_INCREF(Py_None);
623 	}
624 
625 	result = PyObject_SetAttrString(obj, attrname, val);
626 	Py_DECREF(val);
627 
628 	return result != -1;
629 }
630