Skip to main content

js_deobfuscator/scope/
query.rs

1//! Reference counting and symbol queries.
2
3use oxc::semantic::{Scoping, SymbolId};
4
5/// Check if a symbol has any write references.
6#[inline]
7pub fn has_writes(scoping: &Scoping, symbol_id: SymbolId) -> bool {
8    scoping
9        .get_resolved_references(symbol_id)
10        .any(|r| r.flags().is_write())
11}
12
13/// Check if a symbol has any read references.
14#[inline]
15pub fn has_reads(scoping: &Scoping, symbol_id: SymbolId) -> bool {
16    scoping
17        .get_resolved_references(symbol_id)
18        .any(|r| r.flags().is_read())
19}
20
21/// Count read references to a symbol.
22pub fn count_reads(scoping: &Scoping, symbol_id: SymbolId) -> usize {
23    scoping
24        .get_resolved_references(symbol_id)
25        .filter(|r| r.flags().is_read())
26        .count()
27}
28
29/// Count write references to a symbol.
30pub fn count_writes(scoping: &Scoping, symbol_id: SymbolId) -> usize {
31    scoping
32        .get_resolved_references(symbol_id)
33        .filter(|r| r.flags().is_write())
34        .count()
35}
36
37/// Check if a symbol is declared with `const`.
38#[inline]
39pub fn is_const(scoping: &Scoping, symbol_id: SymbolId) -> bool {
40    scoping.symbol_flags(symbol_id).is_const_variable()
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use crate::scope::resolve;
47    use oxc::allocator::Allocator;
48    use oxc::ast::ast::{Expression, Statement};
49    use oxc::parser::Parser;
50    use oxc::semantic::SemanticBuilder;
51    use oxc::span::SourceType;
52
53    #[test]
54    fn test_const_no_writes() {
55        let source = "const x = 42; x; x;";
56        let alloc = Allocator::default();
57        let ret = Parser::new(&alloc, source, SourceType::mjs()).parse();
58        let scoping = SemanticBuilder::new().build(&ret.program).semantic.into_scoping();
59
60        // Find symbol for x
61        if let Statement::ExpressionStatement(stmt) = &ret.program.body[1] {
62            if let Expression::Identifier(ident) = &stmt.expression {
63                let sym = resolve::get_reference_symbol(&scoping, ident).unwrap();
64                assert!(is_const(&scoping, sym));
65                assert!(!has_writes(&scoping, sym));
66                assert!(has_reads(&scoping, sym));
67                assert_eq!(count_reads(&scoping, sym), 2);
68                assert_eq!(count_writes(&scoping, sym), 0);
69            }
70        }
71    }
72
73    #[test]
74    fn test_let_with_writes() {
75        let source = "let y = 1; y = 2; y;";
76        let alloc = Allocator::default();
77        let ret = Parser::new(&alloc, source, SourceType::mjs()).parse();
78        let scoping = SemanticBuilder::new().build(&ret.program).semantic.into_scoping();
79
80        // Find symbol via the last expression statement (read of y)
81        if let Statement::ExpressionStatement(stmt) = &ret.program.body[2] {
82            if let Expression::Identifier(ident) = &stmt.expression {
83                let sym = resolve::get_reference_symbol(&scoping, ident).unwrap();
84                assert!(!is_const(&scoping, sym));
85                assert!(has_writes(&scoping, sym));
86                assert!(has_reads(&scoping, sym));
87                assert_eq!(count_writes(&scoping, sym), 1);
88            }
89        }
90    }
91}