Skip to content

Commit a04dcff

Browse files
author
antoine.pitrou
committed
Merged revisions 68360-68361 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk ........ r68360 | antoine.pitrou | 2009-01-06 19:10:47 +0100 (mar., 06 janv. 2009) | 7 lines Issue #1180193: When importing a module from a .pyc (or .pyo) file with an existing .py counterpart, override the co_filename attributes of all code objects if the original filename is obsolete (which can happen if the file has been renamed, moved, or if it is accessed through different paths). Patch by Ziga Seilnacht and Jean-Paul Calderone. ........ r68361 | antoine.pitrou | 2009-01-06 19:34:08 +0100 (mar., 06 janv. 2009) | 3 lines Use shutil.rmtree rather than os.rmdir. ........ git-svn-id: http://svn.python.org/projects/python/branches/py3k@68363 6015fed2-1504-0410-9fe1-9d1591cc4771
1 parent 6297820 commit a04dcff

File tree

3 files changed

+145
-1
lines changed

3 files changed

+145
-1
lines changed

Lib/test/test_import.py

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import py_compile
77
import warnings
88
import imp
9+
import marshal
910
from test.support import unlink, TESTFN, unload, run_unittest
1011

1112

@@ -230,6 +231,98 @@ def test_importbyfilename(self):
230231
else:
231232
self.fail("import by path didn't raise an exception")
232233

234+
class TestPycRewriting(unittest.TestCase):
235+
# Test that the `co_filename` attribute on code objects always points
236+
# to the right file, even when various things happen (e.g. both the .py
237+
# and the .pyc file are renamed).
238+
239+
module_name = "unlikely_module_name"
240+
module_source = """
241+
import sys
242+
code_filename = sys._getframe().f_code.co_filename
243+
module_filename = __file__
244+
constant = 1
245+
def func():
246+
pass
247+
func_filename = func.__code__.co_filename
248+
"""
249+
dir_name = os.path.abspath(TESTFN)
250+
file_name = os.path.join(dir_name, module_name) + os.extsep + "py"
251+
compiled_name = file_name + ("c" if __debug__ else "o")
252+
253+
def setUp(self):
254+
self.sys_path = sys.path[:]
255+
self.orig_module = sys.modules.pop(self.module_name, None)
256+
os.mkdir(self.dir_name)
257+
with open(self.file_name, "w") as f:
258+
f.write(self.module_source)
259+
sys.path.insert(0, self.dir_name)
260+
261+
def tearDown(self):
262+
sys.path[:] = self.sys_path
263+
if self.orig_module is not None:
264+
sys.modules[self.module_name] = self.orig_module
265+
else:
266+
del sys.modules[self.module_name]
267+
for file_name in self.file_name, self.compiled_name:
268+
if os.path.exists(file_name):
269+
os.remove(file_name)
270+
if os.path.exists(self.dir_name):
271+
shutil.rmtree(self.dir_name)
272+
273+
def import_module(self):
274+
ns = globals()
275+
__import__(self.module_name, ns, ns)
276+
return sys.modules[self.module_name]
277+
278+
def test_basics(self):
279+
mod = self.import_module()
280+
self.assertEqual(mod.module_filename, self.file_name)
281+
self.assertEqual(mod.code_filename, self.file_name)
282+
self.assertEqual(mod.func_filename, self.file_name)
283+
del sys.modules[self.module_name]
284+
mod = self.import_module()
285+
self.assertEqual(mod.module_filename, self.file_name)
286+
self.assertEqual(mod.code_filename, self.file_name)
287+
self.assertEqual(mod.func_filename, self.file_name)
288+
289+
def test_incorrect_code_name(self):
290+
py_compile.compile(self.file_name, dfile="another_module.py")
291+
mod = self.import_module()
292+
self.assertEqual(mod.module_filename, self.file_name)
293+
self.assertEqual(mod.code_filename, self.file_name)
294+
self.assertEqual(mod.func_filename, self.file_name)
295+
296+
def test_module_without_source(self):
297+
target = "another_module.py"
298+
py_compile.compile(self.file_name, dfile=target)
299+
os.remove(self.file_name)
300+
mod = self.import_module()
301+
self.assertEqual(mod.module_filename, self.compiled_name)
302+
self.assertEqual(mod.code_filename, target)
303+
self.assertEqual(mod.func_filename, target)
304+
305+
def test_foreign_code(self):
306+
py_compile.compile(self.file_name)
307+
with open(self.compiled_name, "rb") as f:
308+
header = f.read(8)
309+
code = marshal.load(f)
310+
constants = list(code.co_consts)
311+
foreign_code = test_main.__code__
312+
pos = constants.index(1)
313+
constants[pos] = foreign_code
314+
code = type(code)(code.co_argcount, code.co_kwonlyargcount,
315+
code.co_nlocals, code.co_stacksize,
316+
code.co_flags, code.co_code, tuple(constants),
317+
code.co_names, code.co_varnames, code.co_filename,
318+
code.co_name, code.co_firstlineno, code.co_lnotab,
319+
code.co_freevars, code.co_cellvars)
320+
with open(self.compiled_name, "wb") as f:
321+
f.write(header)
322+
marshal.dump(code, f)
323+
mod = self.import_module()
324+
self.assertEqual(mod.constant.co_filename, foreign_code.co_filename)
325+
233326
class PathsTests(unittest.TestCase):
234327
SAMPLES = ('test', 'test\u00e4\u00f6\u00fc\u00df', 'test\u00e9\u00e8',
235328
'test\u00b0\u00b3\u00b2')
@@ -288,7 +381,7 @@ def check_relative():
288381
self.assertRaises(ValueError, check_relative)
289382

