11use std:: cell:: UnsafeCell ;
2- use std:: ffi:: { c_char, c_int, c_void} ;
2+ use std:: ffi:: { CStr , c_char, c_int, c_void} ;
33use std:: mem:: MaybeUninit ;
44use std:: ptr;
55use std:: slice;
@@ -12,6 +12,8 @@ use cpython_sys::PyBuffer_Release;
1212use cpython_sys:: PyBytes_AsString ;
1313use cpython_sys:: PyBytes_FromStringAndSize ;
1414use cpython_sys:: PyErr_NoMemory ;
15+ use cpython_sys:: PyErr_SetNone ;
16+ use cpython_sys:: PyErr_SetObject ;
1517use cpython_sys:: PyErr_SetString ;
1618use cpython_sys:: PyExc_TypeError ;
1719use cpython_sys:: PyMethodDef ;
@@ -22,6 +24,59 @@ use cpython_sys::PyModuleDef_Init;
2224use cpython_sys:: PyObject ;
2325use cpython_sys:: PyObject_GetBuffer ;
2426
27+ // Error Handling Abstraction
28+
29+ /// Zero-sized type indicating that a Python exception has been set.
30+ /// Using this type will ensure `Result<&PyObject, ExecutedErr>` and `Result<PyRc, ExecutedErr>`
31+ /// to be same size as `*mut PyObject`.
32+ #[ derive( Debug , Clone , Copy ) ]
33+ pub struct ExecutedErr ;
34+
35+ /// Enum representing different ways to set a Python exception.
36+ ///
37+ /// This type is NOT stored in Result - it's immediately converted to
38+ /// `ExecutedErr` via `.into()`, which triggers the actual C API call.
39+ pub enum MakeErr {
40+ SetString ( * mut PyObject , * const c_char ) ,
41+ SetObject ( * mut PyObject , * mut PyObject ) ,
42+ SetNone ( * mut PyObject ) ,
43+ NoMemory ,
44+ }
45+
46+ impl MakeErr {
47+ fn execute ( self ) -> ExecutedErr {
48+ match self {
49+ MakeErr :: SetString ( exc_type, msg) => {
50+ unsafe { PyErr_SetString ( exc_type, msg) } ;
51+ }
52+ MakeErr :: SetObject ( exc_type, value) => {
53+ unsafe { PyErr_SetObject ( exc_type, value) } ;
54+ }
55+ MakeErr :: SetNone ( exc_type) => {
56+ unsafe { PyErr_SetNone ( exc_type) } ;
57+ }
58+ MakeErr :: NoMemory => {
59+ unsafe { PyErr_NoMemory ( ) } ;
60+ }
61+ }
62+ ExecutedErr
63+ }
64+
65+ #[ inline]
66+ pub fn type_error ( msg : & CStr ) -> Self {
67+ Self :: SetString ( unsafe { PyExc_TypeError } , msg. as_ptr ( ) )
68+ }
69+ }
70+
71+ impl From < MakeErr > for ExecutedErr {
72+ #[ inline]
73+ fn from ( exc : MakeErr ) -> Self {
74+ exc. execute ( )
75+ }
76+ }
77+
78+ pub type PyResult < T > = Result < T , ExecutedErr > ;
79+
2580const PYBUF_SIMPLE : c_int = 0 ;
2681const PAD_BYTE : u8 = b'=' ;
2782const ENCODE_TABLE : [ u8 ; 64 ] = * b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ;
@@ -82,11 +137,12 @@ struct BorrowedBuffer {
82137}
83138
84139impl BorrowedBuffer {
85- fn from_object ( obj : & PyObject ) -> Result < Self , ( ) > {
140+ fn from_object ( obj : & PyObject ) -> PyResult < Self > {
86141 let mut view = MaybeUninit :: < Py_buffer > :: uninit ( ) ;
87142 let buffer = unsafe {
88143 if PyObject_GetBuffer ( obj. as_raw ( ) , view. as_mut_ptr ( ) , PYBUF_SIMPLE ) != 0 {
89- return Err ( ( ) ) ;
144+ // PyObject_GetBuffer already set the exception
145+ return Err ( ExecutedErr ) ;
90146 }
91147 Self {
92148 view : view. assume_init ( ) ,
@@ -122,12 +178,7 @@ pub unsafe extern "C" fn standard_b64encode(
122178 nargs : Py_ssize_t ,
123179) -> * mut PyObject {
124180 if nargs != 1 {
125- unsafe {
126- PyErr_SetString (
127- PyExc_TypeError ,
128- c"standard_b64encode() takes exactly one argument" . as_ptr ( ) ,
129- ) ;
130- }
181+ MakeErr :: type_error ( c"standard_b64encode() takes exactly one argument" ) . execute ( ) ;
131182 return ptr:: null_mut ( ) ;
132183 }
133184
@@ -140,51 +191,40 @@ pub unsafe extern "C" fn standard_b64encode(
140191 }
141192}
142193
143- fn standard_b64encode_impl ( source : & PyObject ) -> Result < * mut PyObject , ( ) > {
144- let buffer = match BorrowedBuffer :: from_object ( source) {
145- Ok ( buf) => buf,
146- Err ( _) => return Err ( ( ) ) ,
147- } ;
194+ fn standard_b64encode_impl ( source : & PyObject ) -> PyResult < * mut PyObject > {
195+ let buffer = BorrowedBuffer :: from_object ( source) ?;
148196
149197 let view_len = buffer. len ( ) ;
150198 if view_len < 0 {
151- unsafe {
152- PyErr_SetString (
153- PyExc_TypeError ,
154- c"standard_b64encode() argument has negative length" . as_ptr ( ) ,
155- ) ;
156- }
157- return Err ( ( ) ) ;
199+ return Err (
200+ MakeErr :: type_error ( c"standard_b64encode() argument has negative length" ) . into ( ) ,
201+ ) ;
158202 }
159203
160204 let input_len = view_len as usize ;
161205 let input = unsafe { slice:: from_raw_parts ( buffer. as_ptr ( ) , input_len) } ;
162206
163207 let Some ( output_len) = encoded_output_len ( input_len) else {
164- unsafe {
165- PyErr_NoMemory ( ) ;
166- }
167- return Err ( ( ) ) ;
208+ return Err ( MakeErr :: NoMemory . into ( ) ) ;
168209 } ;
169210
170211 if output_len > isize:: MAX as usize {
171- unsafe {
172- PyErr_NoMemory ( ) ;
173- }
174- return Err ( ( ) ) ;
212+ return Err ( MakeErr :: NoMemory . into ( ) ) ;
175213 }
176214
177215 let result = unsafe { PyBytes_FromStringAndSize ( ptr:: null ( ) , output_len as Py_ssize_t ) } ;
178216 if result. is_null ( ) {
179- return Err ( ( ) ) ;
217+ // PyBytes_FromStringAndSize already set the exception
218+ return Err ( ExecutedErr ) ;
180219 }
181220
182221 let dest_ptr = unsafe { PyBytes_AsString ( result) } ;
183222 if dest_ptr. is_null ( ) {
184223 unsafe {
185224 Py_DecRef ( result) ;
186225 }
187- return Err ( ( ) ) ;
226+ // PyBytes_AsString already set the exception
227+ return Err ( ExecutedErr ) ;
188228 }
189229 let dest = unsafe { slice:: from_raw_parts_mut ( dest_ptr. cast :: < u8 > ( ) , output_len) } ;
190230
0 commit comments