js_deobfuscator/fold/
binary.rs1use oxc::ast::ast::{BinaryOperator, Expression};
6
7use oxc_traverse::TraverseCtx;
8
9use crate::ast::{create, extract};
10use crate::value::{JsValue, ops};
11
12pub fn try_fold<'a>(
14 expr: &mut Expression<'a>,
15 ctx: &mut TraverseCtx<'a, ()>,
16) -> Option<usize> {
17 let Expression::BinaryExpression(bin) = &*expr else {
18 return None;
19 };
20
21 let left = extract::js_value(&bin.left)?;
22 let right = extract::js_value(&bin.right)?;
23
24 let result = match bin.operator {
25 BinaryOperator::Addition => Some(ops::add(&left, &right)),
27 BinaryOperator::Subtraction => Some(ops::sub(&left, &right)),
28 BinaryOperator::Multiplication => Some(ops::mul(&left, &right)),
29 BinaryOperator::Division => {
30 if let JsValue::Number(r) = &right {
32 if *r == 0.0 { return None; }
33 }
34 Some(ops::div(&left, &right))
35 }
36 BinaryOperator::Remainder => {
37 if let JsValue::Number(r) = &right {
38 if *r == 0.0 { return None; }
39 }
40 Some(ops::rem(&left, &right))
41 }
42 BinaryOperator::Exponential => Some(ops::exp(&left, &right)),
43
44 BinaryOperator::StrictEquality => Some(ops::strict_eq(&left, &right)),
46 BinaryOperator::StrictInequality => Some(ops::strict_ne(&left, &right)),
47 BinaryOperator::LessThan => ops::lt(&left, &right),
48 BinaryOperator::GreaterThan => ops::gt(&left, &right),
49 BinaryOperator::LessEqualThan => ops::le(&left, &right),
50 BinaryOperator::GreaterEqualThan => ops::ge(&left, &right),
51
52 BinaryOperator::BitwiseAnd => Some(ops::bit_and(&left, &right)),
54 BinaryOperator::BitwiseOR => Some(ops::bit_or(&left, &right)),
55 BinaryOperator::BitwiseXOR => Some(ops::bit_xor(&left, &right)),
56
57 BinaryOperator::ShiftLeft => Some(ops::shl(&left, &right)),
59 BinaryOperator::ShiftRight => Some(ops::shr(&left, &right)),
60 BinaryOperator::ShiftRightZeroFill => Some(ops::ushr(&left, &right)),
61
62 _ => None,
64 }?;
65
66 if let JsValue::Number(n) = &result {
69 if n.is_nan() && !matches!(left, JsValue::Number(_)) {
70 return None;
71 }
72 }
73
74 *expr = create::from_js_value(&result, &ctx.ast);
75 Some(1)
76}
77
78#[cfg(test)]
79mod tests {
80 use super::super::test_utils::fold;
81
82 #[test]
83 fn test_arithmetic() {
84 assert!(fold("1 + 2;").contains("3"));
85 assert!(fold("10 - 3;").contains("7"));
86 assert!(fold("3 * 4;").contains("12"));
87 assert!(fold("10 / 2;").contains("5"));
88 assert!(fold("10 % 3;").contains("1"));
89 assert!(fold("2 ** 10;").contains("1024"));
90 }
91
92 #[test]
93 fn test_string_concat() {
94 assert!(fold("\"hello\" + \" world\";").contains("\"hello world\""));
95 assert!(fold("\"x\" + 1;").contains("\"x1\""));
96 }
97
98 #[test]
99 fn test_comparison() {
100 assert!(fold("1 === 1;").contains("true"));
101 assert!(fold("1 === 2;").contains("false"));
102 assert!(fold("1 !== 2;").contains("true"));
103 assert!(fold("1 < 2;").contains("true"));
104 assert!(fold("2 > 1;").contains("true"));
105 assert!(fold("1 <= 1;").contains("true"));
106 assert!(fold("1 >= 1;").contains("true"));
107 }
108
109 #[test]
110 fn test_bitwise() {
111 assert!(fold("255 & 15;").contains("15"));
112 assert!(fold("240 | 15;").contains("255"));
113 assert!(fold("255 ^ 15;").contains("240"));
114 }
115
116 #[test]
117 fn test_shift() {
118 assert!(fold("1 << 8;").contains("256"));
119 assert!(fold("256 >> 4;").contains("16"));
120 }
121
122 #[test]
123 fn test_division_by_zero_not_folded() {
124 let result = fold("1 / 0;");
125 assert!(result.contains("/"), "division by zero should not fold: {result}");
126 }
127}