Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Doc/library/ast.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2254,7 +2254,7 @@ and classes for traversing abstract syntax trees:

In addition, if ``mode`` is ``'func_type'``, the input syntax is
modified to correspond to :pep:`484` "signature type comments",
e.g. ``(str, int) -> List[str]``.
for example ``(str, int) -> List[str]``.

Setting ``feature_version`` to a tuple ``(major, minor)`` will result in
a "best-effort" attempt to parse using that Python version's grammar.
Expand Down
5 changes: 4 additions & 1 deletion Lib/test/support/os_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,10 @@ def _rmtree_inner(path):
file=sys.__stderr__)
mode = 0
if stat.S_ISDIR(mode):
_waitfor(_rmtree_inner, fullname, waitall=True)
# Do not follow junctions, which os.lstat() reports
# as directories.
if not os.path.isjunction(fullname):
_waitfor(_rmtree_inner, fullname, waitall=True)
_force_run(fullname, os.rmdir, fullname)
else:
_force_run(fullname, os.unlink, fullname)
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_ast/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,15 @@ def test_parse_invalid_ast(self):
self.assertRaises(TypeError, ast.parse, ast.Constant(42),
optimize=optval)

def test_parse_ast_func_type(self):
# see gh-156689
tree = ast.parse('(int, str) -> bool', mode='func_type')
self.assertEqual(ast.dump(ast.parse(tree, mode='func_type')),
ast.dump(tree))
self.assertRaises(TypeError, ast.parse, ast.Constant(42),
mode='func_type')
self.assertRaises(TypeError, ast.parse, tree, mode='exec')

def test_optimization_levels__debug__(self):
cases = [(-1, '__debug__'), (0, '__debug__'), (1, False), (2, False)]
for (optval, expected) in cases:
Expand Down
46 changes: 42 additions & 4 deletions Lib/test/test_capi/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,11 +299,11 @@ def test_join(self):
bytes_join(b'', NULL)


class BytesWriterTest(unittest.TestCase):
result_type = bytes
class BaseWriterTest:
result_type = NotImplementedError

def create_writer(self, alloc=0, string=b''):
return _testcapi.PyBytesWriter(alloc, string, 0)
raise NotImplementedError

def test_create(self):
# Test PyBytesWriter_Create()
Expand Down Expand Up @@ -388,10 +388,48 @@ def test_example_highlevel(self):
self.assertEqual(_testcapi.byteswriter_highlevel(), b'Hello World!')


class ByteArrayWriterTest(BytesWriterTest):
class BytesWriterTest(BaseWriterTest, unittest.TestCase):
result_type = bytes

def create_writer(self, alloc=0, string=b''):
# Test PyBytesWriter_Create()
return _testcapi.PyBytesWriter(alloc, string, 0)

# Only PyBytesWriter_Create() returns singletons
def test_singletons(self):
empty = b''
singletons = {ch: bytes((ch,)) for ch in range(256)}
small_buffer = _testcapi.PyBytesWriter_small_buffer

writer = self.create_writer()
self.assertIs(writer.finish(), empty)

# Test writer larger than small_buffer
writer = self.create_writer()
unused_text = b'x' * (small_buffer * 2)
writer.write_bytes(unused_text, len(unused_text))
self.assertIs(writer.finish_with_size(0), empty)

for ch in range(256):
text = bytes((ch,))

writer = self.create_writer()
writer.write_bytes(text, 1)
self.assertIs(writer.finish(), singletons[ch])

# Test writer larger than small_buffer
writer = self.create_writer()
writer.write_bytes(text, 1)
unused_text = b'x' * (small_buffer * 2)
writer.write_bytes(unused_text, len(unused_text))
self.assertIs(writer.finish_with_size(1), singletons[ch])


class ByteArrayWriterTest(BaseWriterTest, unittest.TestCase):
result_type = bytearray

def create_writer(self, alloc=0, string=b''):
# Test private _PyBytesWriter_CreateByteArray()
return _testcapi.PyBytesWriter(alloc, string, 1)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:c:func:`PyBytesWriter_FinishWithSize` now returns single byte singletons if
*size* equals to ``1``. Patch by Victor Stinner.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix an out-of-bounds read in :func:`compile` and :func:`ast.parse` when an AST
object is passed with ``mode='func_type'``.
13 changes: 11 additions & 2 deletions Modules/_testcapi/bytes.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include "parts.h"
#include "util.h"

#include <stddef.h> // offsetof()

#include "pycore_bytesobject.h" // _PyBytesWriter_CreateByteArray()


Expand Down Expand Up @@ -150,8 +152,8 @@ writer_write_bytes(PyObject *self_raw, PyObject *args)
}

char *bytes;
Py_ssize_t size;
if (!PyArg_ParseTuple(args, "yn", &bytes, &size)) {
Py_ssize_t unused_size, size;
if (!PyArg_ParseTuple(args, "y#n", &bytes, &unused_size, &size)) {
return NULL;
}

Expand Down Expand Up @@ -377,5 +379,12 @@ _PyTestCapi_Init_Bytes(PyObject *m)
}
Py_DECREF(writer_type);

// PyBytesWriter.obj is the second member, small_buffer is the first member
long size = (long)offsetof(PyBytesWriter, obj);
if (PyModule_AddIntConstant(m, "PyBytesWriter_small_buffer", size) < 0) {
Py_DECREF(writer_type);
return -1;
}

return 0;
}
10 changes: 10 additions & 0 deletions Objects/bytesobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3766,13 +3766,23 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
}
}
}

result = writer->obj;
writer->obj = NULL;

if (size == 1 && !writer->use_bytearray) {
// Get the single byte singleton
unsigned char ch = PyBytes_AS_STRING(result)[0];
PyObject *op = (PyObject*)CHARACTER(ch);
assert(_Py_IsImmortal(op));
Py_SETREF(result, op);
}
}
else if (writer->use_bytearray) {
result = PyByteArray_FromStringAndSize(writer->small_buffer, size);
}
else {
// The function returns single byte singleton if size equals 1
result = PyBytes_FromStringAndSize(writer->small_buffer, size);
}
PyBytesWriter_Discard(writer);
Expand Down
11 changes: 7 additions & 4 deletions Parser/asdl_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -2122,22 +2122,25 @@ class PartingShots(StaticVisitor):
return result;
}

/* mode is 0 for "exec", 1 for "eval" and 2 for "single" input */
/* mode is 0 for "exec", 1 for "eval", 2 for "single" and 3 for "func_type"
input */
int PyAst_CheckMode(PyObject *ast, int mode)
{
const char * const req_name[] = {"Module", "Expression", "Interactive"};
const char * const req_name[] = {"Module", "Expression", "Interactive",
"FunctionType"};

struct ast_state *state = get_ast_state();
if (state == NULL) {
return -1;
}

PyObject *req_type[3];
PyObject *req_type[4];
req_type[0] = state->Module_type;
req_type[1] = state->Expression_type;
req_type[2] = state->Interactive_type;
req_type[3] = state->FunctionType_type;

assert(0 <= mode && mode <= 2);
assert(0 <= mode && mode <= 3);
int isinstance = PyObject_IsInstance(ast, req_type[mode]);
if (isinstance == -1) {
return -1;
Expand Down
11 changes: 7 additions & 4 deletions Python/Python-ast.c

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

Loading