290383
def test_main(verbose=None):
291-
run_unittest(ImportTest, PathsTests, RelativeImport)
384+
run_unittest(ImportTest, TestPycRewriting, PathsTests, RelativeImport)
292385

293386
if __name__ == '__main__':
294387
# test needs to be a package, so we can do relative import

Misc/NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ What's New in Python 3.1 alpha 0
1212
Core and Builtins
1313
-----------------
1414

15+
- Issue #1180193: When importing a module from a .pyc (or .pyo) file with
16+
an existing .py counterpart, override the co_filename attributes of all
17+
code objects if the original filename is obsolete (which can happen if the
18+
file has been renamed, moved, or if it is accessed through different paths).
19+
Patch by Ziga Seilnacht and Jean-Paul Calderone.
20+
1521
- Issue #4580: Fix slicing of memoryviews when the item size is greater than
1622
one byte. Also fixes the meaning of len() so that it returns the number of
1723
items, rather than the size in bytes.

Python/import.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,49 @@ write_compiled_module(PyCodeObject *co, char *cpathname, struct stat *srcstat)
959959
PySys_WriteStderr("# wrote %s\n", cpathname);
960960
}
961961

962+
static void
963+
update_code_filenames(PyCodeObject *co, PyObject *oldname, PyObject *newname)
964+
{
965+
PyObject *constants, *tmp;
966+
Py_ssize_t i, n;
967+
968+
if (PyUnicode_Compare(co->co_filename, oldname))
969+
return;
970+
971+
tmp = co->co_filename;
972+
co->co_filename = newname;
973+
Py_INCREF(co->co_filename);
974+
Py_DECREF(tmp);
975+
976+
constants = co->co_consts;
977+
n = PyTuple_GET_SIZE(constants);
978+
for (i = 0; i < n; i++) {
979+
tmp = PyTuple_GET_ITEM(constants, i);
980+
if (PyCode_Check(tmp))
981+
update_code_filenames((PyCodeObject *)tmp,
982+
oldname, newname);
983+
}
984+
}
985+
986+
static int
987+
update_compiled_module(PyCodeObject *co, char *pathname)
988+
{
989+
PyObject *oldname, *newname;
990+
991+
if (!PyUnicode_CompareWithASCIIString(co->co_filename, pathname))
992+
return 0;
993+
994+
newname = PyUnicode_FromString(pathname);
995+
if (newname == NULL)
996+
return -1;
997+
998+
oldname = co->co_filename;
999+
Py_INCREF(oldname);
1000+
update_code_filenames(co, oldname, newname);
1001+
Py_DECREF(oldname);
1002+
Py_DECREF(newname);
1003+
return 1;
1004+
}
9621005

9631006
/* Load a source module from a given file and return its module
9641007
object WITH INCREMENTED REFERENCE COUNT. If there's a matching
@@ -999,6 +1042,8 @@ load_source_module(char *name, char *pathname, FILE *fp)
9991042
fclose(fpc);
10001043
if (co == NULL)
10011044
return NULL;
1045+
if (update_compiled_module(co, pathname) < 0)
1046+
return NULL;
10021047
if (Py_VerboseFlag)
10031048
PySys_WriteStderr("import %s # precompiled from %s\n",
10041049
name, cpathname);

0 commit comments

Comments
 (0)