js_deobfuscator/eval/
mod.rs1pub mod safety;
8pub mod node;
9
10use oxc::allocator::Allocator;
11use oxc::ast::ast::{Expression, Program};
12use oxc::semantic::Scoping;
13
14use oxc_traverse::{Traverse, TraverseCtx, traverse_mut};
15
16use crate::ast::{codegen, create, extract, query};
17use crate::engine::error::Result;
18use crate::engine::module::{Module, TransformResult};
19use crate::value::JsValue;
20
21#[derive(Default)]
23pub struct DynamicEvaluator {
24 node: Option<node::NodeProcess>,
25}
26
27impl DynamicEvaluator {
28 pub fn new() -> Self {
29 Self::default()
30 }
31
32 fn ensure_node(&mut self) -> Option<&mut node::NodeProcess> {
33 if self.node.is_none() {
34 self.node = node::NodeProcess::spawn().ok();
35 }
36 self.node.as_mut()
37 }
38}
39
40impl Module for DynamicEvaluator {
41 fn name(&self) -> &'static str {
42 "DynamicEvaluator"
43 }
44
45 fn transform<'a>(
46 &mut self,
47 allocator: &'a Allocator,
48 program: &mut Program<'a>,
49 scoping: Scoping,
50 ) -> Result<TransformResult> {
51 let mut visitor = EvalVisitor {
52 evaluator: self,
53 modifications: 0,
54 };
55 let scoping = traverse_mut(&mut visitor, allocator, program, scoping, ());
56 Ok(TransformResult { modifications: visitor.modifications, scoping })
57 }
58}
59
60struct EvalVisitor<'e> {
61 evaluator: &'e mut DynamicEvaluator,
62 modifications: usize,
63}
64
65impl<'a, 'e> Traverse<'a, ()> for EvalVisitor<'e> {
66 fn exit_expression(
67 &mut self,
68 expr: &mut Expression<'a>,
69 ctx: &mut TraverseCtx<'a, ()>,
70 ) {
71 if extract::js_value(expr).is_some() {
73 return;
74 }
75
76 if !query::is_side_effect_free(expr) {
78 return;
79 }
80
81 if !safety::is_safe_expr(expr) {
83 return;
84 }
85
86 let code = codegen::expr_to_code(expr);
88
89 let Some(node) = self.evaluator.ensure_node() else {
91 return;
92 };
93 let Some(result) = node.eval(&code) else {
94 return;
95 };
96
97 let Some(val) = json_to_jsvalue(&result) else {
99 return;
100 };
101
102 *expr = create::from_js_value(&val, &ctx.ast);
103 self.modifications += 1;
104 }
105}
106
107fn json_to_jsvalue(val: &serde_json::Value) -> Option<JsValue> {
108 match val {
109 serde_json::Value::Number(n) => n.as_f64().map(JsValue::Number),
110 serde_json::Value::String(s) => Some(JsValue::String(s.clone())),
111 serde_json::Value::Bool(b) => Some(JsValue::Boolean(*b)),
112 serde_json::Value::Null => Some(JsValue::Null),
113 _ => None,
114 }
115}