compileall used the ctime of bytecode and source to determine if the bytecode

should be recreated. This created a timing hole. Fixed by just doing what
import does; check the mtime and magic number.
This commit is contained in:
Brett Cannon 2009-02-10 02:07:38 +00:00
parent 322daea7c3
commit 28d108893c
4 changed files with 84 additions and 8 deletions

View file

@ -11,10 +11,11 @@ packages -- for now, you'll have to deal with packages separately.)
See module py_compile for details of the actual byte-compilation. See module py_compile for details of the actual byte-compilation.
""" """
import os import os
import sys import sys
import py_compile import py_compile
import struct
import imp
__all__ = ["compile_dir","compile_path"] __all__ = ["compile_dir","compile_path"]
@ -54,11 +55,17 @@ def compile_dir(dir, maxlevels=10, ddir=None,
if os.path.isfile(fullname): if os.path.isfile(fullname):
head, tail = name[:-3], name[-3:] head, tail = name[:-3], name[-3:]
if tail == '.py': if tail == '.py':
if not force:
try:
mtime = os.stat(fullname).st_mtime
expect = struct.pack('<4sl', imp.get_magic(), mtime)
cfile = fullname + (__debug__ and 'c' or 'o') cfile = fullname + (__debug__ and 'c' or 'o')
ftime = os.stat(fullname).st_mtime with open(cfile, 'rb') as chandle:
try: ctime = os.stat(cfile).st_mtime actual = chandle.read(8)
except os.error: ctime = 0 if expect == actual:
if (ctime > ftime) and not force: continue continue
except IOError:
pass
if not quiet: if not quiet:
print 'Compiling', fullname, '...' print 'Compiling', fullname, '...'
try: try:
@ -80,7 +87,8 @@ def compile_dir(dir, maxlevels=10, ddir=None,
name != os.curdir and name != os.pardir and \ name != os.curdir and name != os.pardir and \
os.path.isdir(fullname) and \ os.path.isdir(fullname) and \
not os.path.islink(fullname): not os.path.islink(fullname):
if not compile_dir(fullname, maxlevels - 1, dfile, force, rx, quiet): if not compile_dir(fullname, maxlevels - 1, dfile, force, rx,
quiet):
success = 0 success = 0
return success return success

View file

@ -0,0 +1,63 @@
import compileall
import imp
import os
import py_compile
import shutil
import struct
import sys
import tempfile
import time
from test import test_support
import unittest
class CompileallTests(unittest.TestCase):
def setUp(self):
self.directory = tempfile.mkdtemp()
self.source_path = os.path.join(self.directory, '_test.py')
self.bc_path = self.source_path + ('c' if __debug__ else 'o')
with open(self.source_path, 'w') as file:
file.write('x = 123\n')
def tearDown(self):
shutil.rmtree(self.directory)
def data(self):
with open(self.bc_path, 'rb') as file:
data = file.read(8)
mtime = int(os.stat(self.source_path).st_mtime)
compare = struct.pack('<4sl', imp.get_magic(), mtime)
return data, compare
def recreation_check(self, metadata):
"""Check that compileall recreates bytecode when the new metadata is
used."""
if not hasattr(os, 'stat'):
return
py_compile.compile(self.source_path)
self.assertEqual(*self.data())
with open(self.bc_path, 'rb') as file:
bc = file.read()[len(metadata):]
with open(self.bc_path, 'wb') as file:
file.write(metadata)
file.write(bc)
self.assertNotEqual(*self.data())
compileall.compile_dir(self.directory, force=False, quiet=True)
self.assertTrue(*self.data())
def test_mtime(self):
# Test a change in mtime leads to a new .pyc.
self.recreation_check(struct.pack('<4sl', imp.get_magic(), 1))
def test_magic_number(self):
# Test a change in mtime leads to a new .pyc.
self.recreation_check(b'\0\0\0\0')
def test_main():
test_support.run_unittest(CompileallTests)
if __name__ == "__main__":
test_main()

View file

@ -233,6 +233,7 @@ Peter Funk
Geoff Furnish Geoff Furnish
Ulisses Furquim Ulisses Furquim
Achim Gaedke Achim Gaedke
Martin von Gagern
Lele Gaifax Lele Gaifax
Santiago Gala Santiago Gala
Yitzchak Gale Yitzchak Gale

View file

@ -152,6 +152,10 @@ Core and Builtins
Library Library
------- -------
- Issue #5128: Make compileall properly inspect bytecode to determine if needs
to be recreated. This avoids a timing hole thanks to the old reliance on the
ctime of the files involved.
- Issue #5122: Synchronize tk load failure check to prevent a potential - Issue #5122: Synchronize tk load failure check to prevent a potential
deadlock. deadlock.