1use bitflags::bitflags;
6use boa_string::JsString;
7use rustc_hash::FxHashMap;
8use std::{
9 cell::{Cell, RefCell},
10 fmt::Debug,
11 rc::Rc,
12};
13
14bitflags! {
15 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
16 struct BindingFlags: u8 {
17 const MUTABLE = 1 << 0;
18 const LEX = 1 << 1;
19 const STRICT = 1 << 2;
20 const ESCAPES = 1 << 3;
21 const ACCESSED = 1 << 4;
22 }
23}
24
25impl BindingFlags {
26 fn is_mutable(self) -> bool {
27 self.contains(BindingFlags::MUTABLE)
28 }
29 fn is_lex(self) -> bool {
30 self.contains(BindingFlags::LEX)
31 }
32 fn is_strict(self) -> bool {
33 self.contains(BindingFlags::STRICT)
34 }
35 fn escapes(self) -> bool {
36 self.contains(BindingFlags::ESCAPES)
37 }
38 fn is_accessed(self) -> bool {
39 self.contains(BindingFlags::ACCESSED)
40 }
41}
42
43#[derive(Clone, Debug, PartialEq)]
44struct Binding {
45 name: JsString,
46 index: u32,
47 flags: BindingFlags,
48}
49
50impl Binding {
51 fn is_mutable(&self) -> bool {
52 self.flags.is_mutable()
53 }
54 fn is_lex(&self) -> bool {
55 self.flags.is_lex()
56 }
57 fn is_strict(&self) -> bool {
58 self.flags.is_strict()
59 }
60 fn escapes(&self) -> bool {
61 self.flags.escapes()
62 }
63 fn is_accessed(&self) -> bool {
64 self.flags.is_accessed()
65 }
66}
67
68#[derive(Clone, PartialEq)]
72pub struct Scope {
73 inner: Rc<Inner>,
74}
75
76impl Debug for Scope {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 f.debug_struct("Scope")
79 .field("outer", &self.inner.outer)
80 .field("index", &self.inner.index)
81 .field("bindings", &self.inner.bindings)
82 .field("function", &self.inner.function)
83 .finish()
84 }
85}
86
87impl Default for Scope {
88 fn default() -> Self {
89 Self::new_global()
90 }
91}
92
93#[cfg(feature = "arbitrary")]
94impl<'a> arbitrary::Arbitrary<'a> for Scope {
95 fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
96 Ok(Self::new_global())
97 }
98}
99
100#[derive(Debug, PartialEq)]
101pub(crate) struct Inner {
102 unique_id: u32,
103 outer: Option<Scope>,
104 index: Cell<u32>,
105 bindings: RefCell<Vec<Binding>>,
106 names: RefCell<FxHashMap<JsString, usize>>,
111 function: bool,
112 this_escaped: Cell<bool>,
114
115 context: Rc<ScopeContext>,
116}
117
118impl Scope {
119 #[must_use]
121 pub fn new_global() -> Self {
122 Self {
123 inner: Rc::new(Inner {
124 unique_id: 0,
125 outer: None,
126 index: Cell::default(),
127 bindings: RefCell::default(),
128 names: RefCell::default(),
129 function: true,
130 this_escaped: Cell::new(false),
131 context: Rc::default(),
132 }),
133 }
134 }
135
136 #[must_use]
138 pub fn new(parent: Self, function: bool) -> Self {
139 Self {
140 inner: Rc::new(Inner {
141 unique_id: parent.inner.context.next_unique_id(),
142 index: Cell::new(parent.inner.index.get() + 1),
143 bindings: RefCell::default(),
144 names: RefCell::default(),
145 function,
146 this_escaped: Cell::new(false),
147 context: parent.inner.context.clone(),
148 outer: Some(parent),
149 }),
150 }
151 }
152
153 #[must_use]
155 pub fn all_bindings_local(&self) -> bool {
156 self.inner
158 .bindings
159 .borrow()
160 .iter()
161 .all(|binding| !binding.escapes())
162 }
163
164 pub fn escape_all_bindings(&self) {
166 for binding in self.inner.bindings.borrow_mut().iter_mut() {
167 binding.flags.insert(BindingFlags::ESCAPES);
168 }
169 }
170
171 #[must_use]
173 pub fn escaped_this(&self) -> bool {
174 self.inner.this_escaped.get()
175 }
176
177 fn position(&self, name: &JsString) -> usize {
180 self.inner
181 .names
182 .borrow()
183 .get(name)
184 .copied()
185 .unwrap_or(usize::MAX)
186 }
187
188 #[must_use]
190 pub fn has_lex_binding(&self, name: &JsString) -> bool {
191 self.inner
192 .bindings
193 .borrow()
194 .get(self.position(name))
195 .is_some_and(Binding::is_lex)
196 }
197
198 #[must_use]
200 pub fn has_binding(&self, name: &JsString) -> bool {
201 self.position(name) < usize::MAX
202 }
203
204 #[must_use]
207 pub fn get_identifier_reference(&self, name: JsString) -> IdentifierReference {
208 if let Some(binding) = self.inner.bindings.borrow().get(self.position(&name)) {
209 IdentifierReference::new(
210 BindingLocator::declarative(
211 name,
212 self.inner.index.get(),
213 binding.index,
214 self.inner.unique_id,
215 ),
216 binding.is_lex(),
217 binding.escapes(),
218 )
219 } else if let Some(outer) = &self.inner.outer {
220 outer.get_identifier_reference(name)
221 } else {
222 IdentifierReference::new(BindingLocator::global(name), false, true)
223 }
224 }
225
226 #[must_use]
228 #[allow(clippy::cast_possible_truncation)]
229 pub fn num_bindings(&self) -> u32 {
230 self.inner.bindings.borrow().len() as u32
231 }
232
233 #[must_use]
235 #[allow(clippy::cast_possible_truncation)]
236 pub fn num_bindings_non_local(&self) -> u32 {
237 self.inner
238 .bindings
239 .borrow()
240 .iter()
241 .filter(|binding| binding.escapes())
242 .count() as u32
243 }
244
245 pub(crate) fn reorder_binding_indices(&self) {
247 let mut bindings = self.inner.bindings.borrow_mut();
248 let mut index = 0;
249 for binding in bindings.iter_mut() {
250 if !binding.escapes() {
251 binding.index = 0;
252 continue;
253 }
254 binding.index = index;
255 index += 1;
256 }
257 }
258
259 #[must_use]
261 pub fn scope_index(&self) -> u32 {
262 self.inner.index.get()
263 }
264
265 pub(crate) fn set_index(&self, index: u32) {
267 self.inner.index.set(index);
268 }
269
270 #[must_use]
272 pub fn is_function(&self) -> bool {
273 self.inner.function
274 }
275
276 #[must_use]
278 pub fn is_global(&self) -> bool {
279 self.inner.outer.is_none()
280 }
281
282 #[must_use]
288 pub fn is_binding_mutable(&self, name: &JsString) -> Option<bool> {
289 if let Some(binding) = self.inner.bindings.borrow().get(self.position(name)) {
290 Some(binding.is_mutable())
291 } else if let Some(outer) = &self.inner.outer {
292 outer.is_binding_mutable(name)
293 } else {
294 None
295 }
296 }
297
298 #[must_use]
300 pub fn get_binding(&self, name: &JsString) -> Option<BindingLocator> {
301 self.inner
302 .bindings
303 .borrow()
304 .get(self.position(name))
305 .map(|binding| {
306 BindingLocator::declarative(
307 name.clone(),
308 self.inner.index.get(),
309 binding.index,
310 self.inner.unique_id,
311 )
312 })
313 }
314
315 #[must_use]
317 pub fn get_binding_reference(&self, name: &JsString) -> Option<IdentifierReference> {
318 self.inner
319 .bindings
320 .borrow()
321 .get(self.position(name))
322 .map(|binding| {
323 IdentifierReference::new(
324 BindingLocator::declarative(
325 name.clone(),
326 self.inner.index.get(),
327 binding.index,
328 self.inner.unique_id,
329 ),
330 binding.is_lex(),
331 binding.escapes(),
332 )
333 })
334 }
335
336 pub fn access_binding(&self, name: &JsString, eval_or_with: bool) {
341 let mut crossed_function_border = false;
342 let mut current = self;
343 loop {
344 if let Some(binding) = current
345 .inner
346 .bindings
347 .borrow_mut()
348 .get_mut(current.position(name))
349 {
350 binding.flags.insert(BindingFlags::ACCESSED);
351 if crossed_function_border || eval_or_with {
352 binding.flags.insert(BindingFlags::ESCAPES);
353 }
354 return;
355 }
356 if let Some(outer) = ¤t.inner.outer {
357 if current.inner.function {
358 crossed_function_border = true;
359 }
360 current = outer;
361 } else {
362 return;
363 }
364 }
365 }
366
367 pub fn escape_this_in_enclosing_function_scope(&self) {
369 let mut current = self;
370 let mut crossed_function_border = false;
371
372 loop {
373 if crossed_function_border && current.is_function() {
374 current.inner.this_escaped.set(true);
375 return;
376 }
377 if let Some(outer) = ¤t.inner.outer {
378 if current.is_function() {
379 crossed_function_border = true;
380 }
381 current = outer;
382 } else {
383 return;
384 }
385 }
386 }
387
388 #[must_use]
390 #[allow(clippy::cast_possible_truncation)]
391 pub fn create_mutable_binding(&self, name: JsString, function_scope: bool) -> BindingLocator {
392 let mut bindings = self.inner.bindings.borrow_mut();
393 let binding_index = bindings.len() as u32;
394 if let Some(binding) = bindings.get(self.position(&name)) {
395 return BindingLocator::declarative(
396 name,
397 self.inner.index.get(),
398 binding.index,
399 self.inner.unique_id,
400 );
401 }
402 let mut flags = BindingFlags::MUTABLE;
403 flags.set(BindingFlags::LEX, !function_scope);
404 flags.set(BindingFlags::ESCAPES, self.is_global());
405 bindings.push(Binding {
406 name: name.clone(),
407 index: binding_index,
408 flags,
409 });
410 self.inner
411 .names
412 .borrow_mut()
413 .insert(name.clone(), bindings.len() - 1);
414 BindingLocator::declarative(
415 name,
416 self.inner.index.get(),
417 binding_index,
418 self.inner.unique_id,
419 )
420 }
421
422 #[allow(clippy::cast_possible_truncation)]
424 pub(crate) fn create_immutable_binding(&self, name: JsString, strict: bool) {
425 let mut bindings = self.inner.bindings.borrow_mut();
426 if self.position(&name) < usize::MAX {
427 return;
428 }
429 let binding_index = bindings.len() as u32;
430 let mut flags = BindingFlags::LEX;
431 flags.set(BindingFlags::STRICT, strict);
432 flags.set(BindingFlags::ESCAPES, self.is_global());
433 bindings.push(Binding {
434 name: name.clone(),
435 index: binding_index,
436 flags,
437 });
438 self.inner
439 .names
440 .borrow_mut()
441 .insert(name, bindings.len() - 1);
442 }
443
444 pub fn set_mutable_binding(
449 &self,
450 name: JsString,
451 ) -> Result<IdentifierReference, BindingLocatorError> {
452 Ok(
453 match self.inner.bindings.borrow().get(self.position(&name)) {
454 Some(binding) if binding.is_mutable() => IdentifierReference::new(
455 BindingLocator::declarative(
456 name,
457 self.inner.index.get(),
458 binding.index,
459 self.inner.unique_id,
460 ),
461 binding.is_lex(),
462 binding.escapes(),
463 ),
464 Some(binding) if binding.is_strict() => {
465 return Err(BindingLocatorError::MutateImmutable);
466 }
467 Some(_) => return Err(BindingLocatorError::Silent),
468 None => self.inner.outer.as_ref().map_or_else(
469 || {
470 Ok(IdentifierReference::new(
471 BindingLocator::global(name.clone()),
472 false,
473 true,
474 ))
475 },
476 |outer| outer.set_mutable_binding(name.clone()),
477 )?,
478 },
479 )
480 }
481
482 #[cfg(feature = "annex-b")]
483 pub fn set_mutable_binding_var(
488 &self,
489 name: JsString,
490 ) -> Result<IdentifierReference, BindingLocatorError> {
491 if !self.is_function() {
492 return self.inner.outer.as_ref().map_or_else(
493 || {
494 Ok(IdentifierReference::new(
495 BindingLocator::global(name.clone()),
496 false,
497 true,
498 ))
499 },
500 |outer| outer.set_mutable_binding_var(name.clone()),
501 );
502 }
503
504 Ok(
505 match self.inner.bindings.borrow().get(self.position(&name)) {
506 Some(binding) if binding.is_mutable() => IdentifierReference::new(
507 BindingLocator::declarative(
508 name,
509 self.inner.index.get(),
510 binding.index,
511 self.inner.unique_id,
512 ),
513 binding.is_lex(),
514 binding.escapes(),
515 ),
516 Some(binding) if binding.is_strict() => {
517 return Err(BindingLocatorError::MutateImmutable);
518 }
519 Some(_) => return Err(BindingLocatorError::Silent),
520 None => self.inner.outer.as_ref().map_or_else(
521 || {
522 Ok(IdentifierReference::new(
523 BindingLocator::global(name.clone()),
524 false,
525 true,
526 ))
527 },
528 |outer| outer.set_mutable_binding_var(name.clone()),
529 )?,
530 },
531 )
532 }
533
534 #[must_use]
536 pub fn outer(&self) -> Option<&Self> {
537 self.inner.outer.as_ref()
538 }
539
540 #[must_use]
542 pub fn unique_id(&self) -> u32 {
543 self.inner.unique_id
544 }
545}
546
547#[derive(Debug, PartialEq, Default)]
549struct ScopeContext {
550 unique_id_ctr: Cell<u32>,
557}
558
559impl ScopeContext {
560 fn next_unique_id(&self) -> u32 {
562 let id = self.unique_id_ctr.get() + 1;
563 self.unique_id_ctr.set(id);
564 id
565 }
566}
567
568#[derive(Clone, Debug, PartialEq, Eq, Hash)]
570pub struct IdentifierReference {
571 locator: BindingLocator,
572 lexical: bool,
573 escapes: bool,
574}
575
576impl IdentifierReference {
577 pub(crate) fn new(locator: BindingLocator, lexical: bool, escapes: bool) -> Self {
579 Self {
580 locator,
581 lexical,
582 escapes,
583 }
584 }
585
586 #[must_use]
588 pub fn locator(&self) -> BindingLocator {
589 self.locator.clone()
590 }
591
592 #[must_use]
594 pub fn local(&self) -> bool {
595 self.locator.scope > 0 && !self.escapes
596 }
597
598 #[must_use]
600 pub fn is_global_object(&self) -> bool {
601 self.locator.scope == 0
602 }
603
604 #[must_use]
606 pub fn is_lexical(&self) -> bool {
607 self.lexical
608 }
609}
610
611#[derive(Clone, Debug, Eq, Hash, PartialEq)]
613pub struct BindingLocator {
614 name: JsString,
616
617 scope: u32,
622
623 binding_index: u32,
625
626 unique_scope_id: u32,
627}
628
629impl BindingLocator {
630 pub(crate) const fn declarative(
632 name: JsString,
633 scope_index: u32,
634 binding_index: u32,
635 unique_scope_id: u32,
636 ) -> Self {
637 Self {
638 name,
639 scope: scope_index + 1,
640 binding_index,
641 unique_scope_id,
642 }
643 }
644
645 pub(super) const fn global(name: JsString) -> Self {
647 Self {
648 name,
649 scope: 0,
650 binding_index: 0,
651 unique_scope_id: 0,
652 }
653 }
654
655 #[must_use]
657 pub const fn name(&self) -> &JsString {
658 &self.name
659 }
660
661 #[must_use]
663 pub const fn is_global(&self) -> bool {
664 self.scope == 0
665 }
666
667 #[must_use]
669 pub fn scope(&self) -> BindingLocatorScope {
670 match self.scope {
671 0 => BindingLocatorScope::GlobalObject,
672 1 => BindingLocatorScope::GlobalDeclarative,
673 n => BindingLocatorScope::Stack(n - 2),
674 }
675 }
676
677 pub fn set_scope(&mut self, scope: BindingLocatorScope) {
679 self.scope = match scope {
680 BindingLocatorScope::GlobalObject => 0,
681 BindingLocatorScope::GlobalDeclarative => 1,
682 BindingLocatorScope::Stack(index) => index + 2,
683 };
684 }
685
686 #[must_use]
688 pub const fn binding_index(&self) -> u32 {
689 self.binding_index
690 }
691
692 pub fn set_binding_index(&mut self, index: u32) {
694 self.binding_index = index;
695 }
696
697 #[must_use]
699 pub fn unique_scope_id(&self) -> u32 {
700 self.unique_scope_id
701 }
702}
703
704#[derive(Copy, Clone, Debug)]
706pub enum BindingLocatorError {
707 MutateImmutable,
709
710 Silent,
712}
713
714#[derive(Clone, Copy, Debug, PartialEq, Eq)]
716pub enum BindingLocatorScope {
717 GlobalObject,
719
720 GlobalDeclarative,
722
723 Stack(u32),
725}
726
727#[derive(Clone, Debug, Default, PartialEq)]
729pub struct FunctionScopes {
730 pub(crate) function_scope: Scope,
731 pub(crate) parameters_eval_scope: Option<Scope>,
732 pub(crate) parameters_scope: Option<Scope>,
733 pub(crate) lexical_scope: Option<Scope>,
734 pub(crate) mapped_arguments_object: bool,
735 pub(crate) requires_function_scope: bool,
736}
737
738impl FunctionScopes {
739 #[must_use]
741 pub fn function_scope(&self) -> &Scope {
742 &self.function_scope
743 }
744
745 #[must_use]
747 pub fn arguments_object_accessed(&self) -> bool {
748 if self
749 .function_scope
750 .inner
751 .bindings
752 .borrow()
753 .first()
754 .as_ref()
755 .is_some_and(|b| b.name == "arguments" && b.is_accessed())
756 {
757 return true;
758 }
759
760 if let Some(scope) = &self.parameters_eval_scope
761 && scope
762 .inner
763 .bindings
764 .borrow()
765 .first()
766 .as_ref()
767 .is_some_and(|b| b.name == "arguments" && b.is_accessed())
768 {
769 return true;
770 }
771
772 false
773 }
774
775 #[must_use]
777 pub fn requires_function_scope(&self) -> bool {
778 self.requires_function_scope
779 }
780
781 #[must_use]
783 pub fn parameters_eval_scope(&self) -> Option<&Scope> {
784 self.parameters_eval_scope.as_ref()
785 }
786
787 #[must_use]
789 pub fn parameters_scope(&self) -> Option<&Scope> {
790 self.parameters_scope.as_ref()
791 }
792
793 #[must_use]
795 pub fn lexical_scope(&self) -> Option<&Scope> {
796 self.lexical_scope.as_ref()
797 }
798
799 #[must_use]
801 pub fn parameter_scope(&self) -> Scope {
802 if let Some(parameters_eval_scope) = &self.parameters_eval_scope {
803 return parameters_eval_scope.clone();
804 }
805 self.function_scope.clone()
806 }
807
808 pub(crate) fn body_scope(&self) -> Scope {
810 if let Some(lexical_scope) = &self.lexical_scope {
811 return lexical_scope.clone();
812 }
813 if let Some(parameters_scope) = &self.parameters_scope {
814 return parameters_scope.clone();
815 }
816 if let Some(parameters_eval_scope) = &self.parameters_eval_scope {
817 return parameters_eval_scope.clone();
818 }
819 self.function_scope.clone()
820 }
821
822 pub(crate) fn escape_all_bindings(&self) {
824 self.function_scope.escape_all_bindings();
825 if let Some(parameters_eval_scope) = &self.parameters_eval_scope {
826 parameters_eval_scope.escape_all_bindings();
827 }
828 if let Some(parameters_scope) = &self.parameters_scope {
829 parameters_scope.escape_all_bindings();
830 }
831 if let Some(lexical_scope) = &self.lexical_scope {
832 lexical_scope.escape_all_bindings();
833 }
834 }
835
836 pub(crate) fn reorder_binding_indices(&self) {
837 self.function_scope.reorder_binding_indices();
838 if let Some(parameters_eval_scope) = &self.parameters_eval_scope {
839 parameters_eval_scope.reorder_binding_indices();
840 }
841 if let Some(parameters_scope) = &self.parameters_scope {
842 parameters_scope.reorder_binding_indices();
843 }
844 if let Some(lexical_scope) = &self.lexical_scope {
845 lexical_scope.reorder_binding_indices();
846 }
847 }
848}
849
850#[cfg(feature = "arbitrary")]
851impl<'a> arbitrary::Arbitrary<'a> for FunctionScopes {
852 fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
853 Ok(Self {
854 function_scope: Scope::new_global(),
855 parameters_eval_scope: None,
856 parameters_scope: None,
857 lexical_scope: None,
858 mapped_arguments_object: false,
859 requires_function_scope: false,
860 })
861 }
862}