Skip to main content

js_deobfuscator/value/
number.rs

1//! Number methods and global numeric functions.
2
3use super::JsValue;
4use super::coerce::{string_to_number, to_number};
5
6/// Evaluate `Number.method(args)` or `Number(value)`.
7pub fn call(method: &str, args: &[JsValue]) -> Option<JsValue> {
8    match method {
9        "isNaN" => Some(JsValue::Boolean(matches!(args.first(), Some(JsValue::Number(n)) if n.is_nan()))),
10        "isFinite" => Some(JsValue::Boolean(matches!(args.first(), Some(JsValue::Number(n)) if n.is_finite()))),
11        "isInteger" => Some(JsValue::Boolean(matches!(args.first(), Some(JsValue::Number(n)) if n.is_finite() && n.fract() == 0.0))),
12        "isSafeInteger" => Some(JsValue::Boolean(matches!(args.first(), Some(JsValue::Number(n)) if n.is_finite() && n.fract() == 0.0 && n.abs() <= 9_007_199_254_740_991.0))),
13        "parseInt" => parse_int(args),
14        "parseFloat" => parse_float(args),
15        _ => None,
16    }
17}
18
19/// `Number(value)` constructor coercion (without `new`).
20pub fn coerce(args: &[JsValue]) -> JsValue {
21    match args.first() {
22        None => JsValue::Number(0.0),
23        Some(v) => JsValue::Number(to_number(v)),
24    }
25}
26
27/// Global `parseInt(string, radix?)`.
28pub fn parse_int(args: &[JsValue]) -> Option<JsValue> {
29    let s = args.first().map(super::coerce::to_string)?;
30    let trimmed = s.trim();
31    if trimmed.is_empty() {
32        return Some(JsValue::Number(f64::NAN));
33    }
34
35    let radix = args.get(1).map(|v| to_number(v) as i32).unwrap_or(0);
36
37    // Auto-detect radix
38    let (num_str, base) = if radix == 0 || radix == 16 {
39        if let Some(rest) = trimmed.strip_prefix("0x").or_else(|| trimmed.strip_prefix("0X")) {
40            (rest, 16)
41        } else {
42            (trimmed, if radix == 0 { 10 } else { radix })
43        }
44    } else {
45        (trimmed, radix)
46    };
47
48    if !(2..=36).contains(&base) {
49        return Some(JsValue::Number(f64::NAN));
50    }
51
52    // Parse as many valid digits as possible
53    let valid: String = num_str.chars()
54        .take_while(|c| c.is_digit(base as u32))
55        .collect();
56
57    if valid.is_empty() {
58        return Some(JsValue::Number(f64::NAN));
59    }
60
61    i64::from_str_radix(&valid, base as u32)
62        .map(|v| JsValue::Number(v as f64))
63        .ok()
64        .or(Some(JsValue::Number(f64::NAN)))
65}
66
67/// Global `parseFloat(string)`.
68pub fn parse_float(args: &[JsValue]) -> Option<JsValue> {
69    let s = args.first().map(super::coerce::to_string)?;
70    Some(JsValue::Number(string_to_number(&s)))
71}
72
73/// Global `isNaN(value)`.
74pub fn global_is_nan(args: &[JsValue]) -> Option<JsValue> {
75    let n = args.first().map(to_number).unwrap_or(f64::NAN);
76    Some(JsValue::Boolean(n.is_nan()))
77}
78
79/// Global `isFinite(value)`.
80pub fn global_is_finite(args: &[JsValue]) -> Option<JsValue> {
81    let n = args.first().map(to_number).unwrap_or(f64::NAN);
82    Some(JsValue::Boolean(n.is_finite()))
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    fn n(v: f64) -> JsValue { JsValue::Number(v) }
89    fn s(v: &str) -> JsValue { JsValue::String(v.into()) }
90
91    #[test]
92    fn test_parse_int() {
93        assert_eq!(parse_int(&[s("42")]), Some(n(42.0)));
94        assert_eq!(parse_int(&[s("0xff")]), Some(n(255.0)));
95        assert_eq!(parse_int(&[s("11"), n(2.0)]), Some(n(3.0)));
96        assert_eq!(parse_int(&[s("  123abc")]), Some(n(123.0)));
97        assert!(matches!(parse_int(&[s("abc")]), Some(JsValue::Number(v)) if v.is_nan()));
98    }
99
100    #[test]
101    fn test_parse_float() {
102        assert_eq!(parse_float(&[s("3.14")]), Some(n(3.14)));
103        assert_eq!(parse_float(&[s("  42  ")]), Some(n(42.0)));
104    }
105
106    #[test]
107    fn test_number_static() {
108        assert_eq!(call("isNaN", &[n(f64::NAN)]), Some(JsValue::Boolean(true)));
109        assert_eq!(call("isNaN", &[n(42.0)]), Some(JsValue::Boolean(false)));
110        assert_eq!(call("isFinite", &[n(42.0)]), Some(JsValue::Boolean(true)));
111        assert_eq!(call("isFinite", &[n(f64::INFINITY)]), Some(JsValue::Boolean(false)));
112        assert_eq!(call("isInteger", &[n(5.0)]), Some(JsValue::Boolean(true)));
113        assert_eq!(call("isInteger", &[n(5.5)]), Some(JsValue::Boolean(false)));
114    }
115
116    #[test]
117    fn test_coerce() {
118        assert_eq!(coerce(&[s("42")]), n(42.0));
119        assert_eq!(coerce(&[JsValue::Boolean(true)]), n(1.0));
120        assert_eq!(coerce(&[]), n(0.0));
121    }
122
123    #[test]
124    fn test_global_is_nan() {
125        assert_eq!(global_is_nan(&[s("abc")]), Some(JsValue::Boolean(true)));
126        assert_eq!(global_is_nan(&[n(42.0)]), Some(JsValue::Boolean(false)));
127    }
128}