mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	gh-110543: Fix CodeType.replace in presence of comprehensions (#110586)
This commit is contained in:
		
							parent
							
								
									804575b5c0
								
							
						
					
					
						commit
						0b718e6407
					
				
					 3 changed files with 75 additions and 2 deletions
				
			
		|  | @ -1,5 +1,6 @@ | ||||||
| import doctest | import doctest | ||||||
| import textwrap | import textwrap | ||||||
|  | import types | ||||||
| import unittest | import unittest | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -92,7 +93,8 @@ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ListComprehensionTest(unittest.TestCase): | class ListComprehensionTest(unittest.TestCase): | ||||||
|     def _check_in_scopes(self, code, outputs=None, ns=None, scopes=None, raises=()): |     def _check_in_scopes(self, code, outputs=None, ns=None, scopes=None, raises=(), | ||||||
|  |                          exec_func=exec): | ||||||
|         code = textwrap.dedent(code) |         code = textwrap.dedent(code) | ||||||
|         scopes = scopes or ["module", "class", "function"] |         scopes = scopes or ["module", "class", "function"] | ||||||
|         for scope in scopes: |         for scope in scopes: | ||||||
|  | @ -119,7 +121,7 @@ def get_output(moddict, name): | ||||||
|                         return moddict[name] |                         return moddict[name] | ||||||
|                 newns = ns.copy() if ns else {} |                 newns = ns.copy() if ns else {} | ||||||
|                 try: |                 try: | ||||||
|                     exec(newcode, newns) |                     exec_func(newcode, newns) | ||||||
|                 except raises as e: |                 except raises as e: | ||||||
|                     # We care about e.g. NameError vs UnboundLocalError |                     # We care about e.g. NameError vs UnboundLocalError | ||||||
|                     self.assertIs(type(e), raises) |                     self.assertIs(type(e), raises) | ||||||
|  | @ -613,6 +615,45 @@ def test_frame_locals(self): | ||||||
|         import sys |         import sys | ||||||
|         self._check_in_scopes(code, {"val": 0}, ns={"sys": sys}) |         self._check_in_scopes(code, {"val": 0}, ns={"sys": sys}) | ||||||
| 
 | 
 | ||||||
|  |     def _recursive_replace(self, maybe_code): | ||||||
|  |         if not isinstance(maybe_code, types.CodeType): | ||||||
|  |             return maybe_code | ||||||
|  |         return maybe_code.replace(co_consts=tuple( | ||||||
|  |             self._recursive_replace(c) for c in maybe_code.co_consts | ||||||
|  |         )) | ||||||
|  | 
 | ||||||
|  |     def _replacing_exec(self, code_string, ns): | ||||||
|  |         co = compile(code_string, "<string>", "exec") | ||||||
|  |         co = self._recursive_replace(co) | ||||||
|  |         exec(co, ns) | ||||||
|  | 
 | ||||||
|  |     def test_code_replace(self): | ||||||
|  |         code = """ | ||||||
|  |             x = 3 | ||||||
|  |             [x for x in (1, 2)] | ||||||
|  |             dir() | ||||||
|  |             y = [x] | ||||||
|  |         """ | ||||||
|  |         self._check_in_scopes(code, {"y": [3], "x": 3}) | ||||||
|  |         self._check_in_scopes(code, {"y": [3], "x": 3}, exec_func=self._replacing_exec) | ||||||
|  | 
 | ||||||
|  |     def test_code_replace_extended_arg(self): | ||||||
|  |         num_names = 300 | ||||||
|  |         assignments = "; ".join(f"x{i} = {i}" for i in range(num_names)) | ||||||
|  |         name_list = ", ".join(f"x{i}" for i in range(num_names)) | ||||||
|  |         expected = { | ||||||
|  |             "y": list(range(num_names)), | ||||||
|  |             **{f"x{i}": i for i in range(num_names)} | ||||||
|  |         } | ||||||
|  |         code = f""" | ||||||
|  |             {assignments} | ||||||
|  |             [({name_list}) for {name_list} in (range(300),)] | ||||||
|  |             dir() | ||||||
|  |             y = [{name_list}] | ||||||
|  |         """ | ||||||
|  |         self._check_in_scopes(code, expected) | ||||||
|  |         self._check_in_scopes(code, expected, exec_func=self._replacing_exec) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| __test__ = {'doctests' : doctests} | __test__ = {'doctests' : doctests} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,3 @@ | ||||||
|  | Fix regression in Python 3.12 where :meth:`types.CodeType.replace` would | ||||||
|  | produce a broken code object if called on a module or class code object that | ||||||
|  | contains a comprehension. Patch by Jelle Zijlstra. | ||||||
|  | @ -643,6 +643,35 @@ PyUnstable_Code_NewWithPosOnlyArgs( | ||||||
|         _Py_set_localsplus_info(offset, name, CO_FAST_FREE, |         _Py_set_localsplus_info(offset, name, CO_FAST_FREE, | ||||||
|                                localsplusnames, localspluskinds); |                                localsplusnames, localspluskinds); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     // gh-110543: Make sure the CO_FAST_HIDDEN flag is set correctly.
 | ||||||
|  |     if (!(flags & CO_OPTIMIZED)) { | ||||||
|  |         Py_ssize_t code_len = PyBytes_GET_SIZE(code); | ||||||
|  |         _Py_CODEUNIT *code_data = (_Py_CODEUNIT *)PyBytes_AS_STRING(code); | ||||||
|  |         Py_ssize_t num_code_units = code_len / sizeof(_Py_CODEUNIT); | ||||||
|  |         int extended_arg = 0; | ||||||
|  |         for (int i = 0; i < num_code_units; i += 1 + _PyOpcode_Caches[code_data[i].op.code]) { | ||||||
|  |             _Py_CODEUNIT *instr = &code_data[i]; | ||||||
|  |             uint8_t opcode = instr->op.code; | ||||||
|  |             if (opcode == EXTENDED_ARG) { | ||||||
|  |                 extended_arg = extended_arg << 8 | instr->op.arg; | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             if (opcode == LOAD_FAST_AND_CLEAR) { | ||||||
|  |                 int oparg = extended_arg << 8 | instr->op.arg; | ||||||
|  |                 if (oparg >= nlocalsplus) { | ||||||
|  |                     PyErr_Format(PyExc_ValueError, | ||||||
|  |                                 "code: LOAD_FAST_AND_CLEAR oparg %d out of range", | ||||||
|  |                                 oparg); | ||||||
|  |                     goto error; | ||||||
|  |                 } | ||||||
|  |                 _PyLocals_Kind kind = _PyLocals_GetKind(localspluskinds, oparg); | ||||||
|  |                 _PyLocals_SetKind(localspluskinds, oparg, kind | CO_FAST_HIDDEN); | ||||||
|  |             } | ||||||
|  |             extended_arg = 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // If any cells were args then nlocalsplus will have shrunk.
 |     // If any cells were args then nlocalsplus will have shrunk.
 | ||||||
|     if (nlocalsplus != PyTuple_GET_SIZE(localsplusnames)) { |     if (nlocalsplus != PyTuple_GET_SIZE(localsplusnames)) { | ||||||
|         if (_PyTuple_Resize(&localsplusnames, nlocalsplus) < 0 |         if (_PyTuple_Resize(&localsplusnames, nlocalsplus) < 0 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jelle Zijlstra
						Jelle Zijlstra