Skip to content
Merged
3 changes: 1 addition & 2 deletions Grammar/python.gram
Original file line number Diff line number Diff line change
Expand Up @@ -1251,8 +1251,7 @@ invalid_expression:
# !(NAME STRING) is not matched so we don't show this error with some invalid string prefixes like: kf"dsfsdf"
# Soft keywords need to also be ignored because they can be parsed as NAME NAME
| !(NAME STRING | SOFT_KEYWORD) a=disjunction b=expression_without_invalid {
_PyPegen_check_legacy_stmt(p, a) ? NULL : p->tokens[p->mark-1]->level == 0 ? NULL :
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Perhaps you forgot a comma?") }
_PyPegen_raise_error_for_missing_comma(p, a, b) }
| a=disjunction 'if' b=disjunction !('else'|':') { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "expected 'else' after 'if' expression") }
| a=disjunction 'if' b=disjunction 'else' !expression {
RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("expected expression after 'else', but statement is given") }
Expand Down
4 changes: 2 additions & 2 deletions Include/internal/pycore_opcode_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Include/internal/pycore_uop_ids.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions Include/internal/pycore_uop_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions Lib/email/_header_value_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,12 @@ class MessageID(MsgID):
class InvalidMessageID(MessageID):
token_type = 'invalid-message-id'

class MessageIDList(TokenList):
token_type = 'message-id-list'

@property
def message_ids(self):
return [x for x in self if x.token_type=='msg-id']

class Header(TokenList):
token_type = 'header'
Expand Down Expand Up @@ -2175,6 +2181,32 @@ def parse_message_id(value):

return message_id

def parse_message_ids(value):
"""in-reply-to = "In-Reply-To:" 1*msg-id CRLF
references = "References:" 1*msg-id CRLF
"""
message_id_list = MessageIDList()
while value:
if value[0] == ',':
# message id list separated with commas - this is invalid,
# but happens rather frequently in the wild
message_id_list.defects.append(
errors.InvalidHeaderDefect("comma in msg-id list"))
message_id_list.append(
WhiteSpaceTerminal(' ', 'invalid-comma-replacement'))
value = value[1:]
continue
try:
token, value = get_msg_id(value)
message_id_list.append(token)
except errors.HeaderParseError as ex:
token = get_unstructured(value)
message_id_list.append(InvalidMessageID(token))
message_id_list.defects.append(
errors.InvalidHeaderDefect("Invalid msg-id: {!r}".format(ex)))
break
return message_id_list

#
# XXX: As I begin to add additional header parsers, I'm realizing we probably
# have two level of parser routines: the get_XXX methods that get a token in
Expand Down
14 changes: 14 additions & 0 deletions Lib/email/headerregistry.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,18 @@ def parse(cls, value, kwds):
kwds['defects'].extend(parse_tree.all_defects)


class ReferencesHeader:

max_count = 1
value_parser = staticmethod(parser.parse_message_ids)

@classmethod
def parse(cls, value, kwds):
kwds['parse_tree'] = parse_tree = cls.value_parser(value)
kwds['decoded'] = str(parse_tree)
kwds['defects'].extend(parse_tree.all_defects)


# The header factory #

_default_header_map = {
Expand All @@ -557,6 +569,8 @@ def parse(cls, value, kwds):
'content-disposition': ContentDispositionHeader,
'content-transfer-encoding': ContentTransferEncodingHeader,
'message-id': MessageIDHeader,
'in-reply-to': ReferencesHeader,
'references': ReferencesHeader,
}

class HeaderRegistry:
Expand Down
16 changes: 16 additions & 0 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -1925,6 +1925,21 @@ def testfunc(n):
uops = get_opnames(ex)
self.assertNotIn("_GUARD_IS_NOT_NONE_POP", uops)

def test_call_tuple_1_pop_top(self):
def testfunc(n):
x = 0
for _ in range(n):
t = tuple(())
x += len(t) == 0
return x

res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, TIER2_THRESHOLD)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_CALL_TUPLE_1", uops)
self.assertIn("_POP_TOP_NOP", uops)

def test_call_str_1(self):
def testfunc(n):
x = 0
Expand Down Expand Up @@ -2051,6 +2066,7 @@ def testfunc(n):
self.assertIn("_CALL_LEN", uops)
self.assertNotIn("_GUARD_NOS_INT", uops)
self.assertNotIn("_GUARD_TOS_INT", uops)
self.assertIn("_POP_TOP_NOP", uops)

def test_call_len_known_length_small_int(self):
# Make sure that len(t) is optimized for a tuple of length 5.
Expand Down
75 changes: 75 additions & 0 deletions Lib/test/test_email/test__header_value_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2867,6 +2867,81 @@ def test_get_msg_id_ws_only_local(self):
)
self.assertEqual(msg_id.token_type, 'msg-id')

def test_parse_message_ids_valid(self):
message_ids = self._test_parse_x(
parser.parse_message_ids,
"<foo@bar> <bar@foo>",
"<foo@bar> <bar@foo>",
"<foo@bar> <bar@foo>",
[],
)
self.assertEqual(message_ids.token_type, 'message-id-list')

def test_parse_message_ids_empty(self):
message_ids = self._test_parse_x(
parser.parse_message_ids,
" ",
" ",
" ",
[errors.InvalidHeaderDefect],
)
self.assertEqual(message_ids.token_type, 'message-id-list')

