This patch addresses the problem mentioned in the "process crash
when a plpython function returns unicode" thread:

http://archives.postgresql.org/pgsql-bugs/2005-06/msg00105.php

In several places PL/Python was calling PyObject_Str() and then
PyString_AsString() without checking if the former had returned
NULL to indicate an error.  PyString_AsString() doesn't expect a
NULL argument, so passing one causes a segmentation fault.  This
patch adds checks for NULL and raises errors via PLy_elog(), which
prints details of the underlying Python exception.  The patch also
adds regression tests for these checks.  All tests pass on my
Solaris 9 box running HEAD and Python 2.4.1.

In one place the patch doesn't call PLy_elog() because that could
cause infinite recursion; see the comment I added.  I'm not sure
how to test that particular case or whether it's even possible to
get an error there: the value that the code should check is the
Python exception type, so I wonder if a NULL value "shouldn't
happen."  This patch converts NULL to "Unknown Exception" but I
wonder if an Assert() would be appropriate.

The patch is against HEAD but the same changes should be applied
to earlier versions because they have the same problem.  The patch
might not apply cleanly against earlier versions -- will the committer
take care of little differences or should I submit different versions
of the patch?

-- 
Michael Fuhr
http://www.fuhr.org/~mfuhr/
Index: src/pl/plpython/plpython.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpython/plpython.c,v
retrieving revision 1.62
diff -c -r1.62 plpython.c
*** src/pl/plpython/plpython.c  6 May 2005 17:24:55 -0000       1.62
--- src/pl/plpython/plpython.c  27 Jun 2005 12:27:06 -0000
***************
*** 517,522 ****
--- 517,524 ----
                        if (plval != Py_None && 
!tupdesc->attrs[atti]->attisdropped)
                        {
                                plstr = PyObject_Str(plval);
+                               if (!plstr)
+                                       PLy_elog(ERROR, "function \"%s\" could 
not modify tuple", proc->proname);
                                src = PyString_AsString(plstr);
  
                                modvalues[i] = 
FunctionCall3(&proc->result.out.r.atts[atti].typfunc,
***************
*** 774,779 ****
--- 776,783 ----
                {
                        fcinfo->isnull = false;
                        plrv_so = PyObject_Str(plrv);
+                       if (!plrv_so)
+                               PLy_elog(ERROR, "function \"%s\" could not 
create return value", proc->proname);
                        plrv_sc = PyString_AsString(plrv_so);
                        rv = FunctionCall3(&proc->result.out.d.typfunc,
                                                           
PointerGetDatum(plrv_sc),
***************
*** 2019,2025 ****
                char       *sv;
  
                PyObject   *so = PyObject_Str(list);
! 
                sv = PyString_AsString(so);
                PLy_exception_set(PLy_exc_spi_error,
                                                  "Expected sequence of %d 
arguments, got %d. %s",
--- 2023,2031 ----
                char       *sv;
  
                PyObject   *so = PyObject_Str(list);
!               if (!so)
!                       PLy_elog(ERROR, "function \"%s\" could not execute 
plan",
!                                        
PLy_procedure_name(PLy_curr_procedure));
                sv = PyString_AsString(so);
                PLy_exception_set(PLy_exc_spi_error,
                                                  "Expected sequence of %d 
arguments, got %d. %s",
***************
*** 2044,2049 ****
--- 2050,2058 ----
                        if (elem != Py_None)
                        {
                                so = PyObject_Str(elem);
+                               if (!so)
+                                       PLy_elog(ERROR, "function \"%s\" could 
not execute plan",
+                                                        
PLy_procedure_name(PLy_curr_procedure));
                                sv = PyString_AsString(so);
  
                                /*
***************
*** 2531,2537 ****
        else
                vstr = "Unknown";
  
!       estr = PyString_AsString(eob);
        xstr = PLy_printf("%s: %s", estr, vstr);
  
        Py_DECREF(eob);
--- 2540,2552 ----
        else
                vstr = "Unknown";
  
!       /*
!        * I'm not sure what to do if eob is NULL here -- we can't call
!        * PLy_elog because that function calls us, so we could end up
!        * with infinite recursion.  I'm not even sure if eob could be
!        * NULL here -- would an Assert() be more appropriate?
!        */
!       estr = eob ? PyString_AsString(eob) : "Unknown Exception";
        xstr = PLy_printf("%s: %s", estr, vstr);
  
        Py_DECREF(eob);
Index: src/pl/plpython/expected/plpython_error.out
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpython/expected/plpython_error.out,v
retrieving revision 1.2
diff -c -r1.2 plpython_error.out
*** src/pl/plpython/expected/plpython_error.out 20 Jun 2005 21:14:01 -0000      
1.2
--- src/pl/plpython/expected/plpython_error.out 27 Jun 2005 12:27:06 -0000
***************
*** 19,21 ****
--- 19,38 ----
   
  (1 row)
  
+ --
+ -- Test Unicode error handling.
+ --
+ SELECT unicode_return_error();
+ ERROR:  plpython: function "unicode_return_error" could not create return 
value
+ DETAIL:  exceptions.UnicodeEncodeError: 'ascii' codec can't encode character 
u'\x80' in position 0: ordinal not in range(128)
+ INSERT INTO unicode_test (testvalue) VALUES ('test');
+ ERROR:  plpython: function "unicode_trigger_error" could not modify tuple
+ DETAIL:  exceptions.UnicodeEncodeError: 'ascii' codec can't encode character 
u'\x80' in position 0: ordinal not in range(128)
+ SELECT unicode_plan_error1();
+ WARNING:  plpython: in function unicode_plan_error1:
+ DETAIL:  plpy.Error: Unknown error in PLy_spi_execute_plan
+ ERROR:  plpython: function "unicode_plan_error1" could not execute plan
+ DETAIL:  exceptions.UnicodeEncodeError: 'ascii' codec can't encode character 
u'\x80' in position 0: ordinal not in range(128)
+ SELECT unicode_plan_error2();
+ ERROR:  plpython: function "unicode_plan_error2" could not execute plan
+ DETAIL:  exceptions.UnicodeEncodeError: 'ascii' codec can't encode character 
u'\x80' in position 0: ordinal not in range(128)
Index: src/pl/plpython/expected/plpython_function.out
===================================================================
RCS file: 
/projects/cvsroot/pgsql/src/pl/plpython/expected/plpython_function.out,v
retrieving revision 1.2
diff -c -r1.2 plpython_function.out
*** src/pl/plpython/expected/plpython_function.out      20 Jun 2005 21:14:01 
-0000      1.2
--- src/pl/plpython/expected/plpython_function.out      27 Jun 2005 12:27:07 
-0000
***************
*** 267,269 ****
--- 267,291 ----
  CREATE OR REPLACE FUNCTION newline_crlf() RETURNS integer AS
  'x = 100\r\ny = 23\r\nreturn x + y\r\n'
  LANGUAGE plpythonu;
+ --
+ -- Unicode error handling
+ --
+ CREATE FUNCTION unicode_return_error() RETURNS text AS '
+ return u"\\x80"
+ ' LANGUAGE plpythonu;
+ CREATE FUNCTION unicode_trigger_error() RETURNS trigger AS '
+ TD["new"]["testvalue"] = u"\\x80"
+ return "MODIFY"
+ ' LANGUAGE plpythonu;
+ CREATE TRIGGER unicode_test_bi BEFORE INSERT ON unicode_test
+   FOR EACH ROW EXECUTE PROCEDURE unicode_trigger_error();
+ CREATE FUNCTION unicode_plan_error1() RETURNS text AS '
+ plan = plpy.prepare("SELECT $1 AS testvalue", ["text"])
+ rv = plpy.execute(plan, [u"\\x80"], 1)
+ return rv[0]["testvalue"]
+ ' LANGUAGE plpythonu;
+ CREATE FUNCTION unicode_plan_error2() RETURNS text AS '
+ plan = plpy.prepare("SELECT $1 AS testvalue1, $2 AS testvalue2", ["text", 
"text"])
+ rv = plpy.execute(plan, u"\\x80", 1)
+ return rv[0]["testvalue1"]
+ ' LANGUAGE plpythonu;
Index: src/pl/plpython/expected/plpython_schema.out
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpython/expected/plpython_schema.out,v
retrieving revision 1.1
diff -c -r1.1 plpython_schema.out
*** src/pl/plpython/expected/plpython_schema.out        14 May 2005 17:55:21 
-0000      1.1
--- src/pl/plpython/expected/plpython_schema.out        27 Jun 2005 12:27:07 
-0000
***************
*** 41,43 ****
--- 41,46 ----
        sequence text not null
        ) ;
  CREATE INDEX xsequences_pid_idx ON xsequences(pid) ;
+ CREATE TABLE unicode_test (
+       testvalue  text NOT NULL
+ );
Index: src/pl/plpython/sql/plpython_error.sql
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpython/sql/plpython_error.sql,v
retrieving revision 1.2
diff -c -r1.2 plpython_error.sql
*** src/pl/plpython/sql/plpython_error.sql      20 Jun 2005 21:14:01 -0000      
1.2
--- src/pl/plpython/sql/plpython_error.sql      27 Jun 2005 12:27:07 -0000
***************
*** 7,9 ****
--- 7,18 ----
  SELECT invalid_type_caught('rick');
  SELECT invalid_type_reraised('rick');
  SELECT valid_type('rick');
+ 
+ --
+ -- Test Unicode error handling.
+ --
+ 
+ SELECT unicode_return_error();
+ INSERT INTO unicode_test (testvalue) VALUES ('test');
+ SELECT unicode_plan_error1();
+ SELECT unicode_plan_error2();
Index: src/pl/plpython/sql/plpython_function.sql
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpython/sql/plpython_function.sql,v
retrieving revision 1.2
diff -c -r1.2 plpython_function.sql
*** src/pl/plpython/sql/plpython_function.sql   20 Jun 2005 21:14:01 -0000      
1.2
--- src/pl/plpython/sql/plpython_function.sql   27 Jun 2005 12:27:07 -0000
***************
*** 313,315 ****
--- 313,343 ----
  CREATE OR REPLACE FUNCTION newline_crlf() RETURNS integer AS
  'x = 100\r\ny = 23\r\nreturn x + y\r\n'
  LANGUAGE plpythonu;
+ 
+ --
+ -- Unicode error handling
+ --
+ 
+ CREATE FUNCTION unicode_return_error() RETURNS text AS '
+ return u"\\x80"
+ ' LANGUAGE plpythonu;
+ 
+ CREATE FUNCTION unicode_trigger_error() RETURNS trigger AS '
+ TD["new"]["testvalue"] = u"\\x80"
+ return "MODIFY"
+ ' LANGUAGE plpythonu;
+ 
+ CREATE TRIGGER unicode_test_bi BEFORE INSERT ON unicode_test
+   FOR EACH ROW EXECUTE PROCEDURE unicode_trigger_error();
+ 
+ CREATE FUNCTION unicode_plan_error1() RETURNS text AS '
+ plan = plpy.prepare("SELECT $1 AS testvalue", ["text"])
+ rv = plpy.execute(plan, [u"\\x80"], 1)
+ return rv[0]["testvalue"]
+ ' LANGUAGE plpythonu;
+ 
+ CREATE FUNCTION unicode_plan_error2() RETURNS text AS '
+ plan = plpy.prepare("SELECT $1 AS testvalue1, $2 AS testvalue2", ["text", 
"text"])
+ rv = plpy.execute(plan, u"\\x80", 1)
+ return rv[0]["testvalue1"]
+ ' LANGUAGE plpythonu;
Index: src/pl/plpython/sql/plpython_schema.sql
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpython/sql/plpython_schema.sql,v
retrieving revision 1.1
diff -c -r1.1 plpython_schema.sql
*** src/pl/plpython/sql/plpython_schema.sql     14 May 2005 17:55:21 -0000      
1.1
--- src/pl/plpython/sql/plpython_schema.sql     27 Jun 2005 12:27:07 -0000
***************
*** 39,42 ****
        ) ;
  CREATE INDEX xsequences_pid_idx ON xsequences(pid) ;
  
! 
--- 39,44 ----
        ) ;
  CREATE INDEX xsequences_pid_idx ON xsequences(pid) ;
  
! CREATE TABLE unicode_test (
!       testvalue  text NOT NULL
! );
---------------------------(end of broadcast)---------------------------
TIP 1: subscribe and unsubscribe commands go to [EMAIL PROTECTED]

Reply via email to