Skip to main content

js_deobfuscator/scope/
resolve.rs

1//! Symbol resolution — safe ID extraction from AST nodes.
2//!
3//! Every function uses `.get()` on `Cell<Option<_>>`. Never panics.
4
5use oxc::ast::ast::{
6    BindingIdentifier, Function, IdentifierReference, VariableDeclarator,
7};
8use oxc::semantic::{Scoping, SymbolId};
9
10/// Resolve an identifier reference to its declared symbol.
11///
12/// Returns `None` for global/unresolved references.
13#[inline]
14pub fn get_reference_symbol(scoping: &Scoping, ident: &IdentifierReference) -> Option<SymbolId> {
15    let ref_id = ident.reference_id.get()?;
16    scoping.get_reference(ref_id).symbol_id()
17}
18
19/// Get symbol ID from a binding identifier (declaration site).
20#[inline]
21pub fn get_binding_symbol(binding: &BindingIdentifier) -> Option<SymbolId> {
22    binding.symbol_id.get()
23}
24
25/// Get symbol ID from a variable declarator.
26///
27/// Traverses: declarator → binding pattern → binding identifier → symbol ID.
28#[inline]
29pub fn get_declarator_symbol(decl: &VariableDeclarator) -> Option<SymbolId> {
30    decl.id.get_binding_identifier().and_then(|b| b.symbol_id.get())
31}
32
33/// Get symbol ID from a named function.
34#[inline]
35pub fn get_function_symbol(func: &Function) -> Option<SymbolId> {
36    func.id.as_ref().and_then(|id| id.symbol_id.get())
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use oxc::allocator::Allocator;
43    use oxc::ast::ast::Statement;
44    use oxc::parser::Parser;
45    use oxc::semantic::SemanticBuilder;
46    use oxc::span::SourceType;
47
48    #[test]
49    fn test_resolve_const() {
50        let source = "const x = 42; x;";
51        let alloc = Allocator::default();
52        let ret = Parser::new(&alloc, source, SourceType::mjs()).parse();
53        let scoping = SemanticBuilder::new().build(&ret.program).semantic.into_scoping();
54
55        // The second statement is an expression statement with identifier "x"
56        if let Statement::ExpressionStatement(stmt) = &ret.program.body[1] {
57            if let oxc::ast::ast::Expression::Identifier(ident) = &stmt.expression {
58                let sym = get_reference_symbol(&scoping, ident);
59                assert!(sym.is_some(), "should resolve const x");
60            }
61        }
62    }
63
64    #[test]
65    fn test_global_is_none() {
66        let source = "console.log(1);";
67        let alloc = Allocator::default();
68        let ret = Parser::new(&alloc, source, SourceType::mjs()).parse();
69        let scoping = SemanticBuilder::new().build(&ret.program).semantic.into_scoping();
70
71        if let Statement::ExpressionStatement(stmt) = &ret.program.body[0] {
72            if let oxc::ast::ast::Expression::CallExpression(call) = &stmt.expression {
73                if let oxc::ast::ast::Expression::StaticMemberExpression(member) = &call.callee {
74                    if let oxc::ast::ast::Expression::Identifier(ident) = &member.object {
75                        let sym = get_reference_symbol(&scoping, ident);
76                        assert!(sym.is_none(), "console should be unresolved (global)");
77                    }
78                }
79            }
80        }
81    }
82
83    #[test]
84    fn test_get_declarator_symbol() {
85        let alloc = Allocator::default();
86        let ret = Parser::new(&alloc, "const x = 1;", SourceType::mjs()).parse();
87        let _scoping = SemanticBuilder::new().build(&ret.program).semantic.into_scoping();
88
89        if let Statement::VariableDeclaration(decl) = &ret.program.body[0] {
90            let sym = get_declarator_symbol(&decl.declarations[0]);
91            assert!(sym.is_some(), "declarator should have symbol ID");
92        }
93    }
94
95    #[test]
96    fn test_get_function_symbol() {
97        let alloc = Allocator::default();
98        let ret = Parser::new(&alloc, "function foo() {}", SourceType::mjs()).parse();
99        let _scoping = SemanticBuilder::new().build(&ret.program).semantic.into_scoping();
100
101        if let Statement::FunctionDeclaration(func) = &ret.program.body[0] {
102            let sym = get_function_symbol(func);
103            assert!(sym.is_some(), "named function should have symbol ID");
104        }
105    }
106}