Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions Doc/builtins/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,13 @@ are always available. They are listed here in alphabetical order.
It is needed to unambiguous :ref:`filter <warning-filter>` syntax warnings
by module name.

This function raises :exc:`SyntaxError` or :exc:`ValueError` if the compiled
source is invalid.
This function raises :exc:`SyntaxError` if the compiled source is invalid,
including a *source* containing a null or surrogate character,
that cannot be decoded,
that is too complex to parse or compile,
for example an expression with many thousands of nested operators,
or that is too large;
and :exc:`ValueError` if *mode* or *flags* is invalid.

If you want to parse Python code into its AST representation, see
:func:`ast.parse`.
Expand Down Expand Up @@ -417,13 +422,24 @@ are always available. They are listed here in alphabetical order.
Previously, :exc:`TypeError` was raised when null bytes were encountered
in *source*.

.. versionadded:: 3.8
.. versionchanged:: 3.8
``ast.PyCF_ALLOW_TOP_LEVEL_AWAIT`` can now be passed in flags to enable
support for top-level ``await``, ``async for``, and ``async with``.

.. versionadded:: 3.15
.. versionchanged:: 3.12
:exc:`SyntaxError` is raised instead of :exc:`ValueError` when null bytes
are encountered in *source*.

.. versionchanged:: 3.15
Added the *module* parameter.

.. versionchanged:: next
:exc:`SyntaxError` is raised instead of :exc:`ValueError` when surrogate
characters are encountered in *source*,
instead of :exc:`MemoryError` or :exc:`RecursionError`
when *source* is too complex,
and instead of :exc:`OverflowError` when *source* is too large.


.. class:: complex(number=0, /)
complex(string, /)
Expand Down
5 changes: 2 additions & 3 deletions Doc/library/ast.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2338,9 +2338,8 @@ and classes for traversing abstract syntax trees:
It is possible to crash the Python interpreter due to stack depth
limitations in Python's AST compiler.

It can raise :exc:`ValueError`, :exc:`TypeError`, :exc:`SyntaxError`,
:exc:`MemoryError` and :exc:`RecursionError` depending on the malformed
input.
It can raise :exc:`ValueError`, :exc:`TypeError` or :exc:`SyntaxError`
depending on the malformed input.

.. versionchanged:: 3.2
Now allows bytes and set literals.
Expand Down
8 changes: 3 additions & 5 deletions Doc/library/code.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,7 @@ build applications which provide an interactive interpreter prompt.

Returns a code object (the same as ``compile(source, filename, symbol)``) if the
command is complete and valid; ``None`` if the command is incomplete; raises
:exc:`SyntaxError` if the command is complete and contains a syntax error, or
raises :exc:`OverflowError` or :exc:`ValueError` if the command contains an
invalid literal.
:exc:`SyntaxError` if the command is complete and invalid.


.. _interpreter-objects:
Expand All @@ -91,8 +89,8 @@ Interactive Interpreter Objects
:func:`compile_command`; the default for *filename* is ``'<input>'``, and for
*symbol* is ``'single'``. One of several things can happen:

* The input is incorrect; :func:`compile_command` raised an exception
(:exc:`SyntaxError` or :exc:`OverflowError`). A syntax traceback will be
* The input is incorrect; :func:`compile_command` raised
:exc:`SyntaxError`. A syntax traceback will be
printed by calling the :meth:`showsyntaxerror` method. :meth:`runsource`
returns ``False``.

Expand Down
4 changes: 1 addition & 3 deletions Doc/library/codeop.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ To do just the former:
``'<input>'``. Returns ``None`` if *source* is *not* valid Python code, but is a
prefix of valid Python code.

If there is a problem with *source*, an exception will be raised.
:exc:`SyntaxError` is raised if there is invalid Python syntax, and
:exc:`OverflowError` or :exc:`ValueError` if there is an invalid literal.
If there is a problem with *source*, :exc:`SyntaxError` is raised.

The *symbol* argument determines whether *source* is compiled as a statement
(``'single'``, the default), as a sequence of :term:`statement` (``'exec'``) or
Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ PyAPI_FUNC(PyCodeObject*) _PyAST_Compile(
PyObject *module);

