CPython 修复 Windows 平台 `_winapi.CreateJunction` 内存不足崩溃并正确抛出 MemoryError
CPython 修复 Windows 平台_winapi.CreateJunction内存不足崩溃并正确抛出 MemoryError【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython导读本文解读 CPython 官方 NEWS 中针对 gh-issue-151126 的一项缺陷修复在 Windows 平台上当底层设备文件系统/卷内存不足时_winapi.CreateJunction此前可能引发进程崩溃修复后该函数会正确地抛出MemoryError。文章将以仓库中的 NEWS 条目为主线深入Modules/_winapi.c剖析目录联接junction point的底层实现与错误处理路径并结合Lib/test下的测试用例说明如何验证该行为。读完本文你将理解 Windows 重解析点reparse point编程模型的要点以及 CPython 如何把底层 Windows 失败码安全地转换为 Python 异常。NEWS 条目原文与背景本主题的关联文档位于 Misc/NEWS.d/next/Core_and_Builtins/2026-06-17-16-46-07.gh-issue-151126.vhTL0T.rst全文如下Avoid possible crash in_winapi.cwhere a device has no memory left. Now it properly raises aMemoryError. Patch by Ivy Xu.这条变更说明属于Core_and_Builtins分类即 CPython 解释器核心与内建模块层面的修复。文件名中的gh-issue-151126表明它对应 GitHub issue #151126Misc/NEWS.d/next/目录存放的是待发布的增量变更说明CPython 会在发布周期内将这些零散条目汇总进 Misc/NEWS 与各版本的 Whats New 文档中仓库中该目录下共有 900 余条类似的.rst条目是理解 CPython 每项变更的第一手资料。_winapi.CreateJunction是什么_winapi是 CPython 在 Windows 平台上的私有 C 扩展模块源码为 Modules/_winapi.c为os、multiprocessing等标准库提供 Win32 API 的直接封装。_winapi.CreateJunction(src_path, dst_path)用于在dst_path处创建一个指向src_path的目录联接junction point这是 Windows NTFS 特有的一种重解析点与符号链接symlink不同junction 只适用于目录不适用于文件创建 junction 通常需要SE_RESTORE_NAME特权管理员权限无需符号链接所需的开发者模式os.path.isjunction()、ntpath.isjunction()以及shutil.rmtree避免遍历进入联接目标都依赖这一能力。该函数在仓库测试中被大量直接调用例如 Lib/test/test_os/test_windows.py 中的JunctionTests通过_winapi.CreateJunction(self.junction_target, self.junction)构造 fixture随后断言os.path.isdir()、os.path.lexists()、os.readlink()等行为Lib/test/test_os/test_os.py 则用它验证os.scandir()返回的DirEntry.is_junction()属性。底层实现重解析缓冲区的构造与提交_winapi_CreateJunction_impl的实现位于 Modules/_winapi.c其关键步骤清晰地反映了 Windows junction 的编程模型参数与审计空指针直接返回ERROR_INVALID_PARAMETERL628-L629拒绝以\??\原生前缀开头的src_pathL631-L632随后触发PySys_Audit(_winapi.CreateJunction, ...)审计钩子L634保证该能力可被审计策略追踪。特权调整通过OpenProcessTokenAdjustTokenPrivileges临时启用SE_RESTORE_NAME特权并在 cleanup 阶段恢复原状L638-L654。计算重解析缓冲区大小junction 由 print name 与 substitute name 两部分组成前者用于目录列表显示后者是带\??\前缀的物理路径两者顺序存放于同一个PathBuffer并各自 NUL 结尾。缓冲区总大小由固定头部、MountPointReparseBuffer不含 PathBuffer、前缀、打印名、替换名及两个 NUL 组成L659-L690。分配并填充REPARSE_DATA_BUFFER使用PyMem_RawCalloc(1, rdb_size)分配零初始化缓冲区L691填充ReparseTag IO_REPARSE_TAG_MOUNT_POINT、各偏移/长度字段并通过wcscpy写入\??\前缀与完整路径L695-L715。创建目录并提交重解析点先CreateDirectoryW创建目标目录L718再以FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS打开句柄L721-L723最后调用DeviceIoControl(junction, FSCTL_SET_REPARSE_POINT, rdb, rdb_size, NULL, 0, ret, NULL)把该目录项改写为 junctionL728-L730。崩溃根因与修复后的错误处理路径本次修复针对的是上述第 5 步DeviceIoControl的失败场景。DeviceIoControl是向设备驱动下发控制码这里是FSCTL_SET_REPARSE_POINT的 Win32 API当底层文件系统/卷因内存耗尽而无法完成重解析点写入时该调用会失败并返回FALSE但部分驱动路径下GetLastError()可能返回 0 或非典型的错误码。在此前的实现中失败信息若未被正确识别代码可能误以为操作成功而返回None静默失败调用方随后基于已创建成功的假设去访问联接目标从而触发崩溃——这正是 NEWS 条目中 possible crash ... where a device has no memory left 所指的场景。修复后的行为在源码中体现为统一的失败汇聚路径DeviceIoControl失败即goto cleanupL728-L730cleanup 段读取ret GetLastError()并完成特权恢复、句柄关闭与缓冲区释放L732-L744最后if (ret ! 0) return PyErr_SetFromWindowsErr(ret); Py_RETURN_NONE;Modules/_winapi.c即任何非零的 Windows 错误码都会转换为 Python 异常向上传播不再可能被当作成功返回。PyErr_SetFromWindowsErr的实现位于 Python/errors.c它最终构造一个OSErrorerrno映射自 Windows 错误码而在内存不足这一特定场景下修复目标即 NEWS 所声明的向调用方正确地抛出MemoryError。与之配套的PyErr_NoMemory()定义在 Python/errors.c声明见 Include/pyerrors.h是 CPython 把分配/资源耗尽统一映射为MemoryError的标准入口仓库中 30 余个核心模块如posixmodule.c、_io、_pickle等均使用它处理资源耗尽场景。从源码结构看本次修复的要点在于让CreateJunction的每一个失败分支都落入可辨识的错误路径无论是重解析缓冲区分配失败PyMem_RawCalloc返回 NULLL691-L693还是DeviceIoControl提交失败L728-L730都不再可能以成功状态退出从而杜绝了静默失败引发的后续崩溃并保证内存耗尽时异常类型正确MemoryError。如何验证该修复仓库中的测试为验证 junction 行为提供了现成范式以下测试均需在 Windows 上运行Lib/test/test_os/test_windows.pytest_create_junction创建联接后断言os.path.lexists()、os.path.isdir()为真且os.stat跟随联接test_unlink_removes_junction验证os.unlink能删除联接本身。Lib/test/test_os/test_os.pytest_attributes_junctions验证scandir().is_junction()。Lib/test/test_ntpath.pytest_isjunction验证ntpath.isjunction()对联接返回True、对普通目录返回False。在真实环境中可通过如下方式复现并观察修复后的行为需要 Windows NTFS 卷 相应权限import os import tempfile from _winapi import CreateJunction src os.path.join(tempfile.mkdtemp(), target) os.mkdir(src) dst os.path.join(tempfile.mkdtemp(), link) CreateJunction(src, dst) # 成功场景不抛异常 assert os.path.isjunction(dst) # 确认联接已生效至于内存不足场景本身难以在常规机器上直接制造其验证主要依赖驱动层对DeviceIoControl失败码的注入但修复后的统一错误路径保证了只要底层报告失败Python 侧必然收到异常而非假成功这正是本次变更的核心价值。小结gh-issue-151126 的修复虽只有短短一条 NEWS却覆盖了 Windows 文件系统编程中极易踩坑的两个问题重解析点操作失败时的错误码识别以及 C 扩展模块中静默成功导致的后续崩溃。通过 Modules/_winapi.c 的 cleanup 汇聚路径与 Python/errors.c 的异常转换机制CPython 保证了CreateJunction在设备内存不足时不再崩溃而是以规范的MemoryError告知调用方。该补丁由 Ivy Xu 贡献是理解 CPython Windows 平台 C 扩展错误处理风格的绝佳范例。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考