mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 21:51:50 +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 textwrap | ||||
| import types | ||||
| import unittest | ||||
| 
 | ||||
| 
 | ||||
|  | @ -92,7 +93,8 @@ | |||
| 
 | ||||
| 
 | ||||
| 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) | ||||
|         scopes = scopes or ["module", "class", "function"] | ||||
|         for scope in scopes: | ||||
|  | @ -119,7 +121,7 @@ def get_output(moddict, name): | |||
|                         return moddict[name] | ||||
|                 newns = ns.copy() if ns else {} | ||||
|                 try: | ||||
|                     exec(newcode, newns) | ||||
|                     exec_func(newcode, newns) | ||||
|                 except raises as e: | ||||
|                     # We care about e.g. NameError vs UnboundLocalError | ||||
|                     self.assertIs(type(e), raises) | ||||
|  | @ -613,6 +615,45 @@ def test_frame_locals(self): | |||
|         import 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} | ||||
| 
 | ||||
|  |  | |||
|  | @ -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, | ||||
|                                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 (nlocalsplus != PyTuple_GET_SIZE(localsplusnames)) { | ||||
|         if (_PyTuple_Resize(&localsplusnames, nlocalsplus) < 0 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jelle Zijlstra
						Jelle Zijlstra