Skip to content

Commit b2cbe7f

Browse files
committed
Merge branch 'remove-__package__' of github.com:brettcannon/cpython into remove-__package__
2 parents 2fe0f74 + 7f373ed commit b2cbe7f

File tree

13 files changed

+196
-29
lines changed

13 files changed

+196
-29
lines changed

Lib/encodings/punycode.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def segregate(str):
1717
else:
1818
extended.add(c)
1919
extended = sorted(extended)
20-
return bytes(base), extended
20+
return base.take_bytes(), extended
2121

2222
def selective_len(str, max):
2323
"""Return the length of str, considering only characters below max."""
@@ -83,7 +83,7 @@ def generate_generalized_integer(N, bias):
8383
t = T(j, bias)
8484
if N < t:
8585
result.append(digits[N])
86-
return bytes(result)
86+
return result.take_bytes()
8787
result.append(digits[t + ((N - t) % (36 - t))])
8888
N = (N - t) // (36 - t)
8989
j += 1
@@ -112,7 +112,7 @@ def generate_integers(baselen, deltas):
112112
s = generate_generalized_integer(delta, bias)
113113
result.extend(s)
114114
bias = adapt(delta, points==0, baselen+points+1)
115-
return bytes(result)
115+
return result.take_bytes()
116116

117117
def punycode_encode(text):
118118
base, extended = segregate(text)

Lib/importlib/_bootstrap_external.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,8 @@ def _write_atomic(path, data, mode=0o666):
208208
try:
209209
# We first write data to a temporary file, and then use os.replace() to
210210
# perform an atomic rename.
211-
with _io.FileIO(fd, 'wb') as file:
212-
bytes_written = file.write(data)
213-
if bytes_written != len(data):
214-
# Raise an OSError so the 'except' below cleans up the partially
215-
# written file.
216-
raise OSError("os.write() didn't write the full pyc file")
211+
with _io.open(fd, 'wb') as file:
212+
file.write(data)
217213
_os.replace(path_tmp, path)
218214
except OSError:
219215
try:

Lib/re/_compiler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ def _optimize_charset(charset, iscased=None, fixup=None, fixes=None):
375375
# less significant byte is a bit index in the chunk (just like the
376376
# CHARSET matching).
377377

378-
charmap = bytes(charmap) # should be hashable
378+
charmap = charmap.take_bytes() # should be hashable
379379
comps = {}
380380
mapping = bytearray(256)
381381
block = 0

Lib/test/test_importlib/test_util.py

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -783,31 +783,70 @@ def test_complete_multi_phase_init_module(self):
783783
self.run_with_own_gil(script)
784784

785785

786-
class MiscTests(unittest.TestCase):
787-
def test_atomic_write_should_notice_incomplete_writes(self):
786+
class PatchAtomicWrites:
787+
def __init__(self, truncate_at_length, never_complete=False):
788+
self.truncate_at_length = truncate_at_length
789+
self.never_complete = never_complete
790+
self.seen_write = False
791+
self._children = []
792+
793+
def __enter__(self):
788794
import _pyio
789795

790796
oldwrite = os.write
791-
seen_write = False
792-
793-
truncate_at_length = 100
794797

795798
# Emulate an os.write that only writes partial data.
796799
def write(fd, data):
797-
nonlocal seen_write
798-
seen_write = True
799-
return oldwrite(fd, data[:truncate_at_length])
800+
if self.seen_write and self.never_complete:
801+
return None
802+
self.seen_write = True
803+
return oldwrite(fd, data[:self.truncate_at_length])
800804

801805
# Need to patch _io to be _pyio, so that io.FileIO is affected by the
802806
# os.write patch.
803-
with (support.swap_attr(_bootstrap_external, '_io', _pyio),
804-
support.swap_attr(os, 'write', write)):
805-
with self.assertRaises(OSError):
806-
# Make sure we write something longer than the point where we
807-
# truncate.
808-
content = b'x' * (truncate_at_length * 2)
809-
_bootstrap_external._write_atomic(os_helper.TESTFN, content)
810-
assert seen_write
807+
self.children = [
808+
support.swap_attr(_bootstrap_external, '_io', _pyio),
809+
support.swap_attr(os, 'write', write)
810+
]
811+
for child in self.children:
812+
child.__enter__()
813+
return self
814+
815+
def __exit__(self, exc_type, exc_val, exc_tb):
816+
for child in self.children:
817+
child.__exit__(exc_type, exc_val, exc_tb)
818+
819+
820+
class MiscTests(unittest.TestCase):
821+
822+
def test_atomic_write_retries_incomplete_writes(self):
823+
truncate_at_length = 100
824+
length = truncate_at_length * 2
825+
826+
with PatchAtomicWrites(truncate_at_length=truncate_at_length) as cm:
827+
# Make sure we write something longer than the point where we
828+
# truncate.
829+
content = b'x' * length
830+
_bootstrap_external._write_atomic(os_helper.TESTFN, content)
831+
self.assertTrue(cm.seen_write)
832+
833+
self.assertEqual(os.stat(support.os_helper.TESTFN).st_size, length)
834+
os.unlink(support.os_helper.TESTFN)
835+
836+
def test_atomic_write_errors_if_unable_to_complete(self):
837+
truncate_at_length = 100
838+
839+
with (
840+
PatchAtomicWrites(
841+
truncate_at_length=truncate_at_length, never_complete=True,
842+
) as cm,
843+
self.assertRaises(OSError)
844+
):
845+
# Make sure we write something longer than the point where we
846+
# truncate.
847+
content = b'x' * (truncate_at_length * 2)
848+
_bootstrap_external._write_atomic(os_helper.TESTFN, content)
849+
self.assertTrue(cm.seen_write)
811850

812851
with self.assertRaises(OSError):
813852
os.stat(support.os_helper.TESTFN) # Check that the file did not get written.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
When importing a module, use Python's regular file object to ensure that
2+
writes to ``.pyc`` files are complete or an appropriate error is raised.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Remove data copy from :mod:`codecs` ``punycode`` encoding by using
2+
:meth:`bytearray.take_bytes`.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Remove data copy from :mod:`re` compilation of regexes with large charsets
2+
by using :meth:`bytearray.take_bytes`.

Tools/c-analyzer/c_parser/preprocessor/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from . import (
1717
pure as _pure,
1818
gcc as _gcc,
19+
clang as _clang,
1920
)
2021

2122

@@ -234,7 +235,7 @@ def handling_errors(ignore_exc=None, *, log_err=None):
234235
'bcpp': None,
235236
# aliases/extras:
236237
'gcc': _gcc.preprocess,
237-
'clang': None,
238+
'clang': _clang.preprocess,
238239
}
239240

