Skip to main content

js_deobfuscator/value/
ops.rs

1//! JavaScript operators on JsValue: arithmetic, comparison, bitwise, shift.
2
3use super::JsValue;
4use super::coerce::{to_int32, to_number, to_string, to_uint32};
5
6// ============================================================================
7// Arithmetic
8// ============================================================================
9
10/// JavaScript `+` operator. Handles number addition AND string concatenation.
11pub fn add(left: &JsValue, right: &JsValue) -> JsValue {
12    // String concatenation if either operand is a string.
13    if matches!(left, JsValue::String(_)) || matches!(right, JsValue::String(_)) {
14        let ls = to_string(left);
15        let rs = to_string(right);
16        return JsValue::String(ls + &rs);
17    }
18    JsValue::Number(to_number(left) + to_number(right))
19}
20
21/// JavaScript `-`.
22pub fn sub(left: &JsValue, right: &JsValue) -> JsValue {
23    JsValue::Number(to_number(left) - to_number(right))
24}
25
26/// JavaScript `*`.
27pub fn mul(left: &JsValue, right: &JsValue) -> JsValue {
28    JsValue::Number(to_number(left) * to_number(right))
29}
30
31/// JavaScript `/`.
32pub fn div(left: &JsValue, right: &JsValue) -> JsValue {
33    JsValue::Number(to_number(left) / to_number(right))
34}
35
36/// JavaScript `%`.
37pub fn rem(left: &JsValue, right: &JsValue) -> JsValue {
38    JsValue::Number(to_number(left) % to_number(right))
39}
40
41/// JavaScript `**`.
42pub fn exp(left: &JsValue, right: &JsValue) -> JsValue {
43    JsValue::Number(to_number(left).powf(to_number(right)))
44}
45
46// ============================================================================
47// Comparison
48// ============================================================================
49
50/// JavaScript `===` (strict equality).
51pub fn strict_eq(left: &JsValue, right: &JsValue) -> JsValue {
52    JsValue::Boolean(strict_eq_bool(left, right))
53}
54
55/// Internal: strict equality as bool.
56pub fn strict_eq_bool(left: &JsValue, right: &JsValue) -> bool {
57    match (left, right) {
58        (JsValue::Number(a), JsValue::Number(b)) => {
59            // NaN !== NaN, but -0 === +0
60            if a.is_nan() || b.is_nan() {
61                return false;
62            }
63            a == b
64        }
65        (JsValue::String(a), JsValue::String(b)) => a == b,
66        (JsValue::Boolean(a), JsValue::Boolean(b)) => a == b,
67        (JsValue::Null, JsValue::Null) => true,
68        (JsValue::Undefined, JsValue::Undefined) => true,
69        _ => false, // Different types → not strictly equal
70    }
71}
72
73/// JavaScript `!==`.
74pub fn strict_ne(left: &JsValue, right: &JsValue) -> JsValue {
75    JsValue::Boolean(!strict_eq_bool(left, right))
76}
77
78/// JavaScript `<` (abstract relational comparison).
79pub fn lt(left: &JsValue, right: &JsValue) -> Option<JsValue> {
80    abstract_compare(left, right).map(JsValue::Boolean)
81}
82
83/// JavaScript `>`.
84pub fn gt(left: &JsValue, right: &JsValue) -> Option<JsValue> {
85    abstract_compare(right, left).map(JsValue::Boolean)
86}
87
88/// JavaScript `<=`.
89pub fn le(left: &JsValue, right: &JsValue) -> Option<JsValue> {
90    abstract_compare(right, left).map(|r| JsValue::Boolean(!r))
91}
92
93/// JavaScript `>=`.
94pub fn ge(left: &JsValue, right: &JsValue) -> Option<JsValue> {
95    abstract_compare(left, right).map(|r| JsValue::Boolean(!r))
96}
97
98/// Abstract relational comparison. Returns Some(true) if left < right,
99/// Some(false) if not, None if undefined (NaN involved).
100fn abstract_compare(left: &JsValue, right: &JsValue) -> Option<bool> {
101    // Both strings: lexicographic
102    if let (JsValue::String(a), JsValue::String(b)) = (left, right) {
103        return Some(a < b);
104    }
105    // Otherwise: numeric comparison
106    let a = to_number(left);
107    let b = to_number(right);
108    if a.is_nan() || b.is_nan() {
109        return None; // undefined
110    }
111    Some(a < b)
112}
113
114// ============================================================================
115// Bitwise
116// ============================================================================
117
118/// JavaScript `&`.
119pub fn bit_and(left: &JsValue, right: &JsValue) -> JsValue {
120    JsValue::Number(f64::from(to_int32(left) & to_int32(right)))
121}
122
123/// JavaScript `|`.
124pub fn bit_or(left: &JsValue, right: &JsValue) -> JsValue {
125    JsValue::Number(f64::from(to_int32(left) | to_int32(right)))
126}
127
128/// JavaScript `^`.
129pub fn bit_xor(left: &JsValue, right: &JsValue) -> JsValue {
130    JsValue::Number(f64::from(to_int32(left) ^ to_int32(right)))
131}
132
133// ============================================================================
134// Shift
135// ============================================================================
136
137/// JavaScript `<<`.
138pub fn shl(left: &JsValue, right: &JsValue) -> JsValue {
139    let l = to_int32(left);
140    let r = to_uint32(right) & 0x1F;
141    JsValue::Number(f64::from(l << r))
142}
143
144/// JavaScript `>>` (signed).
145pub fn shr(left: &JsValue, right: &JsValue) -> JsValue {
146    let l = to_int32(left);
147    let r = to_uint32(right) & 0x1F;
148    JsValue::Number(f64::from(l >> r))
149}
150
151/// JavaScript `>>>` (unsigned).
152pub fn ushr(left: &JsValue, right: &JsValue) -> JsValue {
153    let l = to_uint32(left);
154    let r = to_uint32(right) & 0x1F;
155    JsValue::Number(f64::from(l >> r))
156}
157
158// ============================================================================
159// Unary
160// ============================================================================
161
162/// JavaScript unary `-`.
163pub fn neg(val: &JsValue) -> JsValue {
164    JsValue::Number(-to_number(val))
165}
166
167/// JavaScript unary `+`.
168pub fn pos(val: &JsValue) -> JsValue {
169    JsValue::Number(to_number(val))
170}
171
172/// JavaScript `!`.
173pub fn not(val: &JsValue) -> JsValue {
174    JsValue::Boolean(val.is_falsy())
175}
176
177/// JavaScript `~` (bitwise NOT).
178pub fn bit_not(val: &JsValue) -> JsValue {
179    JsValue::Number(f64::from(!to_int32(val)))
180}
181
182/// JavaScript `typeof`.
183pub fn type_of(val: &JsValue) -> JsValue {
184    JsValue::String(val.type_of().to_string())
185}
186
187/// JavaScript `void`.
188pub fn void_op(_val: &JsValue) -> JsValue {
189    JsValue::Undefined
190}
191
192// ============================================================================
193// Tests
194// ============================================================================
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    fn n(v: f64) -> JsValue { JsValue::Number(v) }
201    fn s(v: &str) -> JsValue { JsValue::String(v.into()) }
202    fn b(v: bool) -> JsValue { JsValue::Boolean(v) }
203
204    #[test]
205    fn test_add_numbers() {
206        assert_eq!(add(&n(1.0), &n(2.0)), n(3.0));
207        assert_eq!(add(&n(-1.0), &n(1.0)), n(0.0));
208    }
209
210    #[test]
211    fn test_add_string_concat() {
212        assert_eq!(add(&s("hello"), &s(" world")), s("hello world"));
213        assert_eq!(add(&s("x"), &n(1.0)), s("x1"));
214        assert_eq!(add(&n(1.0), &s("x")), s("1x"));
215        assert_eq!(add(&s(""), &JsValue::Null), s("null"));
216        assert_eq!(add(&s(""), &b(true)), s("true"));
217    }
218
219    #[test]
220    fn test_arithmetic() {
221        assert_eq!(sub(&n(5.0), &n(3.0)), n(2.0));
222        assert_eq!(mul(&n(3.0), &n(4.0)), n(12.0));
223        assert_eq!(div(&n(10.0), &n(3.0)), n(10.0 / 3.0));
224        assert_eq!(rem(&n(10.0), &n(3.0)), n(1.0));
225        assert_eq!(exp(&n(2.0), &n(10.0)), n(1024.0));
226    }
227
228    #[test]
229    fn test_strict_equality() {
230        assert_eq!(strict_eq(&n(1.0), &n(1.0)), b(true));
231        assert_eq!(strict_eq(&n(1.0), &n(2.0)), b(false));
232        assert_eq!(strict_eq(&s("a"), &s("a")), b(true));
233        assert_eq!(strict_eq(&n(1.0), &s("1")), b(false)); // different types
234        assert_eq!(strict_eq(&JsValue::Null, &JsValue::Null), b(true));
235        assert_eq!(strict_eq(&JsValue::Null, &JsValue::Undefined), b(false));
236        // NaN !== NaN
237        assert_eq!(strict_eq(&n(f64::NAN), &n(f64::NAN)), b(false));
238    }
239
240    #[test]
241    fn test_comparison() {
242        assert_eq!(lt(&n(1.0), &n(2.0)), Some(b(true)));
243        assert_eq!(gt(&n(2.0), &n(1.0)), Some(b(true)));
244        assert_eq!(le(&n(1.0), &n(1.0)), Some(b(true)));
245        assert_eq!(ge(&n(1.0), &n(1.0)), Some(b(true)));
246        // String comparison
247        assert_eq!(lt(&s("a"), &s("b")), Some(b(true)));
248        assert_eq!(gt(&s("b"), &s("a")), Some(b(true)));
249        // NaN comparison is undefined
250        assert_eq!(lt(&n(f64::NAN), &n(1.0)), None);
251    }
252
253    #[test]
254    fn test_bitwise() {
255        assert_eq!(bit_and(&n(255.0), &n(15.0)), n(15.0));
256        assert_eq!(bit_or(&n(240.0), &n(15.0)), n(255.0));
257        assert_eq!(bit_xor(&n(255.0), &n(15.0)), n(240.0));
258    }
259
260    #[test]
261    fn test_shift() {
262        assert_eq!(shl(&n(1.0), &n(8.0)), n(256.0));
263        assert_eq!(shr(&n(256.0), &n(4.0)), n(16.0));
264        assert_eq!(ushr(&n(-1.0), &n(0.0)), n(4294967295.0));
265    }
266
267    #[test]
268    fn test_unary() {
269        assert_eq!(neg(&n(5.0)), n(-5.0));
270        assert_eq!(pos(&s("42")), n(42.0));
271        assert_eq!(not(&b(true)), b(false));
272        assert_eq!(not(&n(0.0)), b(true));
273        assert_eq!(bit_not(&n(0.0)), n(-1.0));
274        assert_eq!(type_of(&n(1.0)), s("number"));
275        assert_eq!(void_op(&n(1.0)), JsValue::Undefined);
276    }
277}