引子

最近依然在尝试做一些大模型目前做不到的事儿,去满足一些存在感。但是 TiDB 的 issues 没有以前多了,恰好某一天 @kemingy 发现 cpython 源码里有些写的不好的地方,我想 cpython 也许也可以?但我万万没想到一个 patch 进入到 cpython 是多么的难,我已经前前后后修了 4 个 cpython 的 bug 但是每个都在过程中,过程中学到了不少东西,但更开心的是认识到了目前即使最强的大模型的局限。

bug ?

  • bug 是这个 https://github.com/python/cpython/issues/134163

尝试的过程

下面是我尝试的过程,如果能同样帮到喜欢 bug 的朋友就更好了

  1. 当然是尝试复现,3.13 在 repl 使用 issue 中的语句 hang, 3.14 不 hang 住,是 3.13 only 的问题
  2. Claude Code 启动,先让大模型帮我定位下,大模型前前后后走了几圈,都是错的,而且大模型的思路有问题,它认为 3.14 修了就是有的 commit 修了,再不停的查 commit
  3. 自己来吧,首先先缩小范围
    • 在 repl 里必复现
    • 直接使用 ./python xxx.py 没事
    • pty 没事儿
    • subprocess 没事
    • 卧槽只有 repl 里?
    • 强制使用旧 repl PYTHON_BASIC_REPL=1 ./python 也没事儿!
  4. OK 那是 new repl only, 定位好了问题

能复现就是成功的一半?

  1. no 这个 issue 并不是
  2. 这个是 c 层面的还是 python 层面的?因为只有在 new repl 里我开始以为是 python 层面的
  3. 在所有可能是 memory error 的地方接上 except: nothing work
  4. 在源码里加 print 卧槽加上 print bug 消失了,妈的
  5. 因为上条我怀疑我走错路了,大概率是 c 层面的,但是我得定位到哪一行造成 c 层面的错误
  6. 再一顿 print debug 之后发现是 console.py 里的这一行 exec(code, self.locals)
  7. OK 找到这个 bug 就可以简化为在 new repl 里执行 exec("_testcapi.set_nomemory(0)")

但是但是

  1. 我在 c 层面一路找 memroy 相关的没头绪,能改的地方都改了
  2. 过程中还熟悉了一点 cpython 的代码
  3. 但是还是不行

先放弃了,但还想着

  • 然后我就把问题先搁置了去忙别的了
  • 但是跑步的过程中突然有了一点想法也许不是 3.13 only 的
  • git branch 启动!对不是!发现 3.14 第一个版本仍然有这个 bug
  • 那么是某个小 branch 给修了
  • branch 二分法启动!最后发现是 3.14a2 -> 3.14a3 之间某个 commit 给意外的修复了?
  • 既然 branch 能二分法我突然悟到了可以 bisect
  • 第一次用 bisect 在大模型的教学下一路找(这里有个坑就是 good 和 bad 是反的)最后发现是这个 commit 意外的给修了 5fc6bb2754a25157575efc0b37da78c629fea46e
  • 修的人好强啊
  • 能 cherry-pick 么?不能!因为改动太大了这之间的里
  • 能借住他修的一部分修复么?一路尝试,是不能的,因为本质上不是一个问题
  • 又尝试了好多。。。也不行,确性了 cpython 遗留的 bug 都是有难度的

找朋友吧

  • 当然是先找@kemingy 帮忙,他比我多走了两步但是也卡住了
  • 找 @frostming 也试试
  • 暂时没结果,早上突然想到我要去 pycon 的机票还在拖延,不想麻烦 gray 帮忙了 ,等下 @graymon 的演讲是 gdb 相关也许可以试试
  • gdb 启动
  • 这里也有个坑好像 gdb 会改变内存
  • 但是最后还是找到了是在 /Python/ceval.c b/Python/ceval.c 的 goto exception_unwind; 里陷入了无限的递归
  • 修复如果递归直接 pyerr clear 卧槽不 hang 了但是是 Segmentation fault
  • 继续,成功了!于是有了如下修复
  • 感谢朋友们的帮忙,开心

修复

diff --git a/Python/ceval.c b/Python/ceval.c
index 8c0cb29863c..3fe97a2c74c 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -912,7 +912,13 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
                 int frame_lasti = _PyInterpreterFrame_LASTI(frame);
                 PyObject *lasti = PyLong_FromLong(frame_lasti);
                 if (lasti == NULL) {
-                    goto exception_unwind;
+                    // If we can't allocate memory for lasti during exception handling,
+                    // this likely means we're in a severe memory shortage situation.
+                    // Instead of going back to exception_unwind (which would cause
+                    // infinite recursion), directly exit to let the original exception
+                    // propagate up and hopefully be handled at a higher level.
+                    _PyFrame_SetStackPointer(frame, stack_pointer);
+                    goto exit_unwind;
                 }
                 PUSH(lasti);
             }

修复的对么?

  • 不知道,先跑测试 3.13 都通过了
  • main 有这个问题么?没有,代码结构都改了
  • 对不对我先把过程分享出来,能解决一大部分也是开心的

感想

  • 谢谢朋友们
  • 大模型还是有局限的但还是能帮不少忙的
  • gray 好厉害
  • cpython 能遗留下来的 bug 都非常难
  • 还是开心

---- update ----

https://t.me/c/1459082815/900

和 gray 学习。