Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2342,6 +2342,29 @@ def g(self, __x):
return [__x]
""",
strings=['_ID_A__x']),
# A constant of an earlier method is equal to the co_names of a
# later one. The mangled name is not an ASCII identifier, so the
# compiler does not intern the equal string constants.
'mangled_names_non_ascii': Case(
source="""
class ID_\xe9:
def g(self):
return ("_ID_\xe9__x",), "_ID_\xe9__x"
def f(self):
return self.__x
""",
strings=['_ID_\xe9__x']),
# The same with the names of local variables.
'mangled_varnames_non_ascii': Case(
source="""
class ID_\xe9:
def g(self):
return ("_ID_\xe9__x",), "_ID_\xe9__x"
@staticmethod
def f(__x):
return __x
""",
strings=['_ID_\xe9__x']),
# The names of the keyword arguments are a constant equal to
# the co_names of the function.
'kwnames': Case(
Expand Down Expand Up @@ -2548,6 +2571,29 @@ def code_constants(code):
self.assertEqual(len(names), 1)
self.assertTrue(any(const is names for const in consts))

def test_ast_names_not_interned(self):
# The identifiers of an AST which was not created by the parser are
# not interned yet. A constant of an earlier function is equal to
# the co_names of a later one.
source = textwrap.dedent("""
def g(self):
return ("gh129724_ast_\xe9",), "gh129724_ast_\xe9"
def f(self):
return self.placeholder
""")
keep = []
dumps = []
for attempt in range(2):
tree = ast.parse(source)
attribute, = [node for node in ast.walk(tree)
if isinstance(node, ast.Attribute)]
# Not a literal: a new string object, which is not interned.
attribute.attr = ''.join(['gh129724_ast_', '\xe9'])
code = compile(tree, '<test>', 'exec')
keep.append(code)
dumps.append(marshal.dumps(code))
self.assertEqual(dumps[0], dumps[1])

@support.cpython_only
def test_slice_strings(self):
# The strings of a slice constant are interned like the other
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix the :mod:`marshal` output of compiled code, and so ``.pyc`` files,
differing for the same source depending on what ran before in the process.
This affected private names in classes with non-ASCII names, and
:func:`compile` of an AST with names not created by :func:`ast.parse`.
26 changes: 26 additions & 0 deletions Python/assemble.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
#include "pycore_instruction_sequence.h"
#include "pycore_opcode_utils.h" // IS_BACKWARDS_JUMP_OPCODE
#include "pycore_opcode_metadata.h" // is_pseudo_target, _PyOpcode_Caches
#include "pycore_pystate.h" // _PyInterpreterState_GET()
#include "pycore_symtable.h" // _Py_SourceLocation
#include "pycore_tuple.h" // _PyTuple_ITEMS()
#include "pycore_unicodeobject.h" // _PyUnicode_InternImmortal()

#include <stdbool.h>

Expand Down Expand Up @@ -503,6 +506,27 @@ dict_keys_inorder(PyObject *dict, Py_ssize_t offset)
extern void _Py_set_localsplus_info(int, PyObject *, unsigned char,
PyObject *, PyObject *);

/* Intern the names in *tuple*, as _PyCode_New() does, before the tuple is
* merged with the constant cache.
*
* The merge can replace the tuple by an equal tuple constant of another
* code object, whose items _PyCode_New() then interns in place. A string
* of that constant would become the interned string or be replaced by it,
* depending on whether an equal string was already interned in the process,
* and so would the output of marshal for the same source. Names which the
* parser did not create, such as mangled private names or the identifiers
* of an AST passed to compile(), may not be interned yet.
*/
static void
intern_names(PyObject *tuple)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(tuple); i++) {
assert(PyUnicode_CheckExact(PyTuple_GET_ITEM(tuple, i)));
_PyUnicode_InternImmortal(interp, &_PyTuple_ITEMS(tuple)[i]);
}
}

static int
compute_localsplus_info(_PyCompile_CodeUnitMetadata *umd, int nlocalsplus,
int flags, PyObject *names, PyObject *kinds)
Expand Down Expand Up @@ -610,6 +634,7 @@ makecode(_PyCompile_CodeUnitMetadata *umd, struct assembler *a, PyObject *const_
if (!names) {
goto error;
}
intern_names(names);
if (_PyCompile_ConstCacheMergeOne(const_cache, &names) < 0) {
goto error;
}
Expand Down Expand Up @@ -674,6 +699,7 @@ makecode(_PyCompile_CodeUnitMetadata *umd, struct assembler *a, PyObject *const_
goto error;
}

intern_names(localsplusnames);
if (_PyCompile_ConstCacheMergeOne(const_cache, &localsplusnames) < 0) {
goto error;
}
Expand Down
Loading