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