ext_php_rs/zend/
error_observer.rs1use std::sync::OnceLock;
28
29use crate::ffi;
30pub use crate::flags::ErrorType;
31
32#[derive(Debug, Clone)]
34pub struct BacktraceFrame {
35 pub function: Option<String>,
37 pub class: Option<String>,
39 pub file: Option<String>,
41 pub line: u32,
43}
44
45impl BacktraceFrame {
46 pub(crate) unsafe fn from_execute_data(
50 execute_data: *const ffi::zend_execute_data,
51 ) -> Option<Self> {
52 if execute_data.is_null() {
53 return None;
54 }
55
56 let ex = unsafe { &*execute_data };
57 let func = ex.func;
58 if func.is_null() {
59 return Some(Self {
60 function: None,
61 class: None,
62 file: None,
63 line: 0,
64 });
65 }
66
67 let common = unsafe { &(*func).common };
68
69 let function = if common.function_name.is_null() {
70 None
71 } else {
72 unsafe { zend_string_to_string(common.function_name) }
73 };
74
75 let class = if common.scope.is_null() {
76 None
77 } else {
78 let ce = unsafe { &*common.scope };
79 if ce.name.is_null() {
80 None
81 } else {
82 unsafe { zend_string_to_string(ce.name) }
83 }
84 };
85
86 #[allow(clippy::cast_possible_truncation)]
87 let is_internal = common.type_ == ffi::ZEND_INTERNAL_FUNCTION as u8;
88
89 let (file, line) = if is_internal {
90 (None, ex.opline as u32)
91 } else {
92 let op_array = unsafe { &(*func).op_array };
93 let file = if op_array.filename.is_null() {
94 None
95 } else {
96 unsafe { zend_string_to_string(op_array.filename) }
97 };
98 let line = if ex.opline.is_null() {
100 op_array.line_start
101 } else {
102 unsafe { (*ex.opline).lineno }
103 };
104 (file, line)
105 };
106
107 Some(Self {
108 function,
109 class,
110 file,
111 line,
112 })
113 }
114}
115
116#[derive(Debug)]
118pub struct ErrorInfo<'a> {
119 pub error_type: ErrorType,
121 pub filename: Option<&'a str>,
123 pub lineno: u32,
125 pub message: &'a str,
127}
128
129impl ErrorInfo<'_> {
130 #[must_use]
150 pub fn backtrace(&self) -> Option<Vec<BacktraceFrame>> {
151 let eg = unsafe { crate::ffi::ext_php_rs_executor_globals().as_ref()? };
152 let mut execute_data = eg.current_execute_data;
153
154 let mut frames = Vec::new();
155 while !execute_data.is_null() {
156 if let Some(frame) = unsafe { BacktraceFrame::from_execute_data(execute_data) } {
157 frames.push(frame);
158 }
159 execute_data = unsafe { (*execute_data).prev_execute_data };
160 }
161
162 if frames.is_empty() {
163 None
164 } else {
165 Some(frames)
166 }
167 }
168}
169
170pub trait ErrorObserver: 'static {
202 fn should_observe(&self, error_type: ErrorType) -> bool;
207
208 fn on_error(&self, error: &ErrorInfo);
210}
211
212type ErrorObserverFactory = Box<dyn Fn() -> Box<dyn ErrorObserver + Send + Sync> + Send + Sync>;
213
214static ERROR_OBSERVER_FACTORY: OnceLock<ErrorObserverFactory> = OnceLock::new();
215static ERROR_OBSERVER_INSTANCE: OnceLock<Box<dyn ErrorObserver + Send + Sync>> = OnceLock::new();
216
217fn get_error_observer() -> Option<&'static (dyn ErrorObserver + Send + Sync)> {
218 ERROR_OBSERVER_INSTANCE
219 .get()
220 .map(std::convert::AsRef::as_ref)
221}
222
223unsafe fn zend_string_to_str(zs: *mut ffi::zend_string) -> Option<&'static str> {
227 if zs.is_null() {
228 return None;
229 }
230 let len = unsafe { (*zs).len };
231 let ptr = unsafe { (*zs).val.as_ptr() };
232 let slice = unsafe { std::slice::from_raw_parts(ptr.cast::<u8>(), len) };
233 std::str::from_utf8(slice).ok()
234}
235
236unsafe fn zend_string_to_string(zs: *mut ffi::zend_string) -> Option<String> {
240 unsafe { zend_string_to_str(zs) }.map(String::from)
241}
242
243unsafe extern "C" fn error_observer_callback(
247 error_type: std::ffi::c_int,
248 error_filename: *mut ffi::zend_string,
249 error_lineno: u32,
250 message: *mut ffi::zend_string,
251) {
252 let Some(observer) = get_error_observer() else {
253 return;
254 };
255
256 let error_type = ErrorType::from_bits_truncate(error_type.cast_unsigned());
257
258 if !observer.should_observe(error_type) {
259 return;
260 }
261
262 let filename = unsafe { zend_string_to_str(error_filename) };
263 let message_str = unsafe { zend_string_to_str(message) }.unwrap_or("");
264
265 let info = ErrorInfo {
266 error_type,
267 filename,
268 lineno: error_lineno,
269 message: message_str,
270 };
271
272 observer.on_error(&info);
273}
274
275pub(crate) fn register_error_observer_factory(factory: ErrorObserverFactory) {
279 assert!(
280 ERROR_OBSERVER_FACTORY.set(factory).is_ok(),
281 "error_observer can only be registered once per extension"
282 );
283}
284
285pub(crate) unsafe fn error_observer_startup() {
289 let Some(factory) = ERROR_OBSERVER_FACTORY.get() else {
290 return;
291 };
292 ERROR_OBSERVER_INSTANCE.get_or_init(factory);
293 unsafe { ffi::zend_observer_error_register(Some(error_observer_callback)) };
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299
300 struct TestErrorObserver {
301 observe_all: bool,
302 }
303
304 unsafe impl Send for TestErrorObserver {}
305 unsafe impl Sync for TestErrorObserver {}
306
307 impl ErrorObserver for TestErrorObserver {
308 fn should_observe(&self, _error_type: ErrorType) -> bool {
309 self.observe_all
310 }
311
312 fn on_error(&self, _error: &ErrorInfo) {}
313 }
314
315 #[test]
316 fn test_error_type_bitflags() {
317 assert!(ErrorType::Fatal.contains(ErrorType::Error));
318 assert!(ErrorType::Fatal.contains(ErrorType::Parse));
319 assert!(!ErrorType::Fatal.contains(ErrorType::Warning));
320 assert!(!ErrorType::Fatal.contains(ErrorType::Notice));
321 }
322
323 #[test]
324 fn test_error_type_all() {
325 assert!(ErrorType::All.contains(ErrorType::Error));
326 assert!(ErrorType::All.contains(ErrorType::Warning));
327 assert!(ErrorType::All.contains(ErrorType::Notice));
328 assert!(ErrorType::All.contains(ErrorType::Deprecated));
329 }
330
331 #[test]
332 #[allow(deprecated)]
333 fn test_error_type_all_matches_php_e_all() {
334 assert_eq!(ErrorType::All.contains(ErrorType::Strict), !cfg!(php84));
335 }
336
337 #[test]
338 fn test_observer_trait_impl() {
339 let observer = TestErrorObserver { observe_all: true };
340 assert!(observer.should_observe(ErrorType::Error));
341
342 let observer = TestErrorObserver { observe_all: false };
343 assert!(!observer.should_observe(ErrorType::Error));
344 }
345
346 #[test]
347 fn test_error_type_from_bits() {
348 let error_type = ErrorType::from_bits_truncate(1); assert_eq!(error_type, ErrorType::Error);
350
351 let error_type = ErrorType::from_bits_truncate(2); assert_eq!(error_type, ErrorType::Warning);
353
354 let error_type = ErrorType::from_bits_truncate(3); assert!(error_type.contains(ErrorType::Error));
356 assert!(error_type.contains(ErrorType::Warning));
357 }
358}