Technical note / C API robustness

Propagating allocation failure in os._path_normpath()

A three-line guard that preserves Python’s exception contract when an intermediate Unicode allocation fails during Windows bytes-path normalization.

Project
CPython
Contribution
PR #151779
Tracked by
Issue #151763 / OOM-0028
Role
PR author
Outcome
Merged 24 June 2026

A valid C API failure was followed by an invalid API call.

On Windows, CPython’s internal os._path_normpath() implementation can normalize bytes paths by first producing a Unicode object and then encoding it back to the filesystem representation. The conversion uses PyUnicode_FromWideChar(), an allocating C API that can return NULL while setting MemoryError.

PR #151779 addressed finding OOM-0028 under CPython’s broader out-of-memory tracking issue #151763. The umbrella issue remains separate from the merge state of this specific fix.

The bytes branch assumed Unicode construction succeeded.

After normalization, the function created a Python Unicode object from the wide-character result. The bytes-path branch then passed that object to PyUnicode_EncodeFSDefault() without first checking whether allocation had failed.

Normalize wide pathAllocate UnicodeNULL + MemoryErrorEncode NULL

The pending exception was already correct. The missing piece was returning immediately instead of continuing with an invalid object pointer.

Honor the allocating API’s contract before branching.

The merged patch inserts an explicit null check after PyUnicode_FromWideChar(). On failure, the function returns NULL so the existing MemoryError propagates through the Python call boundary. Successful Unicode and bytes paths retain their previous behavior.

result = PyUnicode_FromWideChar(...);
if (result == NULL) {
    return NULL;
}
// Encode only after construction succeeds.

Force the exceptional allocation path in a debug build.

The public PR record documents validation with a Windows debug build and CPython’s allocation-failure controls. Before the patch, the selected failure point produced an access violation. After the guard, the same path raised MemoryError cleanly. Focused ntpath tests also passed.

No regression test was committed for the exact allocation index because that index can vary with the build. The contribution records that limitation rather than presenting a build-sensitive trigger as a stable test contract.

Error propagation is part of memory safety in extension code.

  • Treat every allocating C API as a branch, even when failure is rare.
  • When an API sets the correct Python exception, the safest fix may be immediate propagation rather than replacement.
  • Fault-injection evidence is useful even when the exact allocation index is too build-dependent for a permanent regression test.
  • Small guard patches can be technically complete when they restore an established API invariant.

Authoritative upstream record.