js_deobfuscator/value/
number.rs1use super::JsValue;
4use super::coerce::{string_to_number, to_number};
5
6pub 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
19pub 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
27pub 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 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 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
67pub 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
73pub 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
79pub 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}