240241

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import os.path
2+
import re, sys
3+
4+
from . import common as _common
5+
from . import gcc as _gcc
6+
7+
_normpath = _gcc._normpath
8+
9+
TOOL = 'clang'
10+
11+
META_FILES = {
12+
'<built-in>',
13+
'<command line>',
14+
}
15+
16+
17+
def preprocess(filename,
18+
incldirs=None,
19+
includes=None,
20+
macros=None,
21+
samefiles=None,
22+
cwd=None,
23+
):
24+
if not cwd or not os.path.isabs(cwd):
25+
cwd = os.path.abspath(cwd or '.')
26+
filename = _normpath(filename, cwd)
27+
28+
postargs = _gcc.POST_ARGS
29+
basename = os.path.basename(filename)
30+
dirname = os.path.basename(os.path.dirname(filename))
31+
if (basename not in _gcc.FILES_WITHOUT_INTERNAL_CAPI
32+
and dirname not in _gcc.DIRS_WITHOUT_INTERNAL_CAPI):
33+
postargs += ('-DPy_BUILD_CORE=1',)
34+
35+
text = _common.preprocess(
36+
TOOL,
37+
filename,
38+
incldirs=incldirs,
39+
includes=includes,
40+
macros=macros,
41+
#preargs=PRE_ARGS,
42+
postargs=postargs,
43+
executable=['clang'],
44+
compiler='unix',
45+
cwd=cwd,
46+
)
47+
return _iter_lines(text, filename, samefiles, cwd)
48+
49+
50+
EXIT_MARKERS = {'# 2 "<built-in>" 2', '# 3 "<built-in>" 2', '# 4 "<built-in>" 2'}
51+
52+
53+
def _iter_lines(text, reqfile, samefiles, cwd, raw=False):
54+
lines = iter(text.splitlines())
55+
56+
# The first line is special.
57+
# The subsequent lines are consistent.
58+
firstlines = [
59+
f'# 1 "{reqfile}"',
60+
'# 1 "<built-in>" 1',
61+
'# 1 "<built-in>" 3',
62+
'# 370 "<built-in>" 3',
63+
'# 1 "<command line>" 1',
64+
'# 1 "<built-in>" 2',
65+
]
66+
for expected in firstlines:
67+
line = next(lines)
68+
if line != expected:
69+
raise NotImplementedError((line, expected))
70+
71+
# Do all the CLI-provided includes.
72+
filter_reqfile = (lambda f: _gcc._filter_reqfile(f, reqfile, samefiles))
73+
make_info = (lambda lno: _common.FileInfo(reqfile, lno))
74+
last = None
75+
for line in lines:
76+
assert last != reqfile, (last,)
77+
# NOTE:condition is clang specific
78+
if not line:
79+
continue
80+
lno, included, flags = _gcc._parse_marker_line(line, reqfile)
81+
if not included:
82+
raise NotImplementedError((line,))
83+
if included == reqfile:
84+
# This will be the last one.
85+
assert 2 in flags, (line, flags)
86+
else:
87+
# NOTE:first condition is specific to clang
88+
if _normpath(included, cwd) == reqfile:
89+
assert 1 in flags or 2 in flags, (line, flags, included, reqfile)
90+
else:
91+
assert 1 in flags, (line, flags, included, reqfile)
92+
yield from _gcc._iter_top_include_lines(
93+
lines,
94+
_normpath(included, cwd),
95+
cwd,
96+
filter_reqfile,
97+
make_info,
98+
raw,
99+
EXIT_MARKERS
100+
)
101+
last = included
102+
# The last one is always the requested file.
103+
# NOTE:_normpath is clang specific
104+
assert _normpath(included, cwd) == reqfile, (line,)

Tools/c-analyzer/c_parser/preprocessor/gcc.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
'-E',
6666
)
6767

68+
EXIT_MARKERS = {'# 0 "<command-line>" 2', '# 1 "<command-line>" 2'}
69+
6870

6971
def preprocess(filename,
7072
incldirs=None,
@@ -138,6 +140,7 @@ def _iter_lines(text, reqfile, samefiles, cwd, raw=False):
138140
filter_reqfile,
139141
make_info,
140142
raw,
143+
EXIT_MARKERS
141144
)
142145
last = included
143146
# The last one is always the requested file.
@@ -146,20 +149,28 @@ def _iter_lines(text, reqfile, samefiles, cwd, raw=False):
146149

147150
def _iter_top_include_lines(lines, topfile, cwd,
148151
filter_reqfile, make_info,
149-
raw):
152+
raw, exit_markers):
150153
partial = 0 # depth
151154
files = [topfile]
152155
# We start at 1 in case there are source lines (including blank ones)
153156
# before the first marker line. Also, we already verified in
154157
# _parse_marker_line() that the preprocessor reported lno as 1.
155158
lno = 1
156159
for line in lines:
157-
if line == '# 0 "<command-line>" 2' or line == '# 1 "<command-line>" 2':
160+
if line in exit_markers:
158161
# We're done with this top-level include.
159162
return
160163

161164
_lno, included, flags = _parse_marker_line(line)
162165
if included:
166+
# HACK:
167+
# Mixes curses.h and ncurses.h marker lines
168+
# gcc silently passes this, while clang fails
169+
# See: /Include/py_curses.h #if-elif directives
170+
# And compare with preprocessor output
171+
if os.path.basename(included) == 'curses.h':
172+
included = os.path.join(os.path.dirname(included), 'ncurses.h')
173+
163174
lno = _lno
164175
included = _normpath(included, cwd)
165176
# We hit a marker line.

0 commit comments

Comments
 (0)