aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/util/error_handler.rs
blob: 2f8c69935a23602c56046dce5ae9ed5009fd1e50 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//! ref: composer/src/Composer/Util/ErrorHandler.php

use std::sync::{Mutex, OnceLock};
use shirabe_php_shim::{
    debug_backtrace, error_reporting, filter_var, ini_get, is_resource,
    set_error_handler, E_ALL, E_DEPRECATED, E_USER_DEPRECATED, E_WARNING, E_USER_WARNING,
    FILTER_VALIDATE_BOOLEAN, PHP_EOL, STDERR, PhpMixed, ErrorException,
};
use crate::io::io_interface::IOInterface;

static IO: OnceLock<Mutex<Option<Box<dyn IOInterface + Send>>>> = OnceLock::new();
static HAS_SHOWN_DEPRECATION_NOTICE: Mutex<i64> = Mutex::new(0);

fn io() -> &'static Mutex<Option<Box<dyn IOInterface + Send>>> {
    IO.get_or_init(|| Mutex::new(None))
}

pub struct ErrorHandler;

impl ErrorHandler {
    pub fn handle(level: i64, message: String, file: String, line: i64) -> Result<bool, ErrorException> {
        let is_deprecation_notice = level == E_DEPRECATED || level == E_USER_DEPRECATED;

        // error code is not included in error_reporting
        if !is_deprecation_notice && 0 == (error_reporting(None) & level) {
            return Ok(true);
        }

        let mut message = message;

        let xdebug_scream = ini_get("xdebug.scream").unwrap_or_default();
        if filter_var(&xdebug_scream, FILTER_VALIDATE_BOOLEAN) {
            message += "\n\nWarning: You have xdebug.scream enabled, the warning above may be\na legitimately suppressed error that you were not supposed to see.";
        }

        if !is_deprecation_notice {
            // ignore some newly introduced warnings in new php versions until dependencies
            // can be fixed as we do not want to abort execution for those
            if (level == E_WARNING || level == E_USER_WARNING)
                && message.contains("should either be used or intentionally ignored by casting it as (void)")
            {
                Self::output_warning(
                    &format!("Ignored new PHP warning but it should be reported and fixed: {} in {}:{}", message, file, line),
                    true,
                );
                return Ok(true);
            }

            return Err(ErrorException {
                message,
                code: 0,
                severity: level,
                filename: file,
                lineno: line,
            });
        }

        let io_guard = io().lock().unwrap();
        if io_guard.is_some() {
            let has_shown = *HAS_SHOWN_DEPRECATION_NOTICE.lock().unwrap();
            if has_shown > 0 && !io_guard.as_ref().unwrap().is_verbose() {
                if has_shown == 1 {
                    io_guard.as_ref().unwrap().write_error("<warning>More deprecation notices were hidden, run again with `-v` to show them.</warning>");
                    *HAS_SHOWN_DEPRECATION_NOTICE.lock().unwrap() = 2;
                }
                return Ok(true);
            }
            *HAS_SHOWN_DEPRECATION_NOTICE.lock().unwrap() = 1;
            drop(io_guard);
            Self::output_warning(&format!("Deprecation Notice: {} in {}:{}", message, file, line), false);
        }

        Ok(true)
    }

    pub fn register(io: Option<Box<dyn IOInterface + Send>>) {
        set_error_handler(|level, message, file, line| {
            Self::handle(level, message.to_string(), file.to_string(), line).unwrap_or(true)
        });
        error_reporting(Some(E_ALL));
        *self::io().lock().unwrap() = io;
    }

    fn output_warning(message: &str, output_even_without_io: bool) {
        let io_guard = io().lock().unwrap();
        if let Some(ref io) = *io_guard {
            io.write_error(&format!("<warning>{}</warning>", message));
            if io.is_verbose() {
                io.write_error("<warning>Stack trace:</warning>");
                let frames: Vec<String> = debug_backtrace()
                    .into_iter()
                    .skip(2)
                    .filter_map(|frame| {
                        let line = frame.get("line").and_then(|v| v.as_int());
                        let file = frame.get("file").and_then(|v| v.as_string()).map(|s| s.to_string());
                        if let (Some(line), Some(file)) = (line, file) {
                            Some(format!("<warning> {}:{}</warning>", file, line))
                        } else {
                            None
                        }
                    })
                    .collect();
                for frame_str in frames {
                    io.write_error(&frame_str);
                }
            }
            return;
        }
        drop(io_guard);

        if output_even_without_io {
            if is_resource(&PhpMixed::Int(STDERR)) {
                shirabe_php_shim::fwrite(PhpMixed::Int(STDERR), &format!("Warning: {}{}", message, PHP_EOL), -1);
            } else {
                print!("Warning: {}{}", message, PHP_EOL);
            }
        }
    }
}