Skip to content

Commit

Permalink
bpo-25711: Rewrite zipimport in pure Python. (pythonGH-6809)
Browse files Browse the repository at this point in the history
  • Loading branch information
serhiy-storchaka authored Sep 18, 2018
1 parent 4ba3b50 commit 79d1c2e
Show file tree
Hide file tree
Showing 23 changed files with 2,998 additions and 3,408 deletions.
1 change: 1 addition & 0 deletions Lib/ctypes/test/test_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class struct_frozen(Structure):
bootstrap_expected = [
b'_frozen_importlib',
b'_frozen_importlib_external',
b'zipimport',
]
for entry in ft:
# This is dangerous. We *can* iterate over a pointer, but
Expand Down
4 changes: 2 additions & 2 deletions Lib/importlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
sys.modules['importlib._bootstrap_external'] = _bootstrap_external

# To simplify imports in test code
_w_long = _bootstrap_external._w_long
_r_long = _bootstrap_external._r_long
_pack_uint32 = _bootstrap_external._pack_uint32
_unpack_uint32 = _bootstrap_external._unpack_uint32

# Fully bootstrapped at this point, import whatever you like, circular
# dependencies and startup overhead minimisation permitting :)
Expand Down
26 changes: 16 additions & 10 deletions Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,20 @@ def _relax_case():
return _relax_case


def _w_long(x):
def _pack_uint32(x):
"""Convert a 32-bit integer to little-endian."""
return (int(x) & 0xFFFFFFFF).to_bytes(4, 'little')


def _r_long(int_bytes):
def _unpack_uint32(data):
"""Convert 4 bytes in little-endian to an integer."""
return int.from_bytes(int_bytes, 'little')
assert len(data) == 4
return int.from_bytes(data, 'little')

def _unpack_uint16(data):
"""Convert 2 bytes in little-endian to an integer."""
assert len(data) == 2
return int.from_bytes(data, 'little')


