• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

davidcole1340 / ext-php-rs / 15778794992

20 Jun 2025 12:19PM UTC coverage: 20.64% (-1.4%) from 22.034%
15778794992

Pull #463

github

web-flow
Merge b618ded48 into 660f308c0
Pull Request #463: feat(cargo-php): --features, --all-features, --no-default-features

0 of 11 new or added lines in 1 file covered. (0.0%)

52 existing lines in 10 files now uncovered.

761 of 3687 relevant lines covered (20.64%)

3.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.0
/src/zend/try_catch.rs
1
use crate::ffi::{
2
    ext_php_rs_zend_bailout, ext_php_rs_zend_first_try_catch, ext_php_rs_zend_try_catch,
3
};
4
use std::ffi::c_void;
5
use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe};
6
use std::ptr::null_mut;
7

8
/// Error returned when a bailout occurs
9
#[derive(Debug)]
10
pub struct CatchError;
11

12
pub(crate) unsafe extern "C" fn panic_wrapper<R, F: FnMut() -> R + RefUnwindSafe>(
73✔
13
    ctx: *const c_void,
14
) -> *const c_void {
15
    // we try to catch panic here so we correctly shutdown php if it happens
16
    // mandatory when we do assert on test as other test would not run correctly
17
    let panic = catch_unwind(|| (*(ctx as *mut F))());
219✔
18

19
    Box::into_raw(Box::new(panic)).cast::<c_void>()
219✔
20
}
21

22
/// PHP proposes a try catch mechanism in C using setjmp and longjmp (bailout)
23
/// It stores the arg of setjmp into the bailout field of the global executor
24
/// If a bailout is triggered, the executor will jump to the setjmp and restore
25
/// the previous setjmp
26
///
27
/// [`try_catch`] allows to use this mechanism
28
///
29
/// # Returns
30
///
31
/// * The result of the function
32
///
33
/// # Errors
34
///
35
/// * [`CatchError`] - A bailout occurred during the execution
36
pub fn try_catch<R, F: FnMut() -> R + RefUnwindSafe>(func: F) -> Result<R, CatchError> {
19✔
37
    do_try_catch(func, false)
38✔
38
}
39

40
/// PHP proposes a try catch mechanism in C using setjmp and longjmp (bailout)
41
/// It stores the arg of setjmp into the bailout field of the global executor
42
/// If a bailout is triggered, the executor will jump to the setjmp and restore
43
/// the previous setjmp
44
///
45
/// [`try_catch_first`] allows to use this mechanism
46
///
47
/// This functions differs from [`try_catch`] as it also initialize the bailout
48
/// mechanism for the first time
49
///
50
/// # Returns
51
///
52
/// * The result of the function
53
///
54
/// # Errors
55
///
56
/// * [`CatchError`] - A bailout occurred during the execution
57
pub fn try_catch_first<R, F: FnMut() -> R + RefUnwindSafe>(func: F) -> Result<R, CatchError> {
1✔
58
    do_try_catch(func, true)
2✔
59
}
60

61
fn do_try_catch<R, F: FnMut() -> R + RefUnwindSafe>(func: F, first: bool) -> Result<R, CatchError> {
20✔
62
    let mut panic_ptr = null_mut();
40✔
63
    let has_bailout = unsafe {
64
        if first {
20✔
65
            ext_php_rs_zend_first_try_catch(
66
                panic_wrapper::<R, F>,
1✔
67
                (&raw const func).cast::<c_void>(),
2✔
68
                &raw mut panic_ptr,
1✔
69
            )
70
        } else {
71
            ext_php_rs_zend_try_catch(
UNCOV
72
                panic_wrapper::<R, F>,
×
UNCOV
73
                (&raw const func).cast::<c_void>(),
×
UNCOV
74
                &raw mut panic_ptr,
×
75
            )
76
        }
77
    };
78

79
    let panic = panic_ptr.cast::<std::thread::Result<R>>();
60✔
80

81
    // can be null if there is a bailout
82
    if panic.is_null() || has_bailout {
57✔
83
        return Err(CatchError);
3✔
84
    }
85

UNCOV
86
    match unsafe { *Box::from_raw(panic.cast::<std::thread::Result<R>>()) } {
×
87
        Ok(r) => Ok(r),
32✔
88
        Err(err) => {
1✔
89
            // we resume the panic here so it can be caught correctly by the test framework
UNCOV
90
            resume_unwind(err);
×
91
        }
92
    }
93
}
94

95
/// Trigger a bailout
96
///
97
/// This function will stop the execution of the current script
98
/// and jump to the last try catch block
99
///
100
/// # Safety
101
///
102
/// This function is unsafe because it can cause memory leaks
103
/// Since it will jump to the last try catch block, it will not call the
104
/// destructor of the current scope
105
///
106
/// When using this function you should ensure that all the memory allocated in
107
/// the current scope is released
108
pub unsafe fn bailout() -> ! {
3✔
109
    ext_php_rs_zend_bailout();
3✔
110
}
111

112
#[cfg(feature = "embed")]
113
#[cfg(test)]
114
mod tests {
115
    use crate::embed::Embed;
116
    use crate::zend::{bailout, try_catch};
117
    use std::ptr::null_mut;
118

119
    #[test]
120
    fn test_catch() {
121
        Embed::run(|| {
122
            let catch = try_catch(|| {
123
                unsafe {
124
                    bailout();
125
                }
126

127
                #[allow(unreachable_code)]
128
                #[allow(clippy::assertions_on_constants)]
129
                {
130
                    assert!(false);
131
                }
132
            });
133

134
            assert!(catch.is_err());
135
        });
136
    }
137

138
    #[test]
139
    fn test_no_catch() {
140
        Embed::run(|| {
141
            let catch = try_catch(|| {
142
                #[allow(clippy::assertions_on_constants)]
143
                {
144
                    assert!(true);
145
                }
146
            });
147

148
            assert!(catch.is_ok());
149
        });
150
    }
151

152
    #[test]
153
    fn test_bailout() {
154
        Embed::run(|| {
155
            unsafe {
156
                bailout();
157
            }
158

159
            #[allow(unreachable_code)]
160
            #[allow(clippy::assertions_on_constants)]
161
            {
162
                assert!(false);
163
            }
164
        });
165
    }
166

167
    #[test]
168
    #[should_panic(expected = "should panic")]
169
    fn test_panic() {
170
        Embed::run(|| {
171
            let _ = try_catch(|| {
172
                panic!("should panic");
173
            });
174
        });
175
    }
176

177
    #[test]
178
    fn test_return() {
179
        let foo = Embed::run(|| {
180
            let result = try_catch(|| "foo");
181

182
            assert!(result.is_ok());
183

184
            #[allow(clippy::unwrap_used)]
185
            result.unwrap()
186
        });
187

188
        assert_eq!(foo, "foo");
189
    }
190

191
    #[test]
192
    fn test_memory_leak() {
193
        Embed::run(|| {
194
            let mut ptr = null_mut();
195

196
            let _ = try_catch(|| {
197
                let mut result = "foo".to_string();
198
                ptr = &mut result;
199

200
                unsafe {
201
                    bailout();
202
                }
203
            });
204

205
            // Check that the string is never released
206
            let result = unsafe { &*ptr as &str };
207

208
            assert_eq!(result, "foo");
209
        });
210
    }
211
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc