diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c48bd99a1..8f8783da1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -77,6 +77,7 @@ jobs: ':examples/**' \ ':benches/**' \ ':example_projects/**' \ + ':extra_tests/snippets/**' \ ':.github/workflows/ci.yaml' \ ; then echo "changed=false" >> "$GITHUB_OUTPUT" @@ -1009,10 +1010,71 @@ jobs: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true RUN_WASM_WASI: ${{ needs.determine_changes.outputs.rust_code == 'true' || github.ref == 'refs/heads/main' }} - # Passing snippets under wasm32-wasip1. - WASM_WASI_SNIPPETS: >- - stdlib_random - stdlib_time + # extra_tests/snippets files that still fail or hang under wasm32-wasip1; + # every other snippet is discovered and run automatically. + WASM_WASI_SNIPPET_EXCLUDE: >- + builtin_bytearray + builtin_bytes + builtin_hash + builtin_int + builtin_iter + builtin_list + builtin_map + builtin_memoryview + builtin_open + builtin_posixshmem + builtin_slice + builtin_str + builtin_thread + cli_int_max_str_digits + protocol_index_argument + recursion + stdlib_ast + stdlib_asyncio + stdlib_collections_deque + stdlib_contextvars + stdlib_ctypes + stdlib_ctypes_byvalue + stdlib_ctypes_calls + stdlib_ctypes_from_buffer + stdlib_genericalias + stdlib_io + stdlib_io_blocking_buffer + stdlib_itertools + stdlib_json + stdlib_mmap + stdlib_multiprocessing_condition + stdlib_multiprocessing_fork_sharer + stdlib_multiprocessing_sharedctypes + stdlib_os + stdlib_pwd + stdlib_queue_signal + stdlib_select + stdlib_socket + stdlib_sqlite + stdlib_ssl + stdlib_ssl_short_recv + stdlib_struct + stdlib_subinterpreters + stdlib_subprocess + stdlib_sys + stdlib_sys_audit + stdlib_threading_current_frames + stdlib_threading_gc_frame_race + stdlib_threading_gc_import + stdlib_threading_generator + stdlib_threading_set_repr + stdlib_threading_type_cache + stdlib_thread_native_id + stdlib_types + stdlib_typing + stdlib_urllib_https_misaligned_recv + stdlib_zoneinfo_cache + syntax_async + syntax_async_comprehension + syntax_deep_nesting + vm_frame_locals + xfail_assert # Passing Lib/test files under wasm32-wasip1 WASM_WASI_TEST_FILES: >- test_int @@ -1075,9 +1137,14 @@ jobs: - name: run snippets if: env.RUN_WASM_WASI == 'true' run: | - for name in $WASM_WASI_SNIPPETS; do - echo "::group::extra_tests/snippets/${name}.py" - wasmer run --volume "$(pwd)" target/wasm32-wasip1/wasm-release/rustpython.wasm -- "$(pwd)/extra_tests/snippets/${name}.py" + exclude=" $WASM_WASI_SNIPPET_EXCLUDE " + for path in extra_tests/snippets/*.py; do + name=$(basename "$path" .py) + case "$exclude" in + *" $name "*) continue ;; + esac + echo "::group::${path}" + wasmer run --volume "$(pwd)" target/wasm32-wasip1/wasm-release/rustpython.wasm -- "$(pwd)/${path}" echo "::endgroup::" done - name: run cpython unittest diff --git a/crates/stdlib/src/multiprocessing.rs b/crates/stdlib/src/multiprocessing.rs index c992f90f7..c9a079039 100644 --- a/crates/stdlib/src/multiprocessing.rs +++ b/crates/stdlib/src/multiprocessing.rs @@ -408,19 +408,22 @@ mod _multiprocessing { #[pyfunction] fn recv(socket: usize, size: usize, vm: &VirtualMachine) -> PyResult> { - let mut buf = - host_multiprocessing::recv_socket(socket as host_multiprocessing::RawSocket, size) - .map_err(|_| vm.new_last_os_error())?; + let mut buf = vm + .allow_threads(|| { + host_multiprocessing::recv_socket(socket as host_multiprocessing::RawSocket, size) + }) + .map_err(|error| error.to_pyexception(vm))?; buf.shrink_to_fit(); Ok(buf) } #[pyfunction] fn send(socket: usize, buf: ArgBytesLike, vm: &VirtualMachine) -> PyResult { - buf.with_ref(|b| { - host_multiprocessing::send_socket(socket as host_multiprocessing::RawSocket, b) + let buf = buf.borrow_buf_unlocked(vm)?; + vm.allow_threads(|| { + host_multiprocessing::send_socket(socket as host_multiprocessing::RawSocket, &buf) }) - .map_err(|_| vm.new_last_os_error()) + .map_err(|error| error.to_pyexception(vm)) } } diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index d22d6ef74..8dacc13d3 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -2904,15 +2904,16 @@ where T::Base: core::fmt::Debug, { /// Converts this reference to the base type (ownership transfer). - /// # Safety - /// T and T::Base must have compatible layouts in size_of::() bytes. #[inline] #[must_use] pub fn into_base(self) -> PyRef { - let obj: PyObjectRef = self.into(); - match obj.downcast() { - Ok(base_ref) => base_ref, - Err(_) => unsafe { core::hint::unreachable_unchecked() }, + // PySubclass establishes the physical prefix; Python may have changed + // the object's class or MRO since this typed reference was constructed. + let ptr = self.ptr.cast(); + core::mem::forget(self); + PyRef { + ptr, + marker: PhantomData, } } #[inline] @@ -2934,9 +2935,8 @@ impl Py { /// Converts `&Py` to `&Py`. #[inline] pub fn to_base(&self) -> &Py { - debug_assert!(self.as_object().downcast_ref::().is_some()); - // SAFETY: T is #[repr(transparent)] over T::Base, - // so Py and Py have the same layout. + // SAFETY: PySubclass guarantees a valid base prefix with matching + // payload offsets and compatible object alignment. unsafe { &*(self as *const Self as *const Py) } } @@ -3410,6 +3410,37 @@ mod tests { } } + #[test] + fn native_base_conversions_ignore_python_mro() { + crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let class = vm.ctx.new_class( + None, + "HiddenOSErrorBase", + vm.ctx.exceptions.os_error.to_owned(), + Default::default(), + ); + let marker: PyObjectRef = vm.ctx.new_int(42).into(); + let error = vm.new_os_subtype_error(class.clone(), None, marker.clone()); + // A custom MRO can omit native bases without changing the allocation. + *class.mro.write() = vec![class.clone(), vm.ctx.types.object_type.to_owned()]; + assert!( + !error + .as_object() + .downcastable::() + ); + + assert!(error.to_base().to_base().get_arg(0).unwrap().is(&marker)); + assert!( + error + .into_base() + .into_base() + .get_arg(0) + .unwrap() + .is(&marker) + ); + }); + } + #[test] fn native_type_basicsize_includes_payload_padding() { use crate::class::PyClassDef;