Issue 763201: 3 bugs fixed: handling of SyntaxErrors in symbol table build (original) (raw)
All three bugs found in both 2.2.3 and 2.3b2. It's one patch instead of three because the fixes share a refactoring of Python/compile.c:symtable_build.
Bug 1: Wrong file name
import symtable
symtable.symtable('def foo(a): global a', 'spam', 'exec')
The error message says '' instead of 'spam'. (Cause: PyNode_CompileSymtable doesn't set st_filename.)
Bug 2: Memory leak
while True:
try:
compile('def foo(a): global a', 'spam', 'exec')
except SyntaxError:
pass
(Cause: symtable_build doesn't free c->c_symtable on error.)
The patch is missing a test case for this one; I don't see how to write it.
Bug 3: Exception clobbered
def foo(a):
global a # SyntaxError
def bar():
b = 1
global b # SyntaxWarning
Running this as a script, the SyntaxWarning is issued, and then the interpreter dies silently (that is, without printing a traceback reporting the SyntaxError). Compiling it with compile() causes a SystemError. (Cause: see below.)
What to do about bugs 1 and 2 is obvious, but bug 3 (which was actually reported on c.l.py) is not so clear to me. Here's how the problem occurs:
symtable_global() sees the global statement in foo(),
raises a
SyntaxError, increments st_errors, and returns.
Processing
continues. Later, symtable_global() sees the global
statement in
bar() and issues a warning by (indirectly) calling
PyErr_WarnExplicit(). This call clears the SyntaxError,
which is
still pending at that time. (It's actually cleared during the
attempt to import the warnings module, in
PyImport_Import, which
seems to think the exception was raised in
PyEval_GetGlobals.)
But st_errors is still > 0, as it should be, so
symtable_build()
returns -1 (resp. PyNode_CompileSymtable, NULL), its
callers
return their error values, etc., until eventually PyErr_Print
tries to print the exception that isn't there.
What the patch implements is this:
Do not issue SyntaxWarnings if an exception is pending; fail instead and let that exception propagate. Also, as a defensive measure against other bugs of this type (present and future), when returning with error from symtable_build(), verify that there's an exception pending (and raise SystemError if not). Finally, refactor symtable_build() so PyNode_CompileSymtable can use it and thereby benefit from that defensive measure.
Alternatives (and why I don't like them):
Do not try to continue processing after a SyntaxError is raised. (Seems like the Right Thing to me, but also seems to be contrary to the intent of the existing code. There are oodles of places in compile.c which call symtable_node without checking st_errors immediately afterwards.)
Put the check for a pending exception in PyErr_WarnExplicit() instead of in the helper function in compile.c. (Doesn't seem like a common enough coding error to merit a check there. In symtable_node etc we deliberately let SyntaxErrors, er, "pend" while we do a bit more compiling, so there it's worth checking. Note that jcompile() already has a check for something similar, though not for the symbol-table-building phase.)
Squirrel the pending exception away, issue the warning, then restore the exception. (Not worth the bother, IMO. And if the warning gets strengthened into an exception, should that exception or the squirrelled one propagate? Ick.)