def test_parse_message_ids_comment(self):
message_ids = self._test_parse_x(
parser.parse_message_ids,
"<foo@bar> (foo's message from \"bar\")",
"<foo@bar> (foo's message from \"bar\")",
"<foo@bar> ",
[],
)
self.assertEqual(message_ids.message_ids[0].value, '<foo@bar> ')
self.assertEqual(message_ids.token_type, 'message-id-list')

def test_parse_message_ids_no_sep(self):
message_ids = self._test_parse_x(
parser.parse_message_ids,
"<foo@bar><bar@foo>",
"<foo@bar><bar@foo>",
"<foo@bar><bar@foo>",
[],
)
self.assertEqual(message_ids.message_ids[0].value, '<foo@bar>')
self.assertEqual(message_ids.message_ids[1].value, '<bar@foo>')
self.assertEqual(message_ids.token_type, 'message-id-list')

def test_parse_message_ids_comma_sep(self):
message_ids = self._test_parse_x(
parser.parse_message_ids,
"<foo@bar>,<bar@foo>",
"<foo@bar> <bar@foo>",
"<foo@bar> <bar@foo>",
[errors.InvalidHeaderDefect],
)
self.assertEqual(message_ids.message_ids[0].value, '<foo@bar>')
self.assertEqual(message_ids.message_ids[1].value, '<bar@foo>')
self.assertEqual(message_ids.token_type, 'message-id-list')

def test_parse_message_ids_invalid_id(self):
message_ids = self._test_parse_x(
parser.parse_message_ids,
"<Date: Wed, 08 Jun 2002 09:78:58 +0600>",
"<Date: Wed, 08 Jun 2002 09:78:58 +0600>",
"<Date: Wed, 08 Jun 2002 09:78:58 +0600>",
[errors.InvalidHeaderDefect]*2,
)
self.assertEqual(message_ids.token_type, 'message-id-list')

def test_parse_message_ids_broken_ang(self):
message_ids = self._test_parse_x(
parser.parse_message_ids,
"<foo@bar> >bar@foo",
"<foo@bar> >bar@foo",
"<foo@bar> >bar@foo",
[errors.InvalidHeaderDefect]*1,
)
self.assertEqual(message_ids.token_type, 'message-id-list')



@parameterize
Expand Down
13 changes: 13 additions & 0 deletions Lib/test/test_email/test_headerregistry.py
Original file line number Diff line number Diff line change
Expand Up @@ -1821,5 +1821,18 @@ def test_message_id_header_is_not_folded(self):
h.fold(policy=policy.default.clone(max_line_length=20)),
'Message-ID:\n <ईमेलfromMessage@wők.com>\n')

def test_fold_references(self):
h = self.make_header(
'References',
'<referenceid1thatislongerthan@maxlinelength.com> '
'<referenceid2thatislongerthan@maxlinelength.com>'
)
self.assertEqual(
h.fold(policy=policy.default.clone(max_line_length=20)),
'References: '
'<referenceid1thatislongerthan@maxlinelength.com>\n'
' <referenceid2thatislongerthan@maxlinelength.com>\n')


if __name__ == '__main__':
unittest.main()
14 changes: 14 additions & 0 deletions Lib/test/test_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -3336,6 +3336,20 @@ def test_multiline_compiler_error_points_to_the_end(self):
lineno=3
)

def test_multiline_string_concat_missing_comma_points_to_last_string(self):
# gh-142236: For multi-line string concatenations with a missing comma,
# the error should point to the last string, not the first.
self._check_error(
"print(\n"
' "line1"\n'
' "line2"\n'
' "line3"\n'
" x=1\n"
")",
"Perhaps you forgot a comma",
lineno=4, # Points to "line3", the last string
)

@support.cpython_only
def test_syntax_error_on_deeply_nested_blocks(self):
# This raises a SyntaxError, it used to raise a SystemError. Context
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Eliminate redundant refcounting from ``_CALL_TUPLE_1``. Patch by Noam Cohen
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Improve the "Perhaps you forgot a comma?" syntax error for multi-line string
concatenations to point to the last string instead of the first, making it
easier to locate where the comma is missing. Patch by Pablo Galindo.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add parsing for ``References`` and ``In-Reply-To`` headers to the :mod:`email`
library that parses the header content as lists of message id tokens. This
prevents them from being folded incorrectly.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed a possible leaked GIL in _PySSL_keylog_callback.
4 changes: 3 additions & 1 deletion Modules/_ssl/debughelpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ _PySSL_keylog_callback(const SSL *ssl, const char *line)
PyThread_type_lock lock = get_state_sock(ssl_obj)->keylog_lock;
assert(lock != NULL);
if (ssl_obj->ctx->keylog_bio == NULL) {
return;
goto done;
}
/*
* The lock is neither released on exit nor on fork(). The lock is
Expand All @@ -155,6 +155,8 @@ _PySSL_keylog_callback(const SSL *ssl, const char *line)
ssl_obj->ctx->keylog_filename);
ssl_obj->exc = PyErr_GetRaisedException();
}

done:
PyGILState_Release(threadstate);
}

Expand Down
2 changes: 1 addition & 1 deletion Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -7903,7 +7903,7 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a
if (argc < 1) {
PyErr_Format(PyExc_ValueError,
"%s: argv must not be empty", func_name);
return NULL;
goto exit;
}

if (!PyMapping_Check(env) && env != Py_None) {
Expand Down
12 changes: 3 additions & 9 deletions PC/clinic/winreg.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading