]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-133371: Don't optimize `LOAD_FAST` instructions whose local is killed by `DELETE_F...
authormpage <mpage@meta.com>
Mon, 5 May 2025 04:00:11 +0000 (21:00 -0700)
committerGitHub <noreply@github.com>
Mon, 5 May 2025 04:00:11 +0000 (21:00 -0700)
In certain cases it's possible for locals loaded by `LOAD_FAST` instructions
to be on the stack when the local is killed by `DEL_FAST`. These `LOAD_FAST`
instructions should not be optimized into `LOAD_FAST_BORROW` as the strong
reference in the frame is killed while there is still a reference on the stack.

Include/internal/pycore_magic_number.h
Lib/test/test_peepholer.py
Python/flowgraph.c

index a96cb6236f78b7c2c6782bfbde13169d7d347fb1..1cc897c5a6946969d36d1a27fb8df657a525f259 100644 (file)
@@ -276,6 +276,7 @@ Known values:
     Python 3.14a7 3621 (Optimize LOAD_FAST opcodes into LOAD_FAST_BORROW)
     Python 3.14a7 3622 (Store annotations in different class dict keys)
     Python 3.14a7 3623 (Add BUILD_INTERPOLATION & BUILD_TEMPLATE opcodes)
+    Python 3.14b1 3624 (Don't optimize LOAD_FAST when local is killed by DELETE_FAST)
 
     Python 3.15 will start with 3650
 
@@ -288,7 +289,7 @@ PC/launcher.c must also be updated.
 
 */
 
-#define PYC_MAGIC_NUMBER 3623
+#define PYC_MAGIC_NUMBER 3624
 /* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
    (little-endian) and then appending b'\r\n'. */
 #define PYC_MAGIC_NUMBER_TOKEN \
index 565e42b04a68d0e88e18c69504874f3755d11157..47f51f1979faabc9f9b064cde474ca0fa5e9ab23 100644 (file)
@@ -1,4 +1,5 @@
 import dis
+import gc
 from itertools import combinations, product
 import opcode
 import sys
@@ -2472,6 +2473,13 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
         ]
         self.check(insts, insts)
 
+        insts = [
+            ("LOAD_FAST", 0, 1),
+            ("DELETE_FAST", 0, 2),
+            ("POP_TOP", None, 3),
+        ]
+        self.check(insts, insts)
+
     def test_unoptimized_if_aliased(self):
         insts = [
             ("LOAD_FAST", 0, 1),
@@ -2606,6 +2614,22 @@ class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
         ]
         self.cfg_optimization_test(insts, expected, consts=[None])
 
+    def test_del_in_finally(self):
+        # This loads `obj` onto the stack, executes `del obj`, then returns the
+        # `obj` from the stack. See gh-133371 for more details.
+        def create_obj():
+            obj = [42]
+            try:
+                return obj
+            finally:
+                del obj
+
+        obj = create_obj()
+        # The crash in the linked issue happens while running GC during
+        # interpreter finalization, so run it here manually.
+        gc.collect()
+        self.assertEqual(obj, [42])
+
 
 
 if __name__ == "__main__":
index e0bb5615db3e0b6e8f35a7dce7b1eed842714867..78ef02a911a72bc2446d6d45a34a550e4fbe9e69 100644 (file)
@@ -2795,6 +2795,11 @@ optimize_load_fast(cfg_builder *g)
             assert(opcode != EXTENDED_ARG);
             switch (opcode) {
                 // Opcodes that load and store locals
+                case DELETE_FAST: {
+                    kill_local(instr_flags, &refs, oparg);
+                    break;
+                }
+
                 case LOAD_FAST: {
                     PUSH_REF(i, oparg);
                     break;