Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 48 additions & 20 deletions eden/scm/lib/commands/cmdpy/src/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,18 +134,40 @@ pub fn py_initialize(args: &[String], sapling_home: Option<&String>) {

check_status!(ffi::Py_PreInitialize(&pre_config), None);

let mut config = ffi::PyConfig::default();
// python3-sys 0.7.2 is missing fields added in Python 3.13 (cpu_count,
// sys_path_0), making the Rust PyConfig struct ~12 bytes too small.
// PyConfig_InitPythonConfig writes the full C struct, so we need extra
// space to prevent a buffer overflow. The overflow_guard absorbs this.
//
// The missing cpu_count field also shifts the C offsets of all later
// fields (executable, prefix, module_search_paths, etc.) relative to
// the Rust struct. We avoid accessing any shifted field directly:
// - executable: computed automatically by PyConfig_Read
// - module_search_paths: only set for Python 3.10
// - prefix: only set for static_libpython builds
// Fields we DO set (install_signal_handlers, site_import, parse_argv,
// user_site_directory, argv) all precede cpu_count and are unaffected.
#[repr(C, align(8))]
struct PyConfigStorage {
inner: ffi::PyConfig,
_overflow_guard: [u64; 8],
}
let mut storage = PyConfigStorage {
inner: ffi::PyConfig::default(),
_overflow_guard: [0u64; 8],
};
let config = (&mut storage as *mut PyConfigStorage).cast::<ffi::PyConfig>();

// Ideally we could use PyConfig_InitIsolatedConfig, but we rely on some
// of the vanilla initialization logic to find the std lib, at least.
ffi::PyConfig_InitPythonConfig(&mut config);
ffi::PyConfig_InitPythonConfig(config);

config.install_signal_handlers = 0;
config.site_import = 0;
config.parse_argv = 0;
(*config).install_signal_handlers = 0;
(*config).site_import = 0;
(*config).parse_argv = 0;

// This allows IPython to be installed in user site dir.
config.user_site_directory = 1;
(*config).user_site_directory = 1;

// This assumes Python has been pre-initialized, and filesystem encoding
// is utf-8 (both done above).
Expand All @@ -156,15 +178,15 @@ pub fn py_initialize(args: &[String], sapling_home: Option<&String>) {
}
}

check_status!(
ffi::PyConfig_SetString(&mut config, &mut config.executable, to_wide(&args[0])),
Some(config)
);

// Note: we intentionally skip setting config.executable here. On
// Python < 3.13 it was set to args[0] (current_exe), but PyConfig_Read
// computes it automatically. On Python >= 3.13, the field offset is
// wrong in python3-sys 0.7.2 due to the missing cpu_count field.
// argv is before cpu_count in the struct layout, so it's safe.
for arg in args.iter() {
check_status!(
ffi::PyWideStringList_Append(&mut config.argv, to_wide(arg)),
Some(config)
ffi::PyWideStringList_Append(&mut (*config).argv, to_wide(arg)),
None
);
}
Comment on lines 186 to 191

Expand All @@ -178,13 +200,19 @@ pub fn py_initialize(args: &[String], sapling_home: Option<&String>) {
let exe_dir = exe_dir.to_str().expect("utf-8 exe dir");
// "prefix" affects many other paths.
// See https://docs.python.org/3/c-api/init_config.html#init-path-config
//
// WARNING: On Python 3.13+, config.prefix has the wrong Rust offset
// due to the missing cpu_count field in python3-sys 0.7.2. This code
// path (static_libpython) is not used on standard OSS builds, so this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not true, though. The manylinux build does use static libpython.

// is acceptable for now. A proper fix requires vendoring python3-sys
// or migrating to PyO3.
check_status!(
ffi::PyConfig_SetString(&mut config, &mut config.prefix, to_wide(exe_dir)),
Some(config)
None
);
Comment on lines +203 to 212
}

check_status!(ffi::PyConfig_Read(&mut config), Some(config));
check_status!(ffi::PyConfig_Read(config), None);

// "3.10.9 (v3.10.9:1dd9be6584, Dec 6 2022, 14:37:36) [Clang 13.0.0 (clang-1300.0.29.30)]"
let version = CStr::from_ptr(ffi::Py_GetVersion());
Expand All @@ -203,10 +231,10 @@ pub fn py_initialize(args: &[String], sapling_home: Option<&String>) {
if minor_version == Some(10) {
if let Some(home) = sapling_home {
// This tells Py_Main to not overwrite sys.path and to copy our below value.
config.module_search_paths_set = 1;
(*config).module_search_paths_set = 1;
check_status!(
ffi::PyWideStringList_Append(&mut config.module_search_paths, to_wide(home)),
Some(config)
ffi::PyWideStringList_Append(&mut (*config).module_search_paths, to_wide(home)),
None
);
}
}
Expand All @@ -217,9 +245,9 @@ pub fn py_initialize(args: &[String], sapling_home: Option<&String>) {
ffi::PyImport_FrozenModules = EXTRA_FROZEN_MODULES.0.as_ptr() as _;
}

check_status!(ffi::Py_InitializeFromConfig(&config), Some(config));
check_status!(ffi::Py_InitializeFromConfig(config), None);

ffi::PyConfig_Clear(&mut config);
ffi::PyConfig_Clear(config);
}
}

Expand Down
Loading