/* AST preprocessing */
extern void _PyCompile_CheckRecursionError(void);

extern int _PyCompile_AstPreprocess(
struct _mod *mod,
PyObject *filename,
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_pythonrun.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ extern const char* _Py_SourceAsString(
PyObject *cmd,
const char *funcname,
const char *what,
PyObject *filename,
PyCompilerFlags *cf,
PyObject **cmd_copy);

Expand Down
2 changes: 1 addition & 1 deletion Lib/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def runsource(self, source, filename="<input>", symbol="single"):
One of several things can happen:

1) The input is incorrect; compile_command() raised an
exception (SyntaxError or OverflowError). A syntax traceback
exception (SyntaxError). A syntax traceback
will be printed by calling the showsyntaxerror() method.

2) The input is incomplete, and more input is required;
Expand Down
12 changes: 3 additions & 9 deletions Lib/codeop.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@

- Return code object if the command is complete and valid
- Return None if the command is incomplete
- Raise SyntaxError, ValueError or OverflowError if the command is a
syntax error (OverflowError and ValueError can be produced by
malformed literals).
- Raise SyntaxError if the command is a syntax error.

The two interfaces are:

Expand Down Expand Up @@ -95,9 +93,7 @@ def compile_command(source, filename="<input>", symbol="single", flags=0):

- Return a code object if the command is complete and valid
- Return None if the command is incomplete
- Raise SyntaxError, ValueError or OverflowError if the command is a
syntax error (OverflowError and ValueError can be produced by
malformed literals).
- Raise SyntaxError if the command is a syntax error.
"""
return _maybe_compile(_compile, source, filename, symbol, flags)

Expand Down Expand Up @@ -147,8 +143,6 @@ def __call__(self, source, filename="<input>", symbol="single"):

- Return a code object if the command is complete and valid
- Return None if the command is incomplete
- Raise SyntaxError, ValueError or OverflowError if the command is a
syntax error (OverflowError and ValueError can be produced by
malformed literals).
- Raise SyntaxError if the command is a syntax error.
"""
return _maybe_compile(self.compiler, source, filename, symbol, flags=self.compiler.flags)
3 changes: 2 additions & 1 deletion Lib/test/test_ast/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -1126,7 +1126,8 @@ def check_limit(prefix, repeated):
broken = prefix + repeated * crash_depth
details = "Compiling ({!r} + {!r} * {})".format(
prefix, repeated, crash_depth)
with self.assertRaises(RecursionError, msg=details):
with self.assertRaisesRegex(SyntaxError, "too complex to compile",
msg=details):
with support.infinite_recursion():
ast.parse(broken)

Expand Down
7 changes: 7 additions & 0 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,13 @@ def test_compile(self):
mode='eval', source='0', filename='tmp')
compile('print("\xe5")\n', '', 'exec')
self.assertRaises(SyntaxError, compile, chr(0), 'f', 'exec')
with self.assertRaises(SyntaxError) as cm:
compile("x = 1\ny = 'ab\udc80cd'\n", 'f', 'exec')
self.assertEqual((cm.exception.filename, cm.exception.lineno,
cm.exception.offset, cm.exception.text),
('f', 2, 8, "y = 'ab\udc80cd'"))
self.assertRaises(SyntaxError, eval, '\udc80')
self.assertRaises(SyntaxError, exec, '\udc80')
self.assertRaises(ValueError, compile, str('a = 1'), 'f', 'bad')