def _path_join(*path_parts):
Expand Down Expand Up @@ -503,7 +509,7 @@ def _classify_pyc(data, name, exc_details):
message = f'reached EOF while reading pyc header of {name!r}'
_bootstrap._verbose_message('{}', message)
raise EOFError(message)
flags = _r_long(data[4:8])
flags = _unpack_uint32(data[4:8])
# Only the first two flags are defined.
if flags & ~0b11:
message = f'invalid flags {flags!r} in {name!r}'
Expand All @@ -530,12 +536,12 @@ def _validate_timestamp_pyc(data, source_mtime, source_size, name,
An ImportError is raised if the bytecode is stale.
"""
if _r_long(data[8:12]) != (source_mtime & 0xFFFFFFFF):
if _unpack_uint32(data[8:12]) != (source_mtime & 0xFFFFFFFF):
message = f'bytecode is stale for {name!r}'
_bootstrap._verbose_message('{}', message)
raise ImportError(message, **exc_details)
if (source_size is not None and
_r_long(data[12:16]) != (source_size & 0xFFFFFFFF)):
_unpack_uint32(data[12:16]) != (source_size & 0xFFFFFFFF)):
raise ImportError(f'bytecode is stale for {name!r}', **exc_details)


Expand Down Expand Up @@ -579,9 +585,9 @@ def _compile_bytecode(data, name=None, bytecode_path=None, source_path=None):
def _code_to_timestamp_pyc(code, mtime=0, source_size=0):
"Produce the data for a timestamp-based pyc."
data = bytearray(MAGIC_NUMBER)
data.extend(_w_long(0))
data.extend(_w_long(mtime))
data.extend(_w_long(source_size))
data.extend(_pack_uint32(0))
data.extend(_pack_uint32(mtime))
data.extend(_pack_uint32(source_size))
data.extend(marshal.dumps(code))
return data

Expand All @@ -590,7 +596,7 @@ def _code_to_hash_pyc(code, source_hash, checked=True):
"Produce the data for a hash-based pyc."
data = bytearray(MAGIC_NUMBER)
flags = 0b1 | checked << 1
data.extend(_w_long(flags))
data.extend(_pack_uint32(flags))
assert len(source_hash) == 8
data.extend(source_hash)
data.extend(marshal.dumps(code))
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_bdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ def main():
('line', 2, 'tfunc_import'), ('step', ),
('line', 3, 'tfunc_import'), ('quit', ),
]
skip = ('importlib*', TEST_MODULE)
skip = ('importlib*', 'zipimport', TEST_MODULE)
with TracerRun(self, skip=skip) as tracer:
tracer.runcall(tfunc_import)

Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_importlib/source/test_file_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ def test_old_timestamp(self):
bytecode_file.write(zeros)
self.import_(mapping['_temp'], '_temp')
source_mtime = os.path.getmtime(mapping['_temp'])
source_timestamp = self.importlib._w_long(source_mtime)
source_timestamp = self.importlib._pack_uint32(source_mtime)
with open(bytecode_path, 'rb') as bytecode_file:
bytecode_file.seek(8)
self.assertEqual(bytecode_file.read(4), source_timestamp)
Expand Down
12 changes: 6 additions & 6 deletions Lib/test/test_importlib/test_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -712,9 +712,9 @@ def __init__(self, path, magic=None):
if magic is None:
magic = self.util.MAGIC_NUMBER
data = bytearray(magic)
data.extend(self.init._w_long(0))
data.extend(self.init._w_long(self.source_mtime))
data.extend(self.init._w_long(self.source_size))
data.extend(self.init._pack_uint32(0))
data.extend(self.init._pack_uint32(self.source_mtime))
data.extend(self.init._pack_uint32(self.source_size))
code_object = compile(self.source, self.path, 'exec',
dont_inherit=True)
data.extend(marshal.dumps(code_object))
Expand Down Expand Up @@ -876,9 +876,9 @@ def verify_code(self, code_object, *, bytecode_written=False):
if bytecode_written:
self.assertIn(self.cached, self.loader.written)
data = bytearray(self.util.MAGIC_NUMBER)
data.extend(self.init._w_long(0))
data.extend(self.init._w_long(self.loader.source_mtime))
data.extend(self.init._w_long(self.loader.source_size))
data.extend(self.init._pack_uint32(0))
data.extend(self.init._pack_uint32(self.loader.source_mtime))
data.extend(self.init._pack_uint32(self.loader.source_size))
data.extend(marshal.dumps(code_object))
self.assertEqual(self.loader.written[self.cached], bytes(data))

Expand Down
29 changes: 17 additions & 12 deletions Lib/test/test_zipimport.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,12 @@ def replace(self, old, new):
z.writestr(name, data)
z.close()
zi = zipimport.zipimporter(TEMP_ZIP)
self.assertEqual(data, zi.get_data(FunnyStr(name)))
try:
data2 = zi.get_data(FunnyStr(name))
except AttributeError:
pass
else:
self.assertEqual(data2, data)
finally:
z.close()
os.remove(TEMP_ZIP)
Expand Down Expand Up @@ -677,24 +682,24 @@ def testBytesPath(self):

zipimport.zipimporter(filename)
zipimport.zipimporter(os.fsencode(filename))
with self.assertWarns(DeprecationWarning):
with self.assertRaises(TypeError):
zipimport.zipimporter(bytearray(os.fsencode(filename)))
with self.assertWarns(DeprecationWarning):
with self.assertRaises(TypeError):
zipimport.zipimporter(memoryview(os.fsencode(filename)))

@support.cpython_only
def testUninitializedZipimporter(self):
# The interpreter shouldn't crash in case of calling methods of an
# uninitialized zipimport.zipimporter object.
zi = zipimport.zipimporter.__new__(zipimport.zipimporter)
self.assertRaises(ValueError, zi.find_module, 'foo')
self.assertRaises(ValueError, zi.find_loader, 'foo')
self.assertRaises(ValueError, zi.load_module, 'foo')
self.assertRaises(ValueError, zi.get_filename, 'foo')
self.assertRaises(ValueError, zi.is_package, 'foo')
self.assertRaises(ValueError, zi.get_data, 'foo')
self.assertRaises(ValueError, zi.get_code, 'foo')
self.assertRaises(ValueError, zi.get_source, 'foo')
self.assertRaises((ValueError, AttributeError), zi.find_module, 'foo')
self.assertRaises((ValueError, AttributeError), zi.find_loader, 'foo')
self.assertRaises((ValueError, AttributeError), zi.load_module, 'foo')
self.assertRaises((ValueError, AttributeError), zi.get_filename, 'foo')
self.assertRaises((ValueError, AttributeError), zi.is_package, 'foo')
self.assertRaises((ValueError, AttributeError), zi.get_data, 'foo')
self.assertRaises((ValueError, AttributeError), zi.get_code, 'foo')
self.assertRaises((ValueError, AttributeError), zi.get_source, 'foo')


@support.requires_zlib
Expand All @@ -712,7 +717,7 @@ def bad_decompress(*args):
zip_file.writestr('bar.py', b'print("hello world")', ZIP_DEFLATED)
zi = zipimport.zipimporter(TEMP_ZIP)
with support.swap_attr(zlib, 'decompress', bad_decompress):
self.assertRaises(TypeError, zi.get_source, 'bar')
self.assertRaises((TypeError, AttributeError), zi.get_source, 'bar')


class BadFileZipImportTestCase(unittest.TestCase):
Expand Down
Loading

0 comments on commit 79d1c2e

Please sign in to comment.