1 /* valaflowanalyzer.vala
3 * Copyright (C) 2008-2009 Jürg Billeter
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 * Jürg Billeter <j@bitron.ch>
27 * Code visitor building the control flow graph.
29 public class Vala
.FlowAnalyzer
: CodeVisitor
{
30 private class JumpTarget
{
31 public bool is_break_target
{ get; set; }
32 public bool is_continue_target
{ get; set; }
33 public bool is_return_target
{ get; set; }
34 public bool is_error_target
{ get; set; }
35 public ErrorDomain? error_domain
{ get; set; }
36 public ErrorCode? error_code
{ get; set; }
37 public bool is_finally_clause
{ get; set; }
38 public BasicBlock basic_block
{ get; set; }
39 public BasicBlock? last_block
{ get; set; }
40 public CatchClause? catch_clause
{ get; set; }
42 public JumpTarget
.break_target (BasicBlock basic_block
) {
43 this
.basic_block
= basic_block
;
44 is_break_target
= true;
47 public JumpTarget
.continue_target (BasicBlock basic_block
) {
48 this
.basic_block
= basic_block
;
49 is_continue_target
= true;
52 public JumpTarget
.return_target (BasicBlock basic_block
) {
53 this
.basic_block
= basic_block
;
54 is_return_target
= true;
57 public JumpTarget
.error_target (BasicBlock basic_block
, CatchClause catch_clause
, ErrorDomain? error_domain
, ErrorCode? error_code
) {
58 this
.basic_block
= basic_block
;
59 this
.catch_clause
= catch_clause
;
60 this
.error_domain
= error_domain
;
61 this
.error_code
= error_code
;
62 is_error_target
= true;
65 public JumpTarget
.any_target (BasicBlock basic_block
) {
66 this
.basic_block
= basic_block
;
67 is_break_target
= true;
68 is_continue_target
= true;
69 is_return_target
= true;
70 is_error_target
= true;
73 public JumpTarget
.finally_clause (BasicBlock basic_block
, BasicBlock last_block
) {
74 this
.basic_block
= basic_block
;
75 this
.last_block
= last_block
;
76 is_finally_clause
= true;
80 private CodeContext context
;
81 private BasicBlock current_block
;
82 private bool unreachable_reported
;
83 private Gee
.List
<JumpTarget
> jump_stack
= new ArrayList
<JumpTarget
> ();
85 Map
<Symbol
, Gee
.List
<LocalVariable
>> var_map
;
86 Set
<LocalVariable
> used_vars
;
87 Map
<LocalVariable
, PhiFunction
> phi_functions
;
89 public FlowAnalyzer () {
93 * Build control flow graph in the specified context.
95 * @param context a code context
97 public void analyze (CodeContext context
) {
98 this
.context
= context
;
100 /* we're only interested in non-pkg source files */
101 var source_files
= context
.get_source_files ();
102 foreach (SourceFile file
in source_files
) {
103 if (!file
.external_package
) {
109 public override void visit_source_file (SourceFile source_file
) {
110 source_file
.accept_children (this
);
113 public override void visit_class (Class cl
) {
114 cl
.accept_children (this
);
117 public override void visit_struct (Struct st
) {
118 st
.accept_children (this
);
121 public override void visit_interface (Interface iface
) {
122 iface
.accept_children (this
);
125 public override void visit_enum (Enum en
) {
126 en
.accept_children (this
);
129 public override void visit_error_domain (ErrorDomain ed
) {
130 ed
.accept_children (this
);
133 public override void visit_field (Field f
) {
134 if (f
.is_internal_symbol () && !f
.used
) {
135 Report
.warning (f
.source_reference
, "field `%s' never used".printf (f
.get_full_name ()));
139 public override void visit_method (Method m
) {
140 if (m
.is_internal_symbol () && !m
.used
&& !m
.entry_point
141 && !m
.overrides
&& (m
.base_interface_method
== null || m
.base_interface_method
== m
)
142 && !(m is CreationMethod
)) {
143 Report
.warning (m
.source_reference
, "method `%s' never used".printf (m
.get_full_name ()));
146 if (m
.body
== null) {
150 m
.entry_block
= new BasicBlock
.entry ();
151 m
.exit_block
= new BasicBlock
.exit ();
153 current_block
= new
BasicBlock ();
154 m
.entry_block
.connect (current_block
);
156 jump_stack
.add (new JumpTarget
.return_target (m
.exit_block
));
158 m
.accept_children (this
);
160 jump_stack
.remove_at (jump_stack
.size
- 1);
162 if (current_block
!= null) {
163 // end of method body reachable
165 if (!(m
.return_type is VoidType
)) {
166 Report
.error (m
.source_reference
, "missing return statement at end of method body");
170 current_block
.connect (m
.exit_block
);
173 build_dominator_tree (m
.entry_block
);
174 build_dominator_frontier (m
.entry_block
);
175 insert_phi_functions (m
.entry_block
);
176 check_variables (m
.entry_block
);
179 Gee
.List
<BasicBlock
> get_depth_first_list (BasicBlock entry_block
) {
180 var list
= new ArrayList
<BasicBlock
> ();
181 depth_first_traverse (entry_block
, list
);
185 void depth_first_traverse (BasicBlock current
, Gee
.List
<BasicBlock
> list
) {
186 if (current
in list
) {
190 foreach (BasicBlock succ
in current
.get_successors ()) {
191 depth_first_traverse (succ
, list
);
195 void build_dominator_tree (BasicBlock entry_block
) {
196 // set dom(n) = {E,1,2...,N,X} forall n
197 var dom
= new HashMap
<BasicBlock
, Set
<BasicBlock
>> ();
198 var block_list
= get_depth_first_list (entry_block
);
199 foreach (BasicBlock block
in block_list
) {
200 var block_set
= new HashSet
<BasicBlock
> ();
201 foreach (BasicBlock b
in block_list
) {
204 dom
.set (block
, block_set
);
208 var entry_dom_set
= new HashSet
<BasicBlock
> ();
209 entry_dom_set
.add (entry_block
);
210 dom
.set (entry_block
, entry_dom_set
);
215 foreach (BasicBlock block
in block_list
) {
216 // intersect dom(pred) forall pred: pred = predecessor(s)
217 var dom_set
= new HashSet
<BasicBlock
> ();
219 foreach (BasicBlock pred
in block
.get_predecessors ()) {
220 var pred_dom_set
= dom
.get (pred
);
222 foreach (BasicBlock dom_block
in pred_dom_set
) {
223 dom_set
.add (dom_block
);
227 var remove_queue
= new ArrayList
<BasicBlock
> ();
228 foreach (BasicBlock dom_block
in dom_set
) {
229 if (!(dom_block
in pred_dom_set
)) {
230 remove_queue
.add (dom_block
);
233 foreach (BasicBlock dom_block
in remove_queue
) {
234 dom_set
.remove (dom_block
);
242 if (dom
.get (block
).size
!= dom_set
.size
) {
245 foreach (BasicBlock dom_block
in dom
.get (block
)) {
246 if (!(dom_block
in dom_set
)) {
252 dom
.set (block
, dom_set
);
257 foreach (BasicBlock block
in block_list
) {
258 if (block
== entry_block
) {
262 BasicBlock immediate_dominator
= null;
263 foreach (BasicBlock dominator
in dom
.get (block
)) {
264 if (dominator
== block
) {
268 if (immediate_dominator
== null) {
269 immediate_dominator
= dominator
;
271 // if immediate_dominator dominates dominator,
272 // update immediate_dominator
273 if (immediate_dominator
in dom
.get (dominator
)) {
274 immediate_dominator
= dominator
;
279 immediate_dominator
.add_child (block
);
283 void build_dominator_frontier (BasicBlock entry_block
) {
284 var block_list
= get_depth_first_list (entry_block
);
285 for (int i
= block_list
.size
- 1; i
>= 0; i
--) {
286 var block
= block_list
[i
];
288 foreach (BasicBlock succ
in block
.get_successors ()) {
289 // if idom(succ) != block
290 if (succ
.parent
!= block
) {
291 block
.add_dominator_frontier (succ
);
295 foreach (BasicBlock child
in block
.get_children ()) {
296 foreach (BasicBlock child_frontier
in child
.get_dominator_frontier ()) {
297 // if idom(child_frontier) != block
298 if (child_frontier
.parent
!= block
) {
299 block
.add_dominator_frontier (child_frontier
);
306 Map
<LocalVariable
, Set
<BasicBlock
>> get_assignment_map (BasicBlock entry_block
) {
307 var map
= new HashMap
<LocalVariable
, Set
<BasicBlock
>> ();
308 foreach (BasicBlock block
in get_depth_first_list (entry_block
)) {
309 var defined_variables
= new ArrayList
<LocalVariable
> ();
310 foreach (CodeNode node
in block
.get_nodes ()) {
311 node
.get_defined_variables (defined_variables
);
314 foreach (LocalVariable local
in defined_variables
) {
315 var block_set
= map
.get (local
);
316 if (block_set
== null) {
317 block_set
= new HashSet
<BasicBlock
> ();
318 map
.set (local
, block_set
);
320 block_set
.add (block
);
326 void insert_phi_functions (BasicBlock entry_block
) {
327 var assign
= get_assignment_map (entry_block
);
330 var work_list
= new ArrayList
<BasicBlock
> ();
332 var added
= new HashMap
<BasicBlock
, int> ();
333 var phi
= new HashMap
<BasicBlock
, int> ();
334 foreach (BasicBlock block
in get_depth_first_list (entry_block
)) {
335 added
.set (block
, 0);
339 foreach (LocalVariable local
in assign
.get_keys ()) {
341 foreach (BasicBlock block
in assign
.get (local
)) {
342 work_list
.add (block
);
343 added
.set (block
, counter
);
345 while (work_list
.size
> 0) {
346 BasicBlock block
= work_list
.get (0);
347 work_list
.remove_at (0);
348 foreach (BasicBlock frontier
in block
.get_dominator_frontier ()) {
349 int blockPhi
= phi
.get (frontier
);
350 if (blockPhi
< counter
) {
351 frontier
.add_phi_function (new
PhiFunction (local
, frontier
.get_predecessors ().size
));
352 phi
.set (frontier
, counter
);
353 int block_added
= added
.get (frontier
);
354 if (block_added
< counter
) {
355 added
.set (frontier
, counter
);
356 work_list
.add (frontier
);
364 void check_variables (BasicBlock entry_block
) {
365 var_map
= new HashMap
<Symbol
, Gee
.List
<LocalVariable
>>();
366 used_vars
= new HashSet
<LocalVariable
> ();
367 phi_functions
= new HashMap
<LocalVariable
, PhiFunction
> ();
369 check_block_variables (entry_block
);
371 // check for variables used before initialization
372 var used_vars_queue
= new ArrayList
<LocalVariable
> ();
373 foreach (LocalVariable local
in used_vars
) {
374 used_vars_queue
.add (local
);
376 while (used_vars_queue
.size
> 0) {
377 LocalVariable used_var
= used_vars_queue
[0];
378 used_vars_queue
.remove_at (0);
380 PhiFunction phi
= phi_functions
.get (used_var
);
382 foreach (LocalVariable local
in phi
.operands
) {
384 Report
.error (used_var
.source_reference
, "use of possibly unassigned local variable `%s'".printf (used_var
.name
));
387 if (!(local
in used_vars
)) {
388 local
.source_reference
= used_var
.source_reference
;
389 used_vars
.add (local
);
390 used_vars_queue
.add (local
);
397 void check_block_variables (BasicBlock block
) {
398 foreach (PhiFunction phi
in block
.get_phi_functions ()) {
399 LocalVariable versioned_var
= process_assignment (var_map
, phi
.original_variable
);
401 phi_functions
.set (versioned_var
, phi
);
404 foreach (CodeNode node
in block
.get_nodes ()) {
405 var used_variables
= new ArrayList
<LocalVariable
> ();
406 node
.get_used_variables (used_variables
);
408 foreach (LocalVariable var_symbol
in used_variables
) {
409 var variable_stack
= var_map
.get (var_symbol
);
410 if (variable_stack
== null || variable_stack
.size
== 0) {
411 Report
.error (node
.source_reference
, "use of possibly unassigned local variable `%s'".printf (var_symbol
.name
));
414 var versioned_local
= variable_stack
.get (variable_stack
.size
- 1);
415 if (!(versioned_local
in used_vars
)) {
416 versioned_local
.source_reference
= node
.source_reference
;
418 used_vars
.add (versioned_local
);
421 var defined_variables
= new ArrayList
<LocalVariable
> ();
422 node
.get_defined_variables (defined_variables
);
424 foreach (LocalVariable local
in defined_variables
) {
425 process_assignment (var_map
, local
);
429 foreach (BasicBlock succ
in block
.get_successors ()) {
431 foreach (BasicBlock pred
in succ
.get_predecessors ()) {
438 foreach (PhiFunction phi
in succ
.get_phi_functions ()) {
439 var variable_stack
= var_map
.get (phi
.original_variable
);
440 if (variable_stack
!= null && variable_stack
.size
> 0) {
441 phi
.operands
.set (j
, variable_stack
.get (variable_stack
.size
- 1));
446 foreach (BasicBlock child
in block
.get_children ()) {
447 check_block_variables (child
);
450 foreach (PhiFunction phi
in block
.get_phi_functions ()) {
451 var variable_stack
= var_map
.get (phi
.original_variable
);
452 variable_stack
.remove_at (variable_stack
.size
- 1);
454 foreach (CodeNode node
in block
.get_nodes ()) {
455 var defined_variables
= new ArrayList
<LocalVariable
> ();
456 node
.get_defined_variables (defined_variables
);
458 foreach (LocalVariable local
in defined_variables
) {
459 var variable_stack
= var_map
.get (local
);
460 variable_stack
.remove_at (variable_stack
.size
- 1);
465 LocalVariable
process_assignment (Map
<Symbol
, Gee
.List
<LocalVariable
>> var_map
, LocalVariable var_symbol
) {
466 var variable_stack
= var_map
.get (var_symbol
);
467 if (variable_stack
== null) {
468 variable_stack
= new ArrayList
<LocalVariable
> ();
469 var_map
.set (var_symbol
, variable_stack
);
471 LocalVariable versioned_var
= new
LocalVariable (var_symbol
.variable_type
, var_symbol
.name
, null, var_symbol
.source_reference
);
472 variable_stack
.add (versioned_var
);
473 return versioned_var
;
476 public override void visit_creation_method (CreationMethod m
) {
480 public override void visit_property (Property prop
) {
481 prop
.accept_children (this
);
484 public override void visit_property_accessor (PropertyAccessor acc
) {
485 if (acc
.body
== null) {
489 acc
.entry_block
= new BasicBlock
.entry ();
490 acc
.exit_block
= new BasicBlock
.exit ();
492 current_block
= new
BasicBlock ();
493 acc
.entry_block
.connect (current_block
);
495 jump_stack
.add (new JumpTarget
.return_target (acc
.exit_block
));
497 acc
.accept_children (this
);
499 jump_stack
.remove_at (jump_stack
.size
- 1);
501 if (current_block
!= null) {
502 // end of property accessor body reachable
505 Report
.error (acc
.source_reference
, "missing return statement at end of property getter body");
509 current_block
.connect (acc
.exit_block
);
512 build_dominator_tree (acc
.entry_block
);
513 build_dominator_frontier (acc
.entry_block
);
514 insert_phi_functions (acc
.entry_block
);
515 check_variables (acc
.entry_block
);
518 public override void visit_block (Block b
) {
519 b
.accept_children (this
);
522 public override void visit_declaration_statement (DeclarationStatement stmt
) {
523 if (unreachable (stmt
)) {
527 if (!stmt
.declaration
.used
) {
528 Report
.warning (stmt
.declaration
.source_reference
, "local variable `%s' declared but never used".printf (stmt
.declaration
.name
));
531 current_block
.add_node (stmt
);
533 var local
= stmt
.declaration as LocalVariable
;
534 if (local
!= null && local
.initializer
!= null) {
535 handle_errors (local
.initializer
);
539 public override void visit_expression_statement (ExpressionStatement stmt
) {
540 if (unreachable (stmt
)) {
544 current_block
.add_node (stmt
);
546 handle_errors (stmt
);
548 if (stmt
.expression is MethodCall
) {
549 var expr
= (MethodCall
) stmt
.expression
;
550 var ma
= expr
.call as MemberAccess
;
551 if (ma
!= null && ma
.symbol_reference
!= null && ma
.symbol_reference
.get_attribute ("NoReturn") != null) {
552 current_block
= null;
553 unreachable_reported
= false;
559 bool always_true (Expression condition
) {
560 var literal
= condition as BooleanLiteral
;
561 return (literal
!= null && literal
.value
);
564 bool always_false (Expression condition
) {
565 var literal
= condition as BooleanLiteral
;
566 return (literal
!= null && !literal
.value
);
569 public override void visit_if_statement (IfStatement stmt
) {
570 if (unreachable (stmt
)) {
575 current_block
.add_node (stmt
.condition
);
577 handle_errors (stmt
.condition
);
580 var last_block
= current_block
;
581 if (always_false (stmt
.condition
)) {
582 current_block
= null;
583 unreachable_reported
= false;
585 current_block
= new
BasicBlock ();
586 last_block
.connect (current_block
);
588 stmt
.true_statement
.accept (this
);
591 var last_true_block
= current_block
;
592 if (always_true (stmt
.condition
)) {
593 current_block
= null;
594 unreachable_reported
= false;
596 current_block
= new
BasicBlock ();
597 last_block
.connect (current_block
);
599 if (stmt
.false_statement
!= null) {
600 stmt
.false_statement
.accept (this
);
604 var last_false_block
= current_block
;
606 if (last_true_block
!= null || last_false_block
!= null) {
607 current_block
= new
BasicBlock ();
608 if (last_true_block
!= null) {
609 last_true_block
.connect (current_block
);
611 if (last_false_block
!= null) {
612 last_false_block
.connect (current_block
);
617 public override void visit_switch_statement (SwitchStatement stmt
) {
618 if (unreachable (stmt
)) {
622 var after_switch_block
= new
BasicBlock ();
623 jump_stack
.add (new JumpTarget
.break_target (after_switch_block
));
626 current_block
.add_node (stmt
.expression
);
627 var condition_block
= current_block
;
629 handle_errors (stmt
.expression
);
631 bool has_default_label
= false;
633 foreach (SwitchSection section
in stmt
.get_sections ()) {
634 current_block
= new
BasicBlock ();
635 condition_block
.connect (current_block
);
636 foreach (Statement section_stmt
in section
.get_statements ()) {
637 section_stmt
.accept (this
);
640 if (section
.has_default_label ()) {
641 has_default_label
= true;
644 if (current_block
!= null) {
645 // end of switch section reachable
646 // we don't allow fall-through
648 Report
.error (section
.source_reference
, "missing break statement at end of switch section");
649 section
.error
= true;
651 current_block
.connect (after_switch_block
);
655 if (!has_default_label
) {
656 condition_block
.connect (after_switch_block
);
661 if (after_switch_block
.get_predecessors ().size
> 0) {
662 current_block
= after_switch_block
;
664 current_block
= null;
665 unreachable_reported
= false;
668 jump_stack
.remove_at (jump_stack
.size
- 1);
671 public override void visit_loop (Loop stmt
) {
672 if (unreachable (stmt
)) {
676 var loop_block
= new
BasicBlock ();
677 jump_stack
.add (new JumpTarget
.continue_target (loop_block
));
678 var after_loop_block
= new
BasicBlock ();
679 jump_stack
.add (new JumpTarget
.break_target (after_loop_block
));
682 var last_block
= current_block
;
683 last_block
.connect (loop_block
);
684 current_block
= loop_block
;
686 stmt
.body
.accept (this
);
687 // end of loop block reachable?
688 if (current_block
!= null) {
689 current_block
.connect (loop_block
);
694 if (after_loop_block
.get_predecessors ().size
== 0) {
695 // after loop block not reachable
696 current_block
= null;
697 unreachable_reported
= false;
699 // after loop block reachable
700 current_block
= after_loop_block
;
703 jump_stack
.remove_at (jump_stack
.size
- 1);
704 jump_stack
.remove_at (jump_stack
.size
- 1);
707 public override void visit_foreach_statement (ForeachStatement stmt
) {
708 if (unreachable (stmt
)) {
713 current_block
.add_node (stmt
.collection
);
714 handle_errors (stmt
.collection
);
716 var loop_block
= new
BasicBlock ();
717 jump_stack
.add (new JumpTarget
.continue_target (loop_block
));
718 var after_loop_block
= new
BasicBlock ();
719 jump_stack
.add (new JumpTarget
.break_target (after_loop_block
));
722 var last_block
= current_block
;
723 last_block
.connect (loop_block
);
724 current_block
= loop_block
;
725 current_block
.add_node (stmt
);
726 stmt
.body
.accept (this
);
727 if (current_block
!= null) {
728 current_block
.connect (loop_block
);
732 last_block
.connect (after_loop_block
);
733 if (current_block
!= null) {
734 current_block
.connect (after_loop_block
);
736 current_block
= after_loop_block
;
738 jump_stack
.remove_at (jump_stack
.size
- 1);
739 jump_stack
.remove_at (jump_stack
.size
- 1);
742 public override void visit_break_statement (BreakStatement stmt
) {
743 if (unreachable (stmt
)) {
747 current_block
.add_node (stmt
);
749 for (int i
= jump_stack
.size
- 1; i
>= 0; i
--) {
750 var jump_target
= jump_stack
[i
];
751 if (jump_target
.is_break_target
) {
752 current_block
.connect (jump_target
.basic_block
);
753 current_block
= null;
754 unreachable_reported
= false;
756 } else if (jump_target
.is_finally_clause
) {
757 current_block
.connect (jump_target
.basic_block
);
758 current_block
= jump_target
.last_block
;
762 Report
.error (stmt
.source_reference
, "no enclosing loop or switch statement found");
766 public override void visit_continue_statement (ContinueStatement stmt
) {
767 if (unreachable (stmt
)) {
771 current_block
.add_node (stmt
);
773 for (int i
= jump_stack
.size
- 1; i
>= 0; i
--) {
774 var jump_target
= jump_stack
[i
];
775 if (jump_target
.is_continue_target
) {
776 current_block
.connect (jump_target
.basic_block
);
777 current_block
= null;
778 unreachable_reported
= false;
780 } else if (jump_target
.is_finally_clause
) {
781 current_block
.connect (jump_target
.basic_block
);
782 current_block
= jump_target
.last_block
;
786 Report
.error (stmt
.source_reference
, "no enclosing loop found");
790 public override void visit_return_statement (ReturnStatement stmt
) {
791 if (unreachable (stmt
)) {
795 current_block
.add_node (stmt
);
797 if (stmt
.return_expression
!= null) {
798 handle_errors (stmt
.return_expression
);
801 for (int i
= jump_stack
.size
- 1; i
>= 0; i
--) {
802 var jump_target
= jump_stack
[i
];
803 if (jump_target
.is_return_target
) {
804 current_block
.connect (jump_target
.basic_block
);
805 current_block
= null;
806 unreachable_reported
= false;
808 } else if (jump_target
.is_finally_clause
) {
809 current_block
.connect (jump_target
.basic_block
);
810 current_block
= jump_target
.last_block
;
814 Report
.error (stmt
.source_reference
, "no enclosing loop found");
818 private void handle_errors (CodeNode node
, bool always_fail
= false) {
819 if (node
.tree_can_fail
) {
820 var last_block
= current_block
;
822 // exceptional control flow
823 foreach (DataType error_data_type
in node
.get_error_types()) {
824 var error_type
= error_data_type as ErrorType
;
825 current_block
= last_block
;
826 unreachable_reported
= true;
828 for (int i
= jump_stack
.size
- 1; i
>= 0; i
--) {
829 var jump_target
= jump_stack
[i
];
830 if (jump_target
.is_return_target
) {
831 current_block
.connect (jump_target
.basic_block
);
832 current_block
= null;
833 unreachable_reported
= false;
835 } else if (jump_target
.is_error_target
) {
836 if (jump_target
.error_domain
== null
837 || (jump_target
.error_domain
== error_type
.error_domain
838 && (jump_target
.error_code
== null
839 || jump_target
.error_code
== error_type
.error_code
))) {
840 current_block
.connect (jump_target
.basic_block
);
841 current_block
= null;
842 unreachable_reported
= false;
845 } else if (jump_target
.is_finally_clause
) {
846 current_block
.connect (jump_target
.basic_block
);
847 current_block
= jump_target
.last_block
;
852 // normal control flow
854 current_block
= new
BasicBlock ();
855 last_block
.connect (current_block
);
860 public override void visit_yield_statement (YieldStatement stmt
) {
861 if (unreachable (stmt
)) {
865 stmt
.accept_children (this
);
868 public override void visit_throw_statement (ThrowStatement stmt
) {
869 if (unreachable (stmt
)) {
873 current_block
.add_node (stmt
);
874 handle_errors (stmt
, true);
877 public override void visit_try_statement (TryStatement stmt
) {
878 if (unreachable (stmt
)) {
882 var before_try_block
= current_block
;
883 var after_try_block
= new
BasicBlock ();
885 BasicBlock finally_block
= null;
886 if (stmt
.finally_body
!= null) {
887 finally_block
= new
BasicBlock ();
888 current_block
= finally_block
;
890 // trap all forbidden jumps
891 var invalid_block
= new
BasicBlock ();
892 jump_stack
.add (new JumpTarget
.any_target (invalid_block
));
894 stmt
.finally_body
.accept (this
);
896 if (invalid_block
.get_predecessors ().size
> 0) {
897 // don't allow finally blocks with e.g. return statements
898 Report
.error (stmt
.source_reference
, "jump out of finally block not permitted");
902 jump_stack
.remove_at (jump_stack
.size
- 1);
904 jump_stack
.add (new JumpTarget
.finally_clause (finally_block
, current_block
));
907 int finally_jump_stack_size
= jump_stack
.size
;
909 var catch_clauses
= stmt
.get_catch_clauses ();
910 for (int i
= catch_clauses
.size
- 1; i
>= 0; i
--) {
911 var catch_clause
= catch_clauses
[i
];
912 if (catch_clause
.error_type
!= null) {
913 var error_type
= catch_clause
.error_type as ErrorType
;
914 jump_stack
.add (new JumpTarget
.error_target (new
BasicBlock (), catch_clause
, catch_clause
.error_type
.data_type as ErrorDomain
, error_type
.error_code
));
916 jump_stack
.add (new JumpTarget
.error_target (new
BasicBlock (), catch_clause
, null, null));
920 current_block
= before_try_block
;
922 stmt
.body
.accept (this
);
924 if (current_block
!= null) {
925 if (finally_block
!= null) {
926 current_block
.connect (finally_block
);
927 current_block
= finally_block
;
929 current_block
.connect (after_try_block
);
932 // remove catch clauses from jump stack
933 Gee
.List
<JumpTarget
> catch_stack
= new ArrayList
<JumpTarget
> ();
934 for (int i
= jump_stack
.size
- 1; i
>= finally_jump_stack_size
; i
--) {
935 var jump_target
= jump_stack
[i
];
936 catch_stack
.add (jump_target
);
937 jump_stack
.remove_at (i
);
940 foreach (JumpTarget jump_target
in catch_stack
) {
942 foreach (JumpTarget prev_target
in catch_stack
) {
943 if (prev_target
== jump_target
) {
947 if (prev_target
.error_domain
== jump_target
.error_domain
&&
948 prev_target
.error_code
== jump_target
.error_code
) {
949 Report
.error (stmt
.source_reference
, "double catch clause of same error detected");
955 if (jump_target
.basic_block
.get_predecessors ().size
== 0) {
957 Report
.warning (jump_target
.catch_clause
.source_reference
, "unreachable catch clause detected");
959 current_block
= jump_target
.basic_block
;
960 current_block
.add_node (jump_target
.catch_clause
);
961 jump_target
.catch_clause
.body
.accept (this
);
962 if (current_block
!= null) {
963 if (finally_block
!= null) {
964 current_block
.connect (finally_block
);
965 current_block
= finally_block
;
967 current_block
.connect (after_try_block
);
972 if (finally_block
!= null) {
973 jump_stack
.remove_at (jump_stack
.size
- 1);
976 // after try statement
978 if (after_try_block
.get_predecessors ().size
> 0) {
979 current_block
= after_try_block
;
981 current_block
= null;
982 unreachable_reported
= false;
986 public override void visit_lock_statement (LockStatement stmt
) {
987 if (unreachable (stmt
)) {
991 stmt
.body
.accept (this
);
994 private bool unreachable (CodeNode node
) {
995 if (current_block
== null) {
996 if (!unreachable_reported
) {
997 Report
.warning (node
.source_reference
, "unreachable code detected");
998 unreachable_reported
= true;