@@ -336,18 +336,18 @@ impl<'py> Bound<'py, PyAny> {
336336 ///
337337 /// # Safety
338338 ///
339- /// - `ptr` must be a valid pointer to a Python object
339+ /// - `ptr` must be a valid pointer to a Python object (or null, which will cause a panic)
340340 /// - `ptr` must be an owned Python reference, as the `Bound<'py, PyAny>` will assume ownership
341+ ///
342+ /// # Panics
343+ ///
344+ /// Panics if `ptr` is null.
341345 #[ inline]
342346 #[ track_caller]
343347 pub unsafe fn from_owned_ptr ( py : Python < ' py > , ptr : * mut ffi:: PyObject ) -> Self {
344- match NonNull :: new ( ptr) {
345- Some ( nonnull_ptr) => {
346- // SAFETY: caller has upheld the safety contract, ptr is known to be non-null
347- unsafe { Py :: from_non_null ( nonnull_ptr) } . into_bound ( py)
348- }
349- None => crate :: err:: panic_after_error ( py) ,
350- }
348+ let non_null = NonNull :: new ( ptr) . unwrap_or_else ( || panic_on_null ( py) ) ;
349+ // SAFETY: caller has upheld the safety contract, ptr is known to be non-null
350+ unsafe { Py :: from_non_null ( non_null) } . into_bound ( py)
351351 }
352352
353353 /// Constructs a new `Bound<'py, PyAny>` from a pointer. Returns `None` if `ptr` is null.
@@ -405,16 +405,16 @@ impl<'py> Bound<'py, PyAny> {
405405 /// # Safety
406406 ///
407407 /// - `ptr` must be a valid pointer to a Python object
408+ ///
409+ /// # Panics
410+ ///
411+ /// Panics if `ptr` is null
408412 #[ inline]
409413 #[ track_caller]
410414 pub unsafe fn from_borrowed_ptr ( py : Python < ' py > , ptr : * mut ffi:: PyObject ) -> Self {
411- match NonNull :: new ( ptr) {
412- Some ( nonnull_ptr) => {
413- // SAFETY: caller has upheld the safety contract, ptr is known to be non-null
414- unsafe { Py :: from_borrowed_non_null ( py, nonnull_ptr) } . into_bound ( py)
415- }
416- None => crate :: err:: panic_after_error ( py) ,
417- }
415+ let non_null = NonNull :: new ( ptr) . unwrap_or_else ( || panic_on_null ( py) ) ;
416+ // SAFETY: caller has upheld the safety contract, ptr is known to be non-null
417+ unsafe { Py :: from_borrowed_non_null ( py, non_null) } . into_bound ( py)
418418 }
419419
420420 /// Constructs a new `Bound<'py, PyAny>` from a pointer by creating a new Python reference.
@@ -1095,14 +1095,18 @@ impl<'a, 'py> Borrowed<'a, 'py, PyAny> {
10951095 ///
10961096 /// # Safety
10971097 ///
1098- /// - `ptr` must be a valid pointer to a Python object
1098+ /// - `ptr` must be a valid pointer to a Python object (or null, which will cause a panic)
10991099 /// - similar to `std::slice::from_raw_parts`, the lifetime `'a` is completely defined by
11001100 /// the caller and it is the caller's responsibility to ensure that the reference this is
11011101 /// derived from is valid for the lifetime `'a`.
1102+ ///
1103+ /// # Panics
1104+ ///
1105+ /// Panics if `ptr` is null
11021106 #[ inline]
11031107 #[ track_caller]
11041108 pub unsafe fn from_ptr ( py : Python < ' py > , ptr : * mut ffi:: PyObject ) -> Self {
1105- let non_null = NonNull :: new ( ptr) . unwrap_or_else ( || crate :: err :: panic_after_error ( py) ) ;
1109+ let non_null = NonNull :: new ( ptr) . unwrap_or_else ( || panic_on_null ( py) ) ;
11061110 // SAFETY: caller has upheld the safety contract
11071111 unsafe { Self :: from_non_null ( py, non_null) }
11081112 }
@@ -1965,12 +1969,12 @@ impl<T> Py<T> {
19651969 /// Create a `Py<T>` instance by taking ownership of the given FFI pointer.
19661970 ///
19671971 /// # Safety
1968- /// `ptr` must be a pointer to a Python object of type T.
19691972 ///
1970- /// Callers must own the object referred to by `ptr`, as this function
1971- /// implicitly takes ownership of that object.
1973+ /// - `ptr` must be a valid pointer to a Python object (or null, which will cause a panic)
1974+ /// - `ptr` must be an owned Python reference, as the `Py<T>` will assume ownership
19721975 ///
19731976 /// # Panics
1977+ ///
19741978 /// Panics if `ptr` is null.
19751979 #[ inline]
19761980 #[ track_caller]
@@ -1981,7 +1985,7 @@ impl<T> Py<T> {
19811985 // SAFETY: caller has upheld the safety contract, ptr is known to be non-null
19821986 unsafe { Self :: from_non_null ( nonnull_ptr) }
19831987 }
1984- None => crate :: err :: panic_after_error ( py) ,
1988+ None => panic_on_null ( py) ,
19851989 }
19861990 }
19871991
@@ -1990,7 +1994,9 @@ impl<T> Py<T> {
19901994 /// If `ptr` is null then the current Python exception is fetched as a [`PyErr`].
19911995 ///
19921996 /// # Safety
1993- /// If non-null, `ptr` must be a pointer to a Python object of type T.
1997+ ///
1998+ /// - `ptr` must be a valid pointer to a Python object, or null
1999+ /// - a non-null `ptr` must be an owned Python reference, as the `Py<T>` will assume ownership
19942000 #[ inline]
19952001 #[ deprecated( note = "use `Bound::from_owned_ptr_or_err` instead" , since = "0.28.0" ) ]
19962002 pub unsafe fn from_owned_ptr_or_err (
@@ -2011,7 +2017,9 @@ impl<T> Py<T> {
20112017 /// If `ptr` is null then `None` is returned.
20122018 ///
20132019 /// # Safety
2014- /// If non-null, `ptr` must be a pointer to a Python object of type T.
2020+ ///
2021+ /// - `ptr` must be a valid pointer to a Python object, or null
2022+ /// - a non-null `ptr` must be an owned Python reference, as the `Py<T>` will assume ownership
20152023 #[ inline]
20162024 #[ deprecated( note = "use `Bound::from_owned_ptr_or_opt` instead" , since = "0.28.0" ) ]
20172025 pub unsafe fn from_owned_ptr_or_opt ( _py : Python < ' _ > , ptr : * mut ffi:: PyObject ) -> Option < Self > {
@@ -2027,15 +2035,15 @@ impl<T> Py<T> {
20272035 /// `ptr` must be a pointer to a Python object of type T.
20282036 ///
20292037 /// # Panics
2038+ ///
20302039 /// Panics if `ptr` is null.
20312040 #[ inline]
20322041 #[ track_caller]
20332042 #[ deprecated( note = "use `Borrowed::from_borrowed_ptr` instead" , since = "0.28.0" ) ]
20342043 pub unsafe fn from_borrowed_ptr ( py : Python < ' _ > , ptr : * mut ffi:: PyObject ) -> Py < T > {
20352044 // SAFETY: caller has upheld the safety contract
20362045 #[ allow( deprecated) ]
2037- unsafe { Self :: from_borrowed_ptr_or_opt ( py, ptr) }
2038- . unwrap_or_else ( || crate :: err:: panic_after_error ( py) )
2046+ unsafe { Self :: from_borrowed_ptr_or_opt ( py, ptr) } . unwrap_or_else ( || panic_on_null ( py) )
20392047 }
20402048
20412049 /// Create a `Py<T>` instance by creating a new reference from the given FFI pointer.
@@ -2437,10 +2445,23 @@ impl<T> Py<T> {
24372445 }
24382446}
24392447
2448+ #[ track_caller]
2449+ #[ cold]
2450+ fn panic_on_null ( py : Python < ' _ > ) -> ! {
2451+ if let Some ( err) = PyErr :: take ( py) {
2452+ err. write_unraisable ( py, None ) ;
2453+ }
2454+ panic ! ( "PyObject pointer is null" ) ;
2455+ }
2456+
24402457#[ cfg( test) ]
24412458mod tests {
24422459 use super :: { Bound , IntoPyObject , Py } ;
2460+ #[ cfg( all( feature = "macros" , Py_3_8 ) ) ]
2461+ use crate :: exceptions:: PyValueError ;
24432462 use crate :: test_utils:: generate_unique_module_name;
2463+ #[ cfg( all( feature = "macros" , Py_3_8 ) ) ]
2464+ use crate :: test_utils:: UnraisableCapture ;
24442465 use crate :: types:: { dict:: IntoPyDict , PyAnyMethods , PyCapsule , PyDict , PyString } ;
24452466 use crate :: { ffi, Borrowed , IntoPyObjectExt , PyAny , PyResult , Python } ;
24462467 use std:: ffi:: CStr ;
@@ -2797,6 +2818,61 @@ a = A()
27972818 } ) ;
27982819 }
27992820
2821+ #[ cfg( all( feature = "macros" , Py_3_8 ) ) ]
2822+ #[ test]
2823+ fn test_constructors_panic_on_null ( ) {
2824+ Python :: attach ( |py| {
2825+ const NULL : * mut ffi:: PyObject = std:: ptr:: null_mut ( ) ;
2826+
2827+ #[ expect( deprecated, reason = "Py<T> constructors" ) ]
2828+ // SAFETY: calling all constructors with null pointer to test panic behavior
2829+ for constructor in unsafe {
2830+ [
2831+ ( |py| {
2832+ Py :: < PyAny > :: from_owned_ptr ( py, NULL ) ;
2833+ } ) as fn ( Python < ' _ > ) ,
2834+ ( |py| {
2835+ Py :: < PyAny > :: from_borrowed_ptr ( py, NULL ) ;
2836+ } ) as fn ( Python < ' _ > ) ,
2837+ ( |py| {
2838+ Bound :: from_owned_ptr ( py, NULL ) ;
2839+ } ) as fn ( Python < ' _ > ) ,
2840+ ( |py| {
2841+ Bound :: from_borrowed_ptr ( py, NULL ) ;
2842+ } ) as fn ( Python < ' _ > ) ,
2843+ ( |py| {
2844+ Borrowed :: from_ptr ( py, NULL ) ;
2845+ } ) as fn ( Python < ' _ > ) ,
2846+ ]
2847+ } {
2848+ UnraisableCapture :: enter ( py, |capture| {
2849+ // panic without exception set, no unraisable hook called
2850+ let result = std:: panic:: catch_unwind ( || {
2851+ constructor ( py) ;
2852+ } ) ;
2853+ assert_eq ! (
2854+ result. unwrap_err( ) . downcast_ref:: <& str >( ) ,
2855+ Some ( & "PyObject pointer is null" )
2856+ ) ;
2857+ assert ! ( capture. take_capture( ) . is_none( ) ) ;
2858+
2859+ // set an exception, panic, unraisable hook called
2860+ PyValueError :: new_err ( "error" ) . restore ( py) ;
2861+ let result = std:: panic:: catch_unwind ( || {
2862+ constructor ( py) ;
2863+ } ) ;
2864+ assert_eq ! (
2865+ result. unwrap_err( ) . downcast_ref:: <& str >( ) ,
2866+ Some ( & "PyObject pointer is null" )
2867+ ) ;
2868+ assert ! ( capture. take_capture( ) . is_some_and( |( err, obj) | {
2869+ err. is_instance_of:: <PyValueError >( py) && obj. is_none( )
2870+ } ) ) ;
2871+ } ) ;
2872+ }
2873+ } ) ;
2874+ }
2875+
28002876 #[ cfg( feature = "macros" ) ]
28012877 mod using_macros {
28022878 use super :: * ;
0 commit comments