Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion pyre/pyre-interpreter/src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7591,7 +7591,7 @@ fn make_exc_type_with_doc(
/// `UnicodeDecodeError.__new__(cls, *args)` call would inherit the
/// typechecking that PyPy keeps confined to `descr_init` — see
/// `_new` at `:274-284` (no per-arg validation).
fn make_exc_type_with_init(
pub(crate) fn make_exc_type_with_init(
name: &'static str,
doc: Option<&'static str>,
new_fn: crate::gateway::BuiltinCodeFn,
Expand Down
2 changes: 1 addition & 1 deletion pyre/pyre-interpreter/src/module/_ctypes/cdata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ pub(super) fn cdata_in_dll(args: &[PyObjectRef]) -> Result<PyObjectRef, crate::P
args[1],
));
}
let address = host_ctypes::lookup_function_symbol_addr(handle, name.as_bytes())
let address = super::interp_ctypes::lookup_symbol(handle, name.as_bytes())
.map_err(|_| crate::PyError::value_error(format!("symbol '{name}' not found")))?;
Ok(make_at_address(cls, address, size, args[1]))
}
Expand Down
2 changes: 1 addition & 1 deletion pyre/pyre-interpreter/src/module/_ctypes/funcptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ fn resolve_from_tuple(t: PyObjectRef) -> Result<usize, crate::PyError> {
b"PyOS_snprintf" => return Ok(INTERNAL_PYOS_SNPRINTF),
_ => {}
}
host_ctypes::lookup_function_symbol_addr(handle, &name_bytes).map_err(|e| {
super::interp_ctypes::lookup_symbol(handle, &name_bytes).map_err(|e| {
use host_ctypes::LookupSymbolError as L;
if matches!(e, L::LibraryNotFound) {
return crate::PyError::value_error("library not found");
Expand Down
469 changes: 429 additions & 40 deletions pyre/pyre-interpreter/src/module/_ctypes/interp_ctypes.rs

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions pyre/pyre-interpreter/src/module/_ctypes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@
//! machinery is split across the submodules — `stginfo` carries a ctypes type's
//! layout, `metaclass` builds the types and their fields, `cdata` holds the
//! scalar instance buffer, and `funcptr` marshals and performs the foreign
//! call. The submodules are unix-only; elsewhere the module is limited to what
//! `interp_ctypes` can offer without `host_env`.
//! call. The submodules need `host_env`; without it the module is limited to
//! the placeholder surface at the foot of `interp_ctypes`.

crate::pyre_module_init!(interp_ctypes);

/// Store into a builtin type's namespace — the dict `make_builtin_type` hands
/// its init closure. The type-namespace sibling of `module_ns_store`.
#[cfg(all(unix, feature = "host_env"))]
#[cfg(all(any(unix, windows), feature = "host_env"))]
fn type_ns_store(ns: pyre_object::PyObjectRef, name: &str, value: pyre_object::PyObjectRef) {
unsafe { pyre_object::dictmultiobject::w_dict_setitem_str_no_proxy(ns, name, value) }
}

#[cfg(all(unix, feature = "host_env"))]
#[cfg(all(any(unix, windows), feature = "host_env"))]
pub mod cdata;
#[cfg(all(unix, feature = "host_env"))]
#[cfg(all(any(unix, windows), feature = "host_env"))]
pub mod funcptr;
#[cfg(all(unix, feature = "host_env"))]
#[cfg(all(any(unix, windows), feature = "host_env"))]
pub mod metaclass;
#[cfg(all(unix, feature = "host_env"))]
#[cfg(all(any(unix, windows), feature = "host_env"))]
pub mod stginfo;
34 changes: 34 additions & 0 deletions pyre/pyre-interpreter/src/module/_winapi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,39 @@ mod process {
Ok(w_int_new(host_winapi::get_last_error() as i64))
}

/// `_winapi.GetModuleFileName(module_handle)` — the path the module was
/// loaded from, or the executable's own path for handle 0.
///
/// `sysconfig._init_non_posix` calls it on `sys.dllhandle` to locate the
/// install prefix, so it is on the path of every `import sysconfig` here.
///
/// The `MAX_PATH` buffer is the interface, not a shortcut: this call is
/// specified to hand back one fixed-size buffer, so a longer path comes
/// back truncated rather than retried in a growing loop. `initpath.py:308-315
/// _get_module_file_name` allocates exactly `_MAX_PATH` and gives up when
/// the result does not fit, and the module this shadows declares
/// `WCHAR filename[MAX_PATH]` and forces `filename[MAX_PATH - 1] = '\0'`
/// before returning it. Growing the buffer here would be the deviation.
///
/// The length is then the NUL scan rather than the count the call reported,
/// because on a truncation the call reports the whole buffer while the
/// loader has already terminated the string inside it — taking the reported
/// count would append that terminator to the path.
pub fn get_module_file_name(args: &[PyObjectRef]) -> Result<PyObjectRef, crate::PyError> {
const MAX_PATH: usize = 260;
let module = handle_w(arg(args, 0, "GetModuleFileName")?)? as *mut core::ffi::c_void;
let mut buffer = [0u16; MAX_PATH];
let length = host_winapi::get_module_file_name(module, &mut buffer);
Comment on lines +124 to +125

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Retry GetModuleFileName with a larger buffer

When pyre is installed or launched from a Windows path longer than 259 UTF-16 units, this fixed buffer truncates the module path and the code then returns that truncated value as success. Callers such as sysconfig._init_non_posix (lib-python/3/sysconfig/__init__.py:420) use the result to derive the installation prefix, so imports and configuration paths can point at a nonexistent directory. Detect a full buffer and retry with a larger allocation instead of forcibly terminating the truncated result.

Useful? React with 👍 / 👎.

if length == 0 {
return Err(super::last_os_error());
}
buffer[MAX_PATH - 1] = 0;
let filename = &buffer[..buffer.iter().position(|&u| u == 0).unwrap_or(MAX_PATH)];
Ok(pyre_object::w_str_from_wtf8(
rustpython_wtf8::Wtf8Buf::from_wide(filename),
))
}

/// `_winapi.TerminateProcess(handle, exit_code)`
pub fn terminate_process(args: &[PyObjectRef]) -> Result<PyObjectRef, crate::PyError> {
let handle = handle_w(arg(args, 0, "TerminateProcess")?)?;
Expand Down Expand Up @@ -408,6 +441,7 @@ crate::py_module! {
("GetCurrentProcess", 0, process::get_current_process),
("GetFileType", 1, process::get_file_type),
("GetLastError", 0, process::get_last_error),
("GetModuleFileName", 1, process::get_module_file_name),
("TerminateProcess", 2, process::terminate_process),
("CreatePipe", 2, process::create_pipe),
("CreateProcess", 9, process::create_process),
Expand Down
229 changes: 193 additions & 36 deletions pyre/pyre-interpreter/src/module/posix/interp_posix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3334,14 +3334,15 @@ pub fn register_module(ns: pyre_object::PyObjectRef) {
// `($module, fd=<unrepresentable>, /)` — the descriptor is
// positional-only, so `fd=1` is a keyword this entry point does
// not take rather than a binding.
let (bound, _kwargs) = bind_posonly_args(
args,
"get_terminal_size",
"posix.get_terminal_size",
1,
0,
&[],
)?;
// A keyword is refused against the module-qualified name, and this
// module answers to `nt` on Windows.
let qualname = if cfg!(windows) {
"nt.get_terminal_size"
} else {
"posix.get_terminal_size"
};
let (bound, _kwargs) =
bind_posonly_args(args, "get_terminal_size", qualname, 1, 0, &[])?;
let fd = match bound[0] {
Some(w) => crate::baseobjspace::c_int_w(w)?,
None => 1,
Expand Down Expand Up @@ -4073,7 +4074,7 @@ pub fn register_module(ns: pyre_object::PyObjectRef) {
/// `argument_unavailable` (`interp_posix.py:298-301`) — a modifier this
/// platform has no call to apply, named together with the entry point that
/// was asked to apply it.
#[cfg(all(unix, feature = "host_env"))]
#[cfg(feature = "host_env")]
fn argument_unavailable(funcname: &str, arg: &str) -> crate::PyError {
crate::PyError::not_implemented(format!("{funcname}: {arg} unavailable on this platform"))
}
Expand Down Expand Up @@ -8996,27 +8997,182 @@ pub fn register_module(ns: pyre_object::PyObjectRef) {
),
);

// os.access(path, mode) -> bool. Windows has no permission bits to
// consult beyond the read-only attribute, so `W_OK` is the only mode
// that can answer False for a name that exists (`os_access_impl`).
// os.access(path, mode, *, dir_fd=None, effective_ids=False,
// follow_symlinks=True) -> bool
//
// Windows has no permission bits to consult beyond the read-only
// attribute, so `W_OK` is the only mode that can answer False for a
// name that exists (`os_access_impl`). None of the three modifiers
// has a call to reach: `dir_fd` types as `dir_fd(requires='faccessat')`
// and the other two are the pair `os_access_impl` turns away without
// `faccessat`, so each is refused rather than answered as though it
// had been applied.
crate::module_ns_store(
ns,
"access",
crate::make_builtin_function("access", |args| {
if args.len() < 2 {
return Err(crate::PyError::type_error("access() requires 2 arguments"));
}
let path = crate::gateway::fsencode_path_named_w(args[0], "access", "path")?;
// The three modifiers are keyword-only, so a third positional
// is an error rather than a `dir_fd`.
let (bound, kwargs) = bind_path_args(
args,
"access",
&["path", "mode"],
2,
&["dir_fd", "effective_ids", "follow_symlinks"],
)?;
// The parameters convert in declaration order and each of them
// can raise, so the order is observable: `path` reports before
// `mode`, and both before either flag's `__bool__` is called.
let path = crate::gateway::fsencode_path_named_w(
bound[0].expect("path is required"),
"access",
"path",
)?;
// Only `W_OK` is read, so the byte holding it is the whole of
// the mode as far as the answer goes.
let mode = crate::baseobjspace::c_int_w(args[1])? as u8;
let mode = crate::baseobjspace::c_int_w(bound[1].expect("mode is required"))? as u8;
dir_fd_kwarg(kwargs, false)?;
if let Some(v) = crate::builtins::kwarg_get(kwargs, "effective_ids")
&& crate::baseobjspace::is_true(v)?
{
return Err(argument_unavailable("access", "effective_ids"));
}
if let Some(v) = crate::builtins::kwarg_get(kwargs, "follow_symlinks")
&& !crate::baseobjspace::is_true(v)?
{
return Err(argument_unavailable("access", "follow_symlinks"));
}
Ok(pyre_object::w_bool_from(host_nt::access(
path_from_bytes(&path.as_bytes).as_ref(),
mode,
)))
}),
);

// os.execv(path, argv) / os.execve(path, argv, env)
//
// `_wexecv` / `_wexecve` are the wide forms `os_execv_impl` reaches
// for; they return only on failure, because on success the calling
// process is gone by the time they would.
fn exec_argv_wide(
w_argv: PyObjectRef,
function: &str,
) -> Result<Vec<widestring::WideCString>, crate::PyError> {
let items = crate::baseobjspace::unpackiterable(w_argv, -1).map_err(|error| {
if error.kind == crate::PyErrorKind::TypeError {
crate::PyError::type_error(format!(
"{function}() arg 2 must be an iterable of strings"
))
} else {
error
}
})?;
if items.is_empty() {
return Err(crate::PyError::value_error(format!(
"{function}() arg 2 must not be empty"
)));
}
let mut argv = Vec::with_capacity(items.len());
for item in items {
// An element is converted on the sequence's behalf, not as an
// argument of the call, so the caller-less message is the one
// it reports — the same for the environment below.
let value = extract_path(item)?;
argv.push(widestring::WideCString::from_os_str(&*os_str_from_bytes(&value))
.map_err(|_| {
crate::PyError::value_error(format!(
"{function}() arg 2 contains an embedded null byte"
))
})?);
}
if argv[0].is_empty() {
return Err(crate::PyError::value_error(format!(
"{function}() arg 2 first element cannot be empty"
)));
}
Ok(argv)
}

fn exec_pointer_array_wide(values: &[widestring::WideCString]) -> Vec<*const u16> {
let mut pointers: Vec<_> = values.iter().map(|value| value.as_ptr()).collect();
pointers.push(std::ptr::null());
pointers
}

crate::module_ns_store(
ns,
"execv",
crate::make_builtin_function_with_arity(
"execv",
|args| {
// The path names itself; the argv entries do not, because
// each of those is converted on the sequence's behalf
// rather than as an argument of its own.
let command =
crate::gateway::fsencode_path_named_w(args[0], "execv", "path")?.as_bytes;
let command_w = wide_path(&command)?;
let argv = exec_argv_wide(args[1], "execv")?;
let argv_ptrs = exec_pointer_array_wide(&argv);
unsafe { libc::wexecv(command_w.as_ptr(), argv_ptrs.as_ptr()) };
// `wrap_oserror` names no file, so the path stays out of
// the error.
Err(io_err(std::io::Error::last_os_error(), ""))
Comment on lines +9116 to +9119

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

execv and execve read the wrong error source after a failed C runtime exec. Both call sites invoke a wide C runtime exec function and then build the OSError from std::io::Error::last_os_error(). On Windows that reads GetLastError(), but _wexecv and _wexecve report failure through the C runtime errno. io_err then maps a Win32 code as though it were a POSIX errno, so a failed exec raises an OSError with an unrelated code. The shared fix is to route both calls through crate::builtins::crt_call! and read crate::builtins::crt_errno(), which is the convention the rest of this file already uses for C runtime calls.

  • pyre/pyre-interpreter/src/module/posix/interp_posix.rs#L9116-L9119: wrap the libc::wexecv call in crt_call! and build the error with errno_err(crate::builtins::crt_errno(), "").
  • pyre/pyre-interpreter/src/module/posix/interp_posix.rs#L9167-L9170: wrap the libc::wexecve call in crt_call! and build the error with errno_err(crate::builtins::crt_errno(), "").
📍 Affects 1 file
  • pyre/pyre-interpreter/src/module/posix/interp_posix.rs#L9116-L9119 (this comment)
  • pyre/pyre-interpreter/src/module/posix/interp_posix.rs#L9167-L9170
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/pyre-interpreter/src/module/posix/interp_posix.rs` around lines 9116 -
9119, Update both exec call sites in
pyre/pyre-interpreter/src/module/posix/interp_posix.rs:9116-9119 and 9167-9170,
covering wexecv and wexecve, to invoke the wide C runtime functions through
crate::builtins::crt_call! and construct failures with
errno_err(crate::builtins::crt_errno(), ""). Replace the current
std::io::Error::last_os_error()/io_err handling at both sites; no other changes
are required.

},
2,
),
);

crate::module_ns_store(
ns,
"execve",
crate::make_builtin_function_with_arity(
"execve",
|args| {
let command =
crate::gateway::fsencode_path_named_w(args[0], "execve", "path")?.as_bytes;
let command_w = wide_path(&command)?;
let argv = exec_argv_wide(args[1], "execve")?;
let argv_ptrs = exec_pointer_array_wide(&argv);

let keys_obj = crate::baseobjspace::call_method(args[2], "keys", &[]);
if keys_obj.is_null() {
return Err(crate::call::take_call_error().unwrap_or_else(|| {
crate::PyError::type_error("execve: env must be a mapping")
}));
}
let keys = crate::baseobjspace::unpackiterable(keys_obj, -1)?;
let mut env = Vec::with_capacity(keys.len());
for key_obj in keys {
let value_obj = crate::baseobjspace::getitem(args[2], key_obj)?;
let key = extract_path(key_obj)?;
Comment on lines +9145 to +9147

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Root environment keys across mapping callbacks

When env is a custom mapping whose __getitem__ allocates or otherwise triggers collection, this loop keeps the mapping, all keys, and the returned value only in raw PyObjectRef locals while calling back into Python; a moving collection can therefore leave key_obj, value_obj, and the remaining keys entries stale before extract_path uses them. The analogous _winapi::environment_block path explicitly pins the mapping and keys and re-reads them after each callback (module/_winapi/mod.rs:203-222); the new Windows execve path needs the same rooting discipline.

Useful? React with 👍 / 👎.

let value = extract_path(value_obj)?;
if key.is_empty() || key.get(1..).is_some_and(|tail| tail.contains(&b'=')) {
return Err(crate::PyError::value_error(
"illegal environment variable name",
));
}
let mut entry = key;
entry.push(b'=');
entry.extend_from_slice(&value);
env.push(
widestring::WideCString::from_os_str(&*os_str_from_bytes(&entry))
.map_err(|_| {
crate::PyError::value_error(
"execve() environment contains an embedded null byte",
)
})?,
);
}
let env_ptrs = exec_pointer_array_wide(&env);
unsafe {
libc::wexecve(command_w.as_ptr(), argv_ptrs.as_ptr(), env_ptrs.as_ptr())
};
Err(io_err(std::io::Error::last_os_error(), ""))
},
3,
),
);

/// The mode bit Windows keeps: with the owner's write bit the
/// read-only attribute comes off, without it goes on.
const S_IWRITE: u32 = 0o200;
Expand Down Expand Up @@ -9185,27 +9341,28 @@ pub fn register_module(ns: pyre_object::PyObjectRef) {
CreateSymbolicLinkW, SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE,
SYMBOLIC_LINK_FLAG_DIRECTORY,
};
let (args, kwargs) = crate::builtins::split_builtin_kwargs(args);
crate::builtins::kwarg_reject_unknown(
kwargs,
&["target_is_directory", "dir_fd"],
let (bound, kwargs) = bind_path_args(
args,
"symlink",
&["src", "dst", "target_is_directory"],
2,
&["dir_fd"],
)?;
if crate::builtins::kwarg_get(kwargs, "dir_fd")
.is_some_and(|w| !unsafe { pyre_object::is_none(w) })
{
return Err(dir_fd_unavailable());
}
if args.len() < 2 {
return Err(crate::PyError::type_error("symlink() requires 2 arguments"));
}
let src = crate::gateway::fsencode_path_named_w(args[0], "symlink", "src")?;
let dst = crate::gateway::fsencode_path_named_w(args[1], "symlink", "dst")?;
let target_is_directory = match args
.get(2)
.copied()
.or_else(|| crate::builtins::kwarg_get(kwargs, "target_is_directory"))
{
// `symlink` types `dir_fd` as `DirFD(rposix.HAVE_SYMLINKAT)`,
// and `CreateSymbolicLinkW` resolves a relative name against
// the working directory alone.
dir_fd_kwarg(kwargs, false)?;
let src = crate::gateway::fsencode_path_named_w(
bound[0].expect("src is required"),
"symlink",
"src",
)?;
let dst = crate::gateway::fsencode_path_named_w(
bound[1].expect("dst is required"),
"symlink",
"dst",
)?;
let target_is_directory = match bound[2] {
Comment on lines +9344 to +9365

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Convert src and dst before you reject dir_fd.

dir_fd is keyword-only and is declared last. The conversions run in declaration order, and each conversion can raise and can run user code through __fspath__. Here dir_fd_kwarg runs first, so os.symlink(bad_path_object, dst, dir_fd=3) reports the dir_fd platform error instead of the src conversion error.

The access entry point added in this same change states this rule at Line 9023 and follows it: it converts path and mode, then calls dir_fd_kwarg. stat_entry follows the same order at Line 3832 and Line 3836. Move the dir_fd_kwarg call after both path conversions so symlink matches.

🐛 Proposed fix for the conversion order
-                // `symlink` types `dir_fd` as `DirFD(rposix.HAVE_SYMLINKAT)`,
-                // and `CreateSymbolicLinkW` resolves a relative name against
-                // the working directory alone.
-                dir_fd_kwarg(kwargs, false)?;
                 let src = crate::gateway::fsencode_path_named_w(
                     bound[0].expect("src is required"),
                     "symlink",
                     "src",
                 )?;
                 let dst = crate::gateway::fsencode_path_named_w(
                     bound[1].expect("dst is required"),
                     "symlink",
                     "dst",
                 )?;
+                // `symlink` types `dir_fd` as `DirFD(rposix.HAVE_SYMLINKAT)`,
+                // and `CreateSymbolicLinkW` resolves a relative name against
+                // the working directory alone.
+                dir_fd_kwarg(kwargs, false)?;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let (bound, kwargs) = bind_path_args(
args,
"symlink",
&["src", "dst", "target_is_directory"],
2,
&["dir_fd"],
)?;
if crate::builtins::kwarg_get(kwargs, "dir_fd")
.is_some_and(|w| !unsafe { pyre_object::is_none(w) })
{
return Err(dir_fd_unavailable());
}
if args.len() < 2 {
return Err(crate::PyError::type_error("symlink() requires 2 arguments"));
}
let src = crate::gateway::fsencode_path_named_w(args[0], "symlink", "src")?;
let dst = crate::gateway::fsencode_path_named_w(args[1], "symlink", "dst")?;
let target_is_directory = match args
.get(2)
.copied()
.or_else(|| crate::builtins::kwarg_get(kwargs, "target_is_directory"))
{
// `symlink` types `dir_fd` as `DirFD(rposix.HAVE_SYMLINKAT)`,
// and `CreateSymbolicLinkW` resolves a relative name against
// the working directory alone.
dir_fd_kwarg(kwargs, false)?;
let src = crate::gateway::fsencode_path_named_w(
bound[0].expect("src is required"),
"symlink",
"src",
)?;
let dst = crate::gateway::fsencode_path_named_w(
bound[1].expect("dst is required"),
"symlink",
"dst",
)?;
let target_is_directory = match bound[2] {
let (bound, kwargs) = bind_path_args(
args,
"symlink",
&["src", "dst", "target_is_directory"],
2,
&["dir_fd"],
)?;
let src = crate::gateway::fsencode_path_named_w(
bound[0].expect("src is required"),
"symlink",
"src",
)?;
let dst = crate::gateway::fsencode_path_named_w(
bound[1].expect("dst is required"),
"symlink",
"dst",
)?;
// `symlink` types `dir_fd` as `DirFD(rposix.HAVE_SYMLINKAT)`,
// and `CreateSymbolicLinkW` resolves a relative name against
// the working directory alone.
dir_fd_kwarg(kwargs, false)?;
let target_is_directory = match bound[2] {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/pyre-interpreter/src/module/posix/interp_posix.rs` around lines 9344 -
9365, Move the dir_fd_kwarg validation in the symlink entry point to after both
fsencode_path_named_w conversions for src and dst. Preserve the existing
arguments and error handling so path conversions, including user-defined
__fspath__, occur before rejecting dir_fd, matching the ordering used by access
and stat_entry.

Some(w) => crate::baseobjspace::is_true(w)?,
None => false,
};
Expand Down
10 changes: 10 additions & 0 deletions pyre/pyre-interpreter/src/module/sys/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,16 @@ pub fn register_module(ns: pyre_object::PyObjectRef) {
// reads it to build USER_SITE.
#[cfg(windows)]
module_ns_store(ns, "winver", w_str_new("3.14"));
// sys.dllhandle — the handle of the DLL exporting the Python C API,
// published beside `winver` because both come from the same `MS_COREDLL`
// block. `ctypes/__init__.py:562` builds `pythonapi` out of it with no
// import guard, so a missing attribute is an AttributeError out of `import
// ctypes`. `vm.py:301 get_dllhandle` answers 0 for a build without
// `cpyext`, and there is no cpyext here, so 0 is the whole answer: the
// handle names an API this interpreter does not export, and reporting the
// executable's own module would only make `pythonapi` fail one call later.
#[cfg(windows)]
module_ns_store(ns, "dllhandle", w_int_new(0));
// sys._vpath — the build's relative path from the executable's directory to
// the prefix. `sysconfig._init_config_vars` subscripts it under `os.name ==
// 'nt'`, so it is an AttributeError out of the first `get_config_var` call
Expand Down
Loading