1#[cfg(test)]
4mod analyze_test;
5
6use deno_ast::swc::ast::*;
7use deno_ast::swc::common::SyntaxContext;
8use deno_ast::swc::utils::ExprCtx;
9use deno_ast::swc::{
10 ecma_visit::{noop_visit_type, Visit, VisitWith},
11 utils::{ExprExt, Value},
12};
13use deno_ast::view;
14use deno_ast::SourcePos;
15use deno_ast::SourceRangedForSpanned;
16use std::{
17 collections::{BTreeMap, HashSet},
18 mem::take,
19};
20
21#[derive(Debug, Clone)]
22pub struct ControlFlow {
23 meta: BTreeMap<SourcePos, Metadata>,
24}
25
26impl ControlFlow {
27 pub fn analyze(
28 program: view::Program,
29 unresolved_ctxt: SyntaxContext,
30 ) -> Self {
31 let mut v = Analyzer {
32 scope: Scope::new(None, BlockKind::Program),
33 info: Default::default(),
34 expr_ctxt: ExprCtx {
35 unresolved_ctxt,
36 is_unresolved_ref_safe: false,
37 in_strict: true,
38 remaining_depth: 4, },
40 };
41 match program {
42 view::Program::Module(module) => module.inner.visit_with(&mut v),
43 view::Program::Script(script) => script.inner.visit_with(&mut v),
44 }
45 ControlFlow { meta: v.info }
46 }
47
48 pub fn meta(&self, start_pos: SourcePos) -> Option<&Metadata> {
53 self.meta.get(&start_pos)
54 }
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum BlockKind {
60 Program,
62 Function,
64 Case,
66 If,
68 Loop,
70 Label(Id),
71 Catch,
73 Finally,
75}
76
77#[derive(Debug, Default, Clone, PartialEq, Eq)]
78pub struct Metadata {
79 pub unreachable: bool,
80 end: Option<End>,
81}
82
83impl Metadata {
84 pub fn stops_execution(&self) -> bool {
86 self
87 .end
88 .is_some_and(|d| matches!(d, End::Forced { .. } | End::Break))
89 }
90
91 pub fn continues_execution(&self) -> bool {
93 self.end.is_none_or(|d| d == End::Continue)
94 }
95}
96
97#[derive(Debug)]
98struct Analyzer<'a> {
99 scope: Scope<'a>,
100 info: BTreeMap<SourcePos, Metadata>,
101 expr_ctxt: ExprCtx,
102}
103
104#[derive(Debug)]
105struct Scope<'a> {
106 _parent: Option<&'a Scope<'a>>,
107 used_hoistable_ids: HashSet<Id>,
111
112 _kind: BlockKind,
113
114 end: Option<End>,
116
117 may_throw: bool,
118
119 found_break: Option<Option<Id>>,
124 found_continue: bool,
125}
126
127#[derive(Debug, Copy, Clone, PartialEq, Eq)]
128enum End {
129 Forced {
148 ret: bool,
150 throw: bool,
152 infinite_loop: bool,
154 },
155
156 Break,
158
159 Continue,
163}
164
165impl End {
166 fn forced_return() -> Self {
167 End::Forced {
168 ret: true,
169 throw: false,
170 infinite_loop: false,
171 }
172 }
173
174 fn forced_throw() -> Self {
175 End::Forced {
176 ret: false,
177 throw: true,
178 infinite_loop: false,
179 }
180 }
181
182 fn forced_infinite_loop() -> Self {
183 End::Forced {
184 ret: false,
185 throw: false,
186 infinite_loop: true,
187 }
188 }
189
190 fn merge_forced(self, other: Self) -> Option<Self> {
191 match (self, other) {
192 (
193 End::Forced {
194 ret: r1,
195 throw: t1,
196 infinite_loop: i1,
197 },
198 End::Forced {
199 ret: r2,
200 throw: t2,
201 infinite_loop: i2,
202 },
203 ) => Some(End::Forced {
204 ret: r1 || r2,
205 throw: t1 || t2,
206 infinite_loop: i1 || i2,
207 }),
208 _ => None,
209 }
210 }
211
212 fn is_forced(&self) -> bool {
213 matches!(self, End::Forced { .. })
214 }
215}
216
217impl<'a> Scope<'a> {
218 pub fn new(parent: Option<&'a Scope<'a>>, kind: BlockKind) -> Self {
219 Self {
220 _parent: parent,
221 _kind: kind,
222 used_hoistable_ids: Default::default(),
223 end: None,
224 may_throw: false,
225 found_break: None,
226 found_continue: false,
227 }
228 }
229}
230
231impl Analyzer<'_> {
232 pub(super) fn with_child_scope<F>(
234 &mut self,
235 kind: BlockKind,
236 start_pos: SourcePos,
237 op: F,
238 ) where
239 F: for<'any> FnOnce(&mut Analyzer<'any>),
240 {
241 let prev_end = self.scope.end;
242 let (info, end, hoist, found_break, found_continue, may_throw) = {
243 let mut child = Analyzer {
244 info: take(&mut self.info),
245 scope: Scope::new(Some(&self.scope), kind.clone()),
246 expr_ctxt: self.expr_ctxt,
247 };
248 match kind {
249 BlockKind::Function => {}
250 _ => match prev_end {
251 Some(e) if matches!(e, End::Forced { .. }) => {
252 child.scope.end = Some(e)
253 }
254 _ => {}
255 },
256 }
257
258 op(&mut child);
259
260 (
261 take(&mut child.info),
262 child.scope.end,
263 child.scope.used_hoistable_ids,
264 child.scope.found_break,
265 child.scope.found_continue,
266 child.scope.may_throw,
267 )
268 };
269
270 self.info = info;
271 self.scope.used_hoistable_ids.extend(hoist);
272
273 self.scope.may_throw |= may_throw;
275
276 self.scope.found_continue |= found_continue;
277
278 match kind {
279 BlockKind::Case => {}
280 BlockKind::Function => {}
281 BlockKind::Loop => {}
282 _ => {
283 if self.scope.found_break.is_none() {
284 self.scope.found_break = found_break;
285 }
286 }
287 };
288
289 if let Some(end) = end {
290 match kind {
291 BlockKind::Program => {}
292 BlockKind::Function => {
293 match end {
294 End::Forced { .. } | End::Continue => {
295 self.mark_as_end(start_pos, end)
296 }
297 _ => { }
298 }
299 self.scope.end = prev_end;
300 }
301 BlockKind::Case => {}
302 BlockKind::If => {}
303 BlockKind::Loop => match end {
304 End::Break | End::Continue => {
305 self.mark_as_end(start_pos, end);
306 self.scope.end = prev_end;
307 }
308 e => {
309 self.mark_as_end(start_pos, e);
310 self.scope.end = Some(e);
311 }
312 },
313 BlockKind::Label(label) => {
314 if let Some(Some(id)) = &self.scope.found_break {
315 if *id == label {
316 self.scope.found_break = None;
318 }
319 }
320 }
321 BlockKind::Catch => {
322 self.mark_as_end(start_pos, end);
323 }
324 BlockKind::Finally => {
325 self.mark_as_end(start_pos, end);
326 }
327 }
328 }
329 }
330
331 fn get_end_reason(&self, start: SourcePos) -> Option<End> {
332 self.info.get(&start).and_then(|md| md.end)
333 }
334
335 fn mark_as_end(&mut self, start: SourcePos, end: End) {
337 let new_end = match self.scope.end {
338 None | Some(End::Continue) => {
343 self.scope.end = Some(end);
344 Some(end)
345 }
346 Some(End::Break) => Some(end),
347 Some(e) => e.merge_forced(end).or(self.scope.end),
348 };
349
350 self.info.entry(start).or_default().end = new_end;
351 }
352
353 fn visit_stmt_or_block(&mut self, s: &Stmt) {
358 s.visit_with(self);
359
360 match s {
362 Stmt::Break(..) | Stmt::Continue(..) => {
363 self.mark_as_end(s.start(), End::Break)
364 }
365 _ => {}
366 }
367 }
368}
369
370impl Visit for Analyzer<'_> {
371 noop_visit_type!();
372
373 fn visit_return_stmt(&mut self, n: &ReturnStmt) {
374 n.visit_children_with(self);
375 self.mark_as_end(n.start(), End::forced_return());
376 }
377
378 fn visit_throw_stmt(&mut self, n: &ThrowStmt) {
379 n.visit_children_with(self);
380 self.mark_as_end(n.start(), End::forced_throw());
381 }
382
383 fn visit_break_stmt(&mut self, n: &BreakStmt) {
384 if let Some(label) = &n.label {
385 let label = label.to_id();
386 self.scope.found_break = Some(Some(label));
387 } else {
388 self.scope.found_break = Some(None);
389 }
390 }
391
392 fn visit_continue_stmt(&mut self, _: &ContinueStmt) {
393 self.scope.found_continue = true;
394 }
395
396 fn visit_block_stmt(&mut self, s: &BlockStmt) {
397 s.visit_children_with(self);
398
399 if let Some(end) = self.scope.end {
400 self.mark_as_end(s.start(), end);
401 } else {
402 self.mark_as_end(s.start(), End::Continue);
403 }
404 }
405
406 fn visit_stmts(&mut self, stmts: &[Stmt]) {
407 for stmt in stmts {
408 self.visit_stmt_or_block(stmt);
409 }
410 }
411
412 fn visit_expr(&mut self, n: &Expr) {
413 n.visit_children_with(self);
414
415 if matches!(self.scope.end, None | Some(End::Continue)) {
416 match n {
417 Expr::Ident(i) => {
418 self.scope.used_hoistable_ids.insert(i.to_id());
419 }
420 Expr::This(..) => {}
421 _ => {
422 self.scope.may_throw = true;
423 }
424 }
425 }
426 }
427
428 fn visit_member_expr(&mut self, n: &MemberExpr) {
429 n.obj.visit_with(self);
430 if let MemberProp::Computed(computed_prop) = &n.prop {
431 computed_prop.visit_with(self);
432 }
433 }
434
435 fn visit_arrow_expr(&mut self, n: &ArrowExpr) {
436 self.with_child_scope(BlockKind::Function, n.start(), |a| {
437 n.visit_children_with(a);
438 })
439 }
440
441 fn visit_function(&mut self, n: &Function) {
442 self.with_child_scope(BlockKind::Function, n.start(), |a| {
443 n.visit_children_with(a);
444 })
445 }
446
447 fn visit_catch_clause(&mut self, n: &CatchClause) {
448 self.with_child_scope(BlockKind::Catch, n.start(), |a| {
449 n.visit_children_with(a);
450 });
451 }
452
453 fn visit_constructor(&mut self, n: &Constructor) {
454 self.with_child_scope(BlockKind::Function, n.start(), |a| {
455 n.visit_children_with(a);
456 });
457 }
458
459 fn visit_getter_prop(&mut self, n: &GetterProp) {
460 self.with_child_scope(BlockKind::Function, n.start(), |a| {
461 n.visit_children_with(a);
462 })
463 }
464
465 fn visit_setter_prop(&mut self, n: &SetterProp) {
466 self.with_child_scope(BlockKind::Function, n.start(), |a| {
467 n.visit_children_with(a);
468 })
469 }
470
471 fn visit_switch_stmt(&mut self, n: &SwitchStmt) {
472 let prev_end = self.scope.end;
473 n.visit_children_with(self);
474
475 let end = {
476 let has_default = n.cases.iter().any(|case| case.test.is_none());
477 let forced_end = n
478 .cases
479 .iter()
480 .filter_map(|case| self.get_end_reason(case.start()))
481 .try_fold(
482 End::Forced {
483 ret: false,
484 throw: false,
485 infinite_loop: false,
486 },
487 |acc, cur| acc.merge_forced(cur),
488 );
489
490 match forced_end {
491 Some(e) if has_default => e,
492 _ => End::Continue,
493 }
494 };
495
496 self.mark_as_end(n.start(), end);
497
498 if !matches!(end, End::Forced { .. }) {
499 self.scope.end = prev_end;
500 }
501 }
502
503 fn visit_switch_case(&mut self, n: &SwitchCase) {
504 let prev_end = self.scope.end;
505 let mut case_end = None;
506
507 self.with_child_scope(BlockKind::Case, n.start(), |a| {
508 n.cons.visit_with(a);
509
510 if a.scope.found_break.is_some() {
511 case_end = Some(End::Break);
512 } else if matches!(a.scope.end, Some(End::Forced { .. })) {
513 case_end = a.scope.end;
514 }
515 });
516
517 if let Some(end) = case_end {
518 self.mark_as_end(n.start(), end);
519 } else {
520 self.mark_as_end(n.start(), End::Continue);
521 }
522
523 self.scope.end = prev_end;
524 }
525
526 fn visit_if_stmt(&mut self, n: &IfStmt) {
527 n.test.visit_with(self);
528
529 let prev_end = self.scope.end;
530
531 self.with_child_scope(BlockKind::If, n.cons.start(), |a| {
532 a.visit_stmt_or_block(&n.cons);
533 });
534
535 let cons_reason = self.get_end_reason(n.cons.start());
536
537 match &n.alt {
538 Some(alt) => {
539 self.with_child_scope(BlockKind::If, alt.start(), |a| {
540 a.visit_stmt_or_block(alt);
541 });
542 let alt_reason = self.get_end_reason(alt.start());
543
544 match (cons_reason, alt_reason) {
545 (Some(x), Some(y)) if x.is_forced() && y.is_forced() => {
546 let end = x.merge_forced(y).unwrap();
548 self.mark_as_end(n.start(), end);
549 }
550 (Some(End::Break), Some(End::Break))
551 | (Some(End::Forced { .. }), Some(End::Break))
552 | (Some(End::Break), Some(End::Forced { .. })) => {
553 self.mark_as_end(n.start(), End::Break);
554 }
555 _ => {
557 self.mark_as_end(n.start(), End::Continue);
558 }
559 }
560 }
561 None => {
562 self.mark_as_end(n.start(), End::Continue);
563 self.scope.end = prev_end;
564 }
565 }
566 }
567
568 fn visit_stmt(&mut self, n: &Stmt) {
569 let scope_end = self
570 .scope
571 .end
572 .is_some_and(|d| matches!(d, End::Forced { .. } | End::Break));
573
574 let unreachable = if scope_end {
575 match n {
577 Stmt::Empty(..) => false,
578 Stmt::Decl(Decl::Fn(FnDecl { ident, .. }))
579 if self.scope.used_hoistable_ids.contains(&ident.to_id()) =>
580 {
581 false
582 }
583 Stmt::Decl(Decl::Var(decl))
584 if decl.kind == VarDeclKind::Var
585 && decl.decls.iter().all(|decl| decl.init.is_none()) =>
586 {
587 false
588 }
589 _ => true,
591 }
592 } else {
593 false
594 };
595
596 self.info.entry(n.start()).or_default().unreachable = unreachable;
597
598 n.visit_children_with(self);
599 }
600
601 fn visit_for_stmt(&mut self, n: &ForStmt) {
604 n.init.visit_with(self);
605 n.update.visit_with(self);
606 n.test.visit_with(self);
607
608 let mut forced_end = None;
609 let expr_ctxt = self.expr_ctxt;
610
611 self.with_child_scope(BlockKind::Loop, n.body.start(), |a| {
612 n.body.visit_with(a);
613
614 let has_break = matches!(a.scope.found_break, Some(None));
615
616 if !has_break {
617 let end = match a.get_end_reason(n.body.start()) {
618 Some(e) if e.is_forced() => e,
619 _ => End::forced_infinite_loop(),
620 };
621 match &n.test {
622 None => {
623 a.mark_as_end(n.start(), end);
624 forced_end = Some(end);
625 }
626 Some(test) => {
627 if matches!(test.cast_to_bool(expr_ctxt), (_, Value::Known(true))) {
628 a.mark_as_end(n.start(), end);
629 forced_end = Some(end);
630 }
631 }
632 }
633 }
634
635 if forced_end.is_none() || has_break {
636 a.mark_as_end(n.body.start(), End::Continue);
637 a.scope.end = Some(End::Continue);
638 }
639 });
640 }
641
642 fn visit_for_of_stmt(&mut self, n: &ForOfStmt) {
643 let body_lo = n.body.start();
644
645 n.right.visit_with(self);
646
647 self.with_child_scope(BlockKind::Loop, body_lo, |a| {
648 n.body.visit_with(a);
649
650 a.mark_as_end(body_lo, End::Continue);
653 a.scope.end = Some(End::Continue);
654 });
655 }
656
657 fn visit_for_in_stmt(&mut self, n: &ForInStmt) {
658 let body_lo = n.body.start();
659
660 n.right.visit_with(self);
661
662 self.with_child_scope(BlockKind::Loop, body_lo, |a| {
663 n.body.visit_with(a);
664
665 a.mark_as_end(body_lo, End::Continue);
668 a.scope.end = Some(End::Continue);
669 });
670 }
671
672 fn visit_while_stmt(&mut self, n: &WhileStmt) {
673 let body_lo = n.body.start();
674 let expr_ctxt = self.expr_ctxt;
675
676 self.with_child_scope(BlockKind::Loop, body_lo, |a| {
677 n.body.visit_with(a);
678
679 let unconditionally_enter =
680 matches!(n.test.cast_to_bool(expr_ctxt), (_, Value::Known(true)));
681 let end_reason = a.get_end_reason(body_lo);
682 let return_or_throw = end_reason.is_some_and(|e| e.is_forced());
683 let has_break = matches!(a.scope.found_break, Some(None));
684
685 if unconditionally_enter && return_or_throw && !has_break {
686 a.mark_as_end(body_lo, end_reason.unwrap());
689 a.scope.end = end_reason;
690 } else if unconditionally_enter && !has_break {
691 let end = End::forced_infinite_loop();
692 a.mark_as_end(body_lo, end);
693 a.scope.end = Some(end);
694 } else {
695 a.mark_as_end(body_lo, End::Continue);
696 a.scope.end = Some(End::Continue);
697 }
698 });
699
700 n.test.visit_with(self);
701 }
702
703 fn visit_do_while_stmt(&mut self, n: &DoWhileStmt) {
704 let body_lo = n.body.start();
705 let expr_ctxt = self.expr_ctxt;
706
707 self.with_child_scope(BlockKind::Loop, body_lo, |a| {
708 n.body.visit_with(a);
709
710 let end_reason = a.get_end_reason(body_lo);
711 let return_or_throw = end_reason.is_some_and(|e| e.is_forced());
712 let infinite_loop =
713 matches!(n.test.cast_to_bool(expr_ctxt), (_, Value::Known(true)))
714 && a.scope.found_break.is_none();
715 let has_break = matches!(a.scope.found_break, Some(None));
716
717 if return_or_throw && !has_break {
718 a.mark_as_end(body_lo, end_reason.unwrap());
721 a.scope.end = end_reason;
722 } else if infinite_loop {
723 let end = End::forced_infinite_loop();
724 a.mark_as_end(body_lo, end);
725 a.scope.end = Some(end);
726 } else {
727 a.mark_as_end(body_lo, End::Continue);
728 a.scope.end = Some(End::Continue);
729 }
730 });
731
732 match self.get_end_reason(body_lo) {
733 Some(e) if e.is_forced() => {
734 self.mark_as_end(n.start(), e);
735 }
736 _ => {}
737 }
738
739 n.test.visit_with(self);
740 }
741
742 fn visit_try_stmt(&mut self, n: &TryStmt) {
743 let old_throw = self.scope.may_throw;
744
745 let prev_end = self.scope.end;
746
747 self.scope.may_throw = false;
748 n.block.visit_with(self);
749
750 let try_block_end = self.scope.end;
751 let try_block_may_throw = self.scope.may_throw;
752
753 if let Some(handler) = &n.handler {
754 if try_block_may_throw {
755 self.scope.end = prev_end;
756 }
757 self.scope.may_throw = false;
758 handler.visit_with(self);
759
760 if try_block_may_throw {
761 match (try_block_end, self.scope.end) {
762 (
763 Some(End::Forced {
764 ret: false,
765 throw: true,
766 infinite_loop: false,
767 }),
768 _,
769 ) => {}
770 (Some(x), Some(y)) if x.is_forced() && y.is_forced() => {
771 self.scope.end = Some(x.merge_forced(y).unwrap());
773 }
774 (_, Some(y)) if y.is_forced() => {
775 self.scope.end = try_block_end;
776 }
777 (None | Some(End::Continue), Some(End::Break)) => {
778 self.scope.end = try_block_end;
779 }
780 _ => {}
781 }
782 } else {
783 self.scope.end = try_block_end;
784 }
785 }
786
787 if let Some(finalizer) = &n.finalizer {
788 let try_catch_end = self.scope.end;
789 self.scope.end = prev_end;
790 self.with_child_scope(BlockKind::Finally, finalizer.start(), |a| {
791 n.finalizer.visit_with(a);
792 });
793 match (try_catch_end, self.scope.end) {
794 (Some(x), Some(End::Break)) if x.is_forced() => {
795 self.scope.end = Some(x);
796 }
797 (Some(x), None | Some(End::Continue)) => {
798 self.scope.end = Some(x);
799 }
800 _ => {}
801 }
802 }
803
804 if let Some(end) = self.scope.end {
805 self.mark_as_end(n.start(), end);
806 }
807 self.scope.may_throw |= old_throw;
808 }
809
810 fn visit_labeled_stmt(&mut self, n: &LabeledStmt) {
811 self.with_child_scope(BlockKind::Label(n.label.to_id()), n.start(), |a| {
812 a.visit_stmt_or_block(&n.body);
813 });
814 }
815}