mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	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:
		
							parent
							
								
									322daea7c3
								
							
						
					
					
						commit
						28d108893c
					
				
					 4 changed files with 84 additions and 8 deletions
				
			
		|  | @ -11,10 +11,11 @@ | ||||||
| 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': | ||||||
|                 cfile = fullname + (__debug__ and 'c' or 'o') |                 if not force: | ||||||
|                 ftime = os.stat(fullname).st_mtime |                     try: | ||||||
|                 try: ctime = os.stat(cfile).st_mtime |                         mtime = os.stat(fullname).st_mtime | ||||||
|                 except os.error: ctime = 0 |                         expect = struct.pack('<4sl', imp.get_magic(), mtime) | ||||||
|                 if (ctime > ftime) and not force: continue |                         cfile = fullname + (__debug__ and 'c' or 'o') | ||||||
|  |                         with open(cfile, 'rb') as chandle: | ||||||
|  |                             actual = chandle.read(8) | ||||||
|  |                         if expect == actual: | ||||||
|  |                             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 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										63
									
								
								Lib/test/test_compileall.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								Lib/test/test_compileall.py
									
										
									
									
									
										Normal 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() | ||||||
|  | @ -183,7 +183,7 @@ Virgil Dupras | ||||||
| Andy Dustman | Andy Dustman | ||||||
| Gary Duzan | Gary Duzan | ||||||
| Eugene Dvurechenski | Eugene Dvurechenski | ||||||
| Josip Dzolonga  | Josip Dzolonga | ||||||
| Maxim Dzumanenko | Maxim Dzumanenko | ||||||
| Walter Dörwald | Walter Dörwald | ||||||
| Hans Eckardt | Hans Eckardt | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Brett Cannon
						Brett Cannon