# test the optimize argument
Expand Down
12 changes: 8 additions & 4 deletions Lib/test/test_code_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,14 @@ def test_unicode_error(self):
self.console.interact()
output = ''.join(''.join(call[1]) for call in self.stderr.method_calls)
output = output[output.index('(InteractiveConsole)'):]
output = output[output.index('\n') + 1:]
self.assertStartsWith(output, 'UnicodeEncodeError: ')
self.assertIs(self.sysmod.last_type, UnicodeEncodeError)
self.assertIs(type(self.sysmod.last_value), UnicodeEncodeError)
output = output[:output.index('\nnow exiting')]
self.assertEqual(output.splitlines()[1:], [
' File "<console>", line 1',
" '\ud800'",
' ^',
'SyntaxError: source code string cannot contain surrogate characters'])
self.assertIs(self.sysmod.last_type, SyntaxError)
self.assertIs(type(self.sysmod.last_value), SyntaxError)
self.assertIsNone(self.sysmod.last_traceback)
self.assertIsNone(self.sysmod.last_value.__traceback__)
self.assertIs(self.sysmod.last_exc, self.sysmod.last_value)
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,8 @@ def check_limit(prefix, repeated, mode="single"):
compile(expect_ok, '<test>', mode)
broken = prefix + repeated * crash_depth
details = f"Compiling ({prefix!r} + {repeated!r} * {crash_depth})"
with self.assertRaises(RecursionError, msg=details):
with self.assertRaisesRegex(SyntaxError, "too complex to compile",
msg=details):
compile(broken, '<test>', mode)

check_limit("a", "()")
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -3543,7 +3543,7 @@ def test_error_on_parser_stack_overflow(self):
source = "-" * 100000 + "4"
for mode in ["exec", "eval", "single"]:
with self.subTest(mode=mode):
with self.assertRaisesRegex(MemoryError, r"too complex"):
with self.assertRaisesRegex(SyntaxError, r"too complex to parse"):
compile(source, "<string>", mode)

@support.cpython_only
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
:func:`compile`, :func:`exec`, :func:`eval` and :func:`ast.parse` now raise
:exc:`SyntaxError` instead of :exc:`ValueError` if the source string contains
surrogate characters, instead of :exc:`MemoryError` or :exc:`RecursionError`
if the source is too complex to parse or compile, and instead of
:exc:`OverflowError` if the source is too large.
2 changes: 1 addition & 1 deletion Modules/symtablemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ _symtable_symtable_impl(PyObject *module, PyObject *source,
cf.cf_flags = PyCF_SOURCE_IS_UTF8;
const char *str = _Py_SourceAsString(source, "symtable",
"string, bytes or AST",
&cf, &source_copy);
filename, &cf, &source_copy);
if (str == NULL) {
return NULL;
}
Expand Down
2 changes: 2 additions & 0 deletions Parser/pegen.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "pycore_ast.h" // _PyAST_Validate(),
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_parser.h" // _PYPEGEN_NSTATISTICS
#include "pycore_compile.h" // _PyCompile_CheckRecursionError()
#include "pycore_pyerrors.h" // PyExc_IncompleteInputError
#include "pycore_runtime.h" // _PyRuntime
#include "pycore_unicodeobject.h" // _PyUnicode_InternImmortal
Expand Down Expand Up @@ -1063,6 +1064,7 @@ _PyPegen_run_parser(Parser *p)
p->start_rule == Py_eval_input)
{
if (!_PyAST_Validate(res)) {
_PyCompile_CheckRecursionError();
return NULL;
}
}
Expand Down
8 changes: 3 additions & 5 deletions Parser/pegen_errors.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,8 @@ _Pypegen_tokenizer_error(Parser *p)
break;
}
case E_COLUMNOVERFLOW:
PyErr_SetString(PyExc_OverflowError,
"Parser column offset overflow - source line is too big");
return -1;
msg = "source line is too long";
break;
default:
msg = "unknown parsing error";
}
Expand Down Expand Up @@ -391,6 +390,5 @@ void
_Pypegen_stack_overflow(Parser *p)
{
p->error_indicator = 1;
PyErr_SetString(PyExc_MemoryError,
"Parser stack overflowed - Python source too complex to parse");
RAISE_SYNTAX_ERROR("Python source too complex to parse");
}
2 changes: 1 addition & 1 deletion Parser/string_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ _PyPegen_parse_string(Parser *p, Token *t)
assert(len >= 1);

