js_deobfuscator/fold/
mod.rs1pub mod binary;
12pub mod unary;
13pub mod logical;
14pub mod conditional;
15pub mod call;
16pub mod sequence;
17pub mod statement;
18pub mod template;
19
20use oxc::allocator::Allocator;
25use oxc::ast::ast::{Expression, Statement};
26use oxc::semantic::Scoping;
27
28use oxc_traverse::{Traverse, TraverseCtx, traverse_mut};
29
30use crate::engine::error::Result;
31use crate::engine::module::{Module, TransformResult};
32
33pub struct StaticFolder;
39
40impl Module for StaticFolder {
41 fn name(&self) -> &'static str {
42 "StaticFolder"
43 }
44
45 fn transform<'a>(
46 &mut self,
47 allocator: &'a Allocator,
48 program: &mut oxc::ast::ast::Program<'a>,
49 scoping: Scoping,
50 ) -> Result<TransformResult> {
51 let mut visitor = FoldVisitor::default();
52 let scoping = traverse_mut(&mut visitor, allocator, program, scoping, ());
53 Ok(TransformResult {
54 modifications: visitor.modifications,
55 scoping,
56 })
57 }
58}
59
60#[derive(Default)]
65struct FoldVisitor {
66 modifications: usize,
67}
68
69impl<'a> Traverse<'a, ()> for FoldVisitor {
70 fn exit_expression(
71 &mut self,
72 expr: &mut Expression<'a>,
73 ctx: &mut TraverseCtx<'a, ()>,
74 ) {
75 if is_leaf_literal(expr) {
77 return;
78 }
79
80 let folded = match expr {
84 Expression::BinaryExpression(_) => binary::try_fold(expr, ctx),
85 Expression::UnaryExpression(_) => unary::try_fold(expr, ctx),
86 Expression::LogicalExpression(_) => logical::try_fold(expr, ctx),
87 Expression::ConditionalExpression(_) => conditional::try_fold(expr, ctx),
88 Expression::CallExpression(_) => call::try_fold(expr, ctx),
89 Expression::SequenceExpression(_) => sequence::try_fold(expr, ctx),
90 Expression::TemplateLiteral(_) => template::try_fold(expr, ctx),
91 _ => None,
92 };
93
94 if let Some(n) = folded {
95 self.modifications += n;
96 }
97 }
98
99 fn exit_statement(
100 &mut self,
101 stmt: &mut Statement<'a>,
102 ctx: &mut TraverseCtx<'a, ()>,
103 ) {
104 self.modifications += statement::try_fold(stmt, ctx);
105 }
106
107 fn exit_statements(
108 &mut self,
109 stmts: &mut oxc::allocator::Vec<'a, Statement<'a>>,
110 ctx: &mut TraverseCtx<'a, ()>,
111 ) {
112 self.modifications += statement::clean(stmts, ctx);
113 }
114}
115
116fn is_leaf_literal(expr: &Expression) -> bool {
118 match expr {
119 Expression::NumericLiteral(_)
120 | Expression::StringLiteral(_)
121 | Expression::BooleanLiteral(_)
122 | Expression::NullLiteral(_) => true,
123 Expression::Identifier(id) => {
124 matches!(id.name.as_str(), "undefined" | "Infinity" | "NaN")
125 }
126 _ => false,
127 }
128}
129
130#[cfg(test)]
135pub(crate) mod test_utils {
136 use oxc::allocator::Allocator;
137 use oxc::codegen::Codegen;
138 use oxc::parser::Parser;
139 use oxc::semantic::SemanticBuilder;
140 use oxc::span::SourceType;
141
142 pub fn fold(source: &str) -> String {
144 let allocator = Allocator::default();
145 let mut program = Parser::new(&allocator, source, SourceType::mjs())
146 .parse()
147 .program;
148 let scoping = SemanticBuilder::new()
149 .build(&program)
150 .semantic
151 .into_scoping();
152
153 let mut folder = super::FoldVisitor::default();
154 oxc_traverse::traverse_mut(&mut folder, &allocator, &mut program, scoping, ());
155
156 Codegen::new().build(&program).code
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::test_utils::fold;
163
164 #[test]
165 fn test_leaf_literals_unchanged() {
166 assert_eq!(fold("42;\n").trim(), "42;");
167 assert_eq!(fold("\"hello\";\n").trim(), "\"hello\";");
168 assert_eq!(fold("true;\n").trim(), "true;");
169 }
170}