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