if (len > INT_MAX) {
PyErr_SetString(PyExc_OverflowError, "string to parse is too long");
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(t, "string literal is too long");
return NULL;
}
if (s[--len] != quote) {
Expand Down
2 changes: 1 addition & 1 deletion Parser/tokenizer/source.c
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ validate_line(const _PyTok_SourceText *source, const char *bytes,
}
if (source->nlines == INT_MAX ||
(source->nlines == INT_MAX - 1 && newline != NULL)) {
PyErr_SetString(PyExc_OverflowError, "too many tokenizer source lines");
PyErr_SetString(PyExc_SyntaxError, "too many lines in source");
return -1;
}
return 0;
Expand Down
8 changes: 5 additions & 3 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -949,7 +949,8 @@ builtin_compile_impl(PyObject *module, PyObject *source, PyObject *filename,
goto finally;
}

str = _Py_SourceAsString(source, "compile", "string, bytes or AST", &cf, &source_copy);
str = _Py_SourceAsString(source, "compile", "string, bytes or AST",
filename, &cf, &source_copy);
if (str == NULL)
goto error;

Expand Down Expand Up @@ -1116,7 +1117,8 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,
else {
PyCompilerFlags cf = _PyCompilerFlags_INIT;
cf.cf_flags = PyCF_SOURCE_IS_UTF8;
str = _Py_SourceAsString(source, "eval", "string, bytes or code", &cf, &source_copy);
str = _Py_SourceAsString(source, "eval", "string, bytes or code",
NULL, &cf, &source_copy);
if (str == NULL)
goto error;

Expand Down Expand Up @@ -1286,7 +1288,7 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
PyCompilerFlags cf = _PyCompilerFlags_INIT;
cf.cf_flags = PyCF_SOURCE_IS_UTF8;
str = _Py_SourceAsString(source, "exec",
"string, bytes or code", &cf,
"string, bytes or code", NULL, &cf,
&source_copy);
if (str == NULL)
goto error;
Expand Down
17 changes: 17 additions & 0 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1524,19 +1524,35 @@ _PyCompile_OptimizeAndAssemble(compiler *c, int addNone)
return optimize_and_assemble_code_unit(u, const_cache, code_flags, filename);
}

/* Replace a RecursionError raised while processing too deeply nested
source with a SyntaxError, as the parser raises for such source. */
void
_PyCompile_CheckRecursionError(void)
{
if (PyErr_ExceptionMatches(PyExc_RecursionError)) {
PyErr_Clear();
PyErr_SetString(PyExc_SyntaxError,
"Python source too complex to compile");
}
}

PyCodeObject *
_PyAST_Compile(mod_ty mod, PyObject *filename, PyCompilerFlags *pflags,
int optimize, PyArena *arena, PyObject *module)
{
assert(!PyErr_Occurred());
compiler *c = new_compiler(mod, filename, pflags, optimize, arena, module);
if (c == NULL) {
_PyCompile_CheckRecursionError();
return NULL;
}

PyCodeObject *co = compiler_mod(c, mod);
compiler_free(c);
assert(co || PyErr_Occurred());
if (co == NULL) {
_PyCompile_CheckRecursionError();
}
return co;
}

Expand All @@ -1556,6 +1572,7 @@ _PyCompile_AstPreprocess(mod_ty mod, PyObject *filename, PyCompilerFlags *cf,
if (!_PyAST_Preprocess(mod, arena, filename, optimize, flags,
no_const_folding, 0, module))
{
_PyCompile_CheckRecursionError();
return -1;
}
return 0;
Expand Down
2 changes: 1 addition & 1 deletion Python/crossinterp.c
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,7 @@ get_script_xidata(PyThreadState *tstate, PyObject *obj, int pure,
PyCompilerFlags cf = _PyCompilerFlags_INIT;
cf.cf_flags = PyCF_SOURCE_IS_UTF8;
PyObject *ref = NULL;
const char *script = _Py_SourceAsString(obj, "???", "???", &cf, &ref);
const char *script = _Py_SourceAsString(obj, "???", "???", NULL, &cf, &ref);
if (script == NULL) {
if (!_PyObject_SupportedAsScript(obj)) {
// We discard the raised exception.
Expand Down
Loading
Loading