1 /* valaflowanalyzer.vala
3 * Copyright (C) 2008-2010 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>
26 * Code visitor building the control flow graph.
28 public class Vala
.FlowAnalyzer
: CodeVisitor
{
29 private class JumpTarget
{
30 public bool is_break_target
{ get; set; }
31 public bool is_continue_target
{ get; set; }
32 public bool is_return_target
{ get; set; }
33 public bool is_exit_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 Class? error_class
{ get; set; }
38 public bool is_finally_clause
{ get; set; }
39 public BasicBlock basic_block
{ get; set; }
40 public BasicBlock? last_block
{ get; set; }
41 public CatchClause? catch_clause
{ get; set; }
43 public JumpTarget
.break_target (BasicBlock basic_block
) {
44 this
.basic_block
= basic_block
;
45 is_break_target
= true;
48 public JumpTarget
.continue_target (BasicBlock basic_block
) {
49 this
.basic_block
= basic_block
;
50 is_continue_target
= true;
53 public JumpTarget
.return_target (BasicBlock basic_block
) {
54 this
.basic_block
= basic_block
;
55 is_return_target
= true;
58 public JumpTarget
.exit_target (BasicBlock basic_block
) {
59 this
.basic_block
= basic_block
;
60 is_exit_target
= true;
63 public JumpTarget
.error_target (BasicBlock basic_block
, CatchClause catch_clause
, ErrorDomain? error_domain
, ErrorCode? error_code
, Class? error_class
) {
64 this
.basic_block
= basic_block
;
65 this
.catch_clause
= catch_clause
;
66 this
.error_domain
= error_domain
;
67 this
.error_code
= error_code
;
68 this
.error_class
= error_class
;
69 is_error_target
= true;
72 public JumpTarget
.any_target (BasicBlock basic_block
) {
73 this
.basic_block
= basic_block
;
74 is_break_target
= true;
75 is_continue_target
= true;
76 is_return_target
= true;
77 is_exit_target
= true;
78 is_error_target
= true;
81 public JumpTarget
.finally_clause (BasicBlock basic_block
, BasicBlock last_block
) {
82 this
.basic_block
= basic_block
;
83 this
.last_block
= last_block
;
84 is_finally_clause
= true;
88 private CodeContext context
;
89 private BasicBlock current_block
;
90 private bool unreachable_reported
;
91 private List
<JumpTarget
> jump_stack
= new ArrayList
<JumpTarget
> ();
92 private Set
<BasicBlock
> all_basic_blocks
;
95 Map
<Symbol
, List
<Variable
>> var_map
;
96 Set
<Variable
> used_vars
;
97 Map
<Variable
, PhiFunction
> phi_functions
;
99 public FlowAnalyzer () {
103 * Build control flow graph in the specified context.
105 * @param context a code context
107 public void analyze (CodeContext context
) {
108 this
.context
= context
;
109 all_basic_blocks
= new HashSet
<BasicBlock
> ();
111 /* we're only interested in non-pkg source files */
112 var source_files
= context
.get_source_files ();
113 foreach (SourceFile file
in source_files
) {
114 if (file
.file_type
== SourceFileType
.SOURCE
) {
119 all_basic_blocks
= null;
123 public override void visit_source_file (SourceFile source_file
) {
124 source_file
.accept_children (this
);
127 public override void visit_class (Class cl
) {
128 cl
.accept_children (this
);
131 public override void visit_struct (Struct st
) {
132 st
.accept_children (this
);
135 public override void visit_interface (Interface iface
) {
136 iface
.accept_children (this
);
139 public override void visit_enum (Enum en
) {
140 en
.accept_children (this
);
143 public override void visit_error_domain (ErrorDomain ed
) {
144 ed
.accept_children (this
);
147 public override void visit_field (Field f
) {
148 if (f
.is_internal_symbol () && !f
.used
) {
149 if (!f
.is_private_symbol () && (context
.internal_header_filename
!= null || context
.use_fast_vapi
)) {
150 // do not warn if internal member may be used outside this compilation unit
152 Report
.warning (f
.source_reference
, "field `%s' never used".printf (f
.get_full_name ()));
157 public override void visit_lambda_expression (LambdaExpression le
) {
158 var old_current_block
= current_block
;
159 var old_unreachable_reported
= unreachable_reported
;
160 var old_jump_stack
= jump_stack
;
162 jump_stack
= new ArrayList
<JumpTarget
> ();
164 le
.accept_children (this
);
166 current_block
= old_current_block
;
167 unreachable_reported
= old_unreachable_reported
;
168 jump_stack
= old_jump_stack
;
171 public override void visit_method (Method m
) {
172 if (m
.is_internal_symbol () && !m
.used
&& !m
.entry_point
173 && !m
.overrides
&& (m
.base_interface_method
== null || m
.base_interface_method
== m
)
174 && !(m is CreationMethod
)) {
175 if (!m
.is_private_symbol () && (context
.internal_header_filename
!= null || context
.use_fast_vapi
)) {
176 // do not warn if internal member may be used outside this compilation unit
177 } else if (m
.parent_symbol
!= null && m
.parent_symbol
.get_attribute ("DBus") != null
178 && m
.get_attribute_bool ("DBus", "visible", true)) {
179 // do not warn if internal member is a visible DBus method
181 Report
.warning (m
.source_reference
, "method `%s' never used".printf (m
.get_full_name ()));
185 visit_subroutine (m
);
188 public override void visit_signal (Signal sig
) {
189 if (sig
.default_handler
!= null) {
190 visit_subroutine (sig
.default_handler
);
194 void visit_subroutine (Subroutine m
) {
195 if (m
.body
== null) {
199 m
.entry_block
= new BasicBlock
.entry ();
200 all_basic_blocks
.add (m
.entry_block
);
201 m
.return_block
= new
BasicBlock ();
202 all_basic_blocks
.add (m
.return_block
);
203 m
.exit_block
= new BasicBlock
.exit ();
204 all_basic_blocks
.add (m
.exit_block
);
206 m
.return_block
.connect (m
.exit_block
);
209 // ensure out parameters are defined at end of method
210 foreach (var param
in ((Method
) m
).get_parameters ()) {
211 if (param
.direction
== ParameterDirection
.OUT
) {
212 var param_ma
= new MemberAccess
.simple (param
.name
, param
.source_reference
);
213 param_ma
.symbol_reference
= param
;
214 m
.return_block
.add_node (param_ma
);
219 current_block
= new
BasicBlock ();
220 all_basic_blocks
.add (current_block
);
222 m
.entry_block
.connect (current_block
);
223 current_block
.add_node (m
);
225 jump_stack
.add (new JumpTarget
.return_target (m
.return_block
));
226 jump_stack
.add (new JumpTarget
.exit_target (m
.exit_block
));
228 m
.accept_children (this
);
230 jump_stack
.remove_at (jump_stack
.size
- 1);
232 if (current_block
!= null) {
233 // end of method body reachable
236 Report
.error (m
.source_reference
, "missing return statement at end of subroutine body");
240 current_block
.connect (m
.return_block
);
243 analyze_body (m
.entry_block
);
246 void analyze_body (BasicBlock entry_block
) {
247 var block_list
= get_depth_first_list (entry_block
);
249 build_dominator_tree (block_list
, entry_block
);
250 build_dominator_frontier (block_list
, entry_block
);
251 insert_phi_functions (block_list
, entry_block
);
252 check_variables (entry_block
);
255 // generates reverse postorder list
256 List
<BasicBlock
> get_depth_first_list (BasicBlock entry_block
) {
257 var list
= new ArrayList
<BasicBlock
> ();
258 depth_first_traverse (entry_block
, list
);
262 void depth_first_traverse (BasicBlock current
, List
<BasicBlock
> list
) {
263 if (current
.postorder_visited
) {
266 current
.postorder_visited
= true;
267 foreach (weak BasicBlock succ
in current
.get_successors ()) {
268 depth_first_traverse (succ
, list
);
270 current
.postorder_number
= list
.size
;
271 list
.insert (0, current
);
274 void build_dominator_tree (List
<BasicBlock
> block_list
, BasicBlock entry_block
) {
275 // immediate dominators
276 var idoms
= new BasicBlock
[block_list
.size
];
277 idoms
[entry_block
.postorder_number
] = entry_block
;
282 foreach (BasicBlock block
in block_list
) {
283 if (block
== entry_block
) {
287 // new immediate dominator
288 BasicBlock new_idom
= null;
290 foreach (weak BasicBlock pred
in block
.get_predecessors ()) {
291 if (idoms
[pred
.postorder_number
] != null) {
296 new_idom
= intersect (idoms
, pred
, new_idom
);
300 if (idoms
[block
.postorder_number
] != new_idom
) {
301 idoms
[block
.postorder_number
] = new_idom
;
308 foreach (BasicBlock block
in block_list
) {
309 if (block
== entry_block
) {
313 idoms
[block
.postorder_number
].add_child (block
);
317 BasicBlock
intersect (BasicBlock
[] idoms
, BasicBlock b1
, BasicBlock b2
) {
319 while (b1
.postorder_number
< b2
.postorder_number
) {
320 b1
= idoms
[b2
.postorder_number
];
322 while (b2
.postorder_number
< b1
.postorder_number
) {
323 b2
= idoms
[b2
.postorder_number
];
329 void build_dominator_frontier (List
<BasicBlock
> block_list
, BasicBlock entry_block
) {
330 for (int i
= block_list
.size
- 1; i
>= 0; i
--) {
331 var block
= block_list
[i
];
333 foreach (weak BasicBlock succ
in block
.get_successors ()) {
334 // if idom(succ) != block
335 if (succ
.parent
!= block
) {
336 block
.add_dominator_frontier (succ
);
340 foreach (weak BasicBlock child
in block
.get_children ()) {
341 foreach (weak BasicBlock child_frontier
in child
.get_dominator_frontier ()) {
342 // if idom(child_frontier) != block
343 if (child_frontier
.parent
!= block
) {
344 block
.add_dominator_frontier (child_frontier
);
351 Map
<Variable
, Set
<BasicBlock
>> get_assignment_map (List
<BasicBlock
> block_list
, BasicBlock entry_block
) {
352 var map
= new HashMap
<Variable
, Set
<BasicBlock
>> ();
353 foreach (BasicBlock block
in block_list
) {
354 var defined_variables
= new ArrayList
<Variable
> ();
355 foreach (CodeNode node
in block
.get_nodes ()) {
356 node
.get_defined_variables (defined_variables
);
359 foreach (Variable variable
in defined_variables
) {
360 var block_set
= map
.get (variable
);
361 if (block_set
== null) {
362 block_set
= new HashSet
<BasicBlock
> ();
363 map
.set (variable
, block_set
);
365 block_set
.add (block
);
371 void insert_phi_functions (List
<BasicBlock
> block_list
, BasicBlock entry_block
) {
372 var assign
= get_assignment_map (block_list
, entry_block
);
375 var work_list
= new ArrayList
<BasicBlock
> ();
377 var added
= new HashMap
<BasicBlock
, int> ();
378 var phi
= new HashMap
<BasicBlock
, int> ();
379 foreach (BasicBlock block
in block_list
) {
380 added
.set (block
, 0);
384 foreach (Variable variable
in assign
.get_keys ()) {
386 foreach (BasicBlock block
in assign
.get (variable
)) {
387 work_list
.add (block
);
388 added
.set (block
, counter
);
390 while (work_list
.size
> 0) {
391 BasicBlock block
= work_list
.remove_at (0);
392 foreach (BasicBlock frontier
in block
.get_dominator_frontier ()) {
393 int blockPhi
= phi
.get (frontier
);
394 if (blockPhi
< counter
) {
395 frontier
.add_phi_function (new
PhiFunction (variable
, frontier
.get_predecessors ().size
));
396 phi
.set (frontier
, counter
);
397 int block_added
= added
.get (frontier
);
398 if (block_added
< counter
) {
399 added
.set (frontier
, counter
);
400 work_list
.add (frontier
);
408 void check_variables (BasicBlock entry_block
) {
409 var_map
= new HashMap
<Symbol
, List
<Variable
>>();
410 used_vars
= new HashSet
<Variable
> ();
411 phi_functions
= new HashMap
<Variable
, PhiFunction
> ();
413 check_block_variables (entry_block
);
415 // check for variables used before initialization
416 var used_vars_queue
= new ArrayList
<Variable
> ();
417 foreach (Variable variable
in used_vars
) {
418 used_vars_queue
.add (variable
);
420 while (used_vars_queue
.size
> 0) {
421 Variable used_var
= used_vars_queue
.remove_at (0);
423 PhiFunction phi
= phi_functions
.get (used_var
);
425 foreach (Variable variable
in phi
.operands
) {
426 if (variable
== null) {
427 if (used_var is LocalVariable
) {
428 Report
.error (used_var
.source_reference
, "use of possibly unassigned local variable `%s'".printf (used_var
.name
));
431 Report
.warning (used_var
.source_reference
, "use of possibly unassigned parameter `%s'".printf (used_var
.name
));
435 if (!(variable
in used_vars
)) {
436 variable
.source_reference
= used_var
.source_reference
;
437 used_vars
.add (variable
);
438 used_vars_queue
.add (variable
);
444 phi_functions
= null;
449 void check_block_variables (BasicBlock block
) {
450 foreach (PhiFunction phi
in block
.get_phi_functions ()) {
451 Variable versioned_var
= process_assignment (var_map
, phi
.original_variable
);
453 phi_functions
.set (versioned_var
, phi
);
456 foreach (CodeNode node
in block
.get_nodes ()) {
457 var used_variables
= new ArrayList
<Variable
> ();
458 node
.get_used_variables (used_variables
);
460 foreach (Variable var_symbol
in used_variables
) {
461 var variable_stack
= var_map
.get (var_symbol
);
462 if (variable_stack
== null || variable_stack
.size
== 0) {
463 if (var_symbol is LocalVariable
) {
464 Report
.error (node
.source_reference
, "use of possibly unassigned local variable `%s'".printf (var_symbol
.name
));
467 Report
.warning (node
.source_reference
, "use of possibly unassigned parameter `%s'".printf (var_symbol
.name
));
471 var versioned_variable
= variable_stack
.get (variable_stack
.size
- 1);
472 if (!(versioned_variable
in used_vars
)) {
473 versioned_variable
.source_reference
= node
.source_reference
;
475 used_vars
.add (versioned_variable
);
478 var defined_variables
= new ArrayList
<Variable
> ();
479 node
.get_defined_variables (defined_variables
);
481 foreach (Variable variable
in defined_variables
) {
482 process_assignment (var_map
, variable
);
486 foreach (weak BasicBlock succ
in block
.get_successors ()) {
488 foreach (weak BasicBlock pred
in succ
.get_predecessors ()) {
495 foreach (PhiFunction phi
in succ
.get_phi_functions ()) {
496 var variable_stack
= var_map
.get (phi
.original_variable
);
497 if (variable_stack
!= null && variable_stack
.size
> 0) {
498 phi
.operands
.set (j
, variable_stack
.get (variable_stack
.size
- 1));
503 foreach (weak BasicBlock child
in block
.get_children ()) {
504 check_block_variables (child
);
507 foreach (PhiFunction phi
in block
.get_phi_functions ()) {
508 var variable_stack
= var_map
.get (phi
.original_variable
);
509 variable_stack
.remove_at (variable_stack
.size
- 1);
511 foreach (CodeNode node
in block
.get_nodes ()) {
512 var defined_variables
= new ArrayList
<Variable
> ();
513 node
.get_defined_variables (defined_variables
);
515 foreach (Variable variable
in defined_variables
) {
516 var variable_stack
= var_map
.get (variable
);
517 variable_stack
.remove_at (variable_stack
.size
- 1);
522 Variable
process_assignment (Map
<Symbol
, List
<Variable
>> var_map
, Variable var_symbol
) {
523 var variable_stack
= var_map
.get (var_symbol
);
524 if (variable_stack
== null) {
525 variable_stack
= new ArrayList
<Variable
> ();
526 var_map
.set (var_symbol
, variable_stack
);
527 var_symbol
.single_assignment
= true;
529 var_symbol
.single_assignment
= false;
531 Variable versioned_var
;
532 if (var_symbol is LocalVariable
) {
533 versioned_var
= new
LocalVariable (var_symbol
.variable_type
.copy (), var_symbol
.name
, null, var_symbol
.source_reference
);
536 versioned_var
= new
Parameter (var_symbol
.name
, var_symbol
.variable_type
.copy (), var_symbol
.source_reference
);
538 variable_stack
.add (versioned_var
);
539 return versioned_var
;
542 public override void visit_creation_method (CreationMethod m
) {
546 public override void visit_property (Property prop
) {
547 prop
.accept_children (this
);
550 public override void visit_property_accessor (PropertyAccessor acc
) {
551 visit_subroutine (acc
);
554 public override void visit_block (Block b
) {
555 b
.accept_children (this
);
558 public override void visit_declaration_statement (DeclarationStatement stmt
) {
559 stmt
.accept_children (this
);
561 if (unreachable (stmt
)) {
562 stmt
.declaration
.unreachable
= true;
566 if (!stmt
.declaration
.used
) {
567 Report
.warning (stmt
.declaration
.source_reference
, "local variable `%s' declared but never used".printf (stmt
.declaration
.name
));
570 current_block
.add_node (stmt
);
572 var local
= stmt
.declaration as LocalVariable
;
573 if (local
!= null && local
.initializer
!= null) {
574 handle_errors (local
.initializer
);
578 public override void visit_local_variable (LocalVariable local
) {
579 if (local
.initializer
!= null) {
580 local
.initializer
.accept (this
);
584 public override void visit_expression_statement (ExpressionStatement stmt
) {
585 stmt
.accept_children (this
);
587 if (unreachable (stmt
)) {
591 current_block
.add_node (stmt
);
593 handle_errors (stmt
);
595 if (stmt
.expression is MethodCall
) {
596 var expr
= (MethodCall
) stmt
.expression
;
597 var ma
= expr
.call as MemberAccess
;
598 if (ma
!= null && ma
.symbol_reference
!= null && ma
.symbol_reference
.get_attribute ("NoReturn") != null) {
605 bool always_true (Expression condition
) {
606 var literal
= condition as BooleanLiteral
;
607 return (literal
!= null && literal
.value
);
610 bool always_false (Expression condition
) {
611 var literal
= condition as BooleanLiteral
;
612 return (literal
!= null && !literal
.value
);
615 public override void visit_if_statement (IfStatement stmt
) {
616 if (unreachable (stmt
)) {
621 current_block
.add_node (stmt
.condition
);
623 handle_errors (stmt
.condition
);
626 var last_block
= current_block
;
627 if (always_false (stmt
.condition
)) {
630 current_block
= new
BasicBlock ();
631 all_basic_blocks
.add (current_block
);
632 last_block
.connect (current_block
);
634 stmt
.true_statement
.accept (this
);
637 var last_true_block
= current_block
;
638 if (always_true (stmt
.condition
)) {
641 current_block
= new
BasicBlock ();
642 all_basic_blocks
.add (current_block
);
643 last_block
.connect (current_block
);
645 if (stmt
.false_statement
!= null) {
646 stmt
.false_statement
.accept (this
);
650 var last_false_block
= current_block
;
652 if (last_true_block
!= null || last_false_block
!= null) {
653 current_block
= new
BasicBlock ();
654 all_basic_blocks
.add (current_block
);
655 if (last_true_block
!= null) {
656 last_true_block
.connect (current_block
);
658 if (last_false_block
!= null) {
659 last_false_block
.connect (current_block
);
664 public override void visit_switch_statement (SwitchStatement stmt
) {
665 if (unreachable (stmt
)) {
669 var after_switch_block
= new
BasicBlock ();
670 all_basic_blocks
.add (after_switch_block
);
671 jump_stack
.add (new JumpTarget
.break_target (after_switch_block
));
674 current_block
.add_node (stmt
.expression
);
675 var condition_block
= current_block
;
677 handle_errors (stmt
.expression
);
679 bool has_default_label
= false;
681 foreach (SwitchSection section
in stmt
.get_sections ()) {
682 current_block
= new
BasicBlock ();
683 all_basic_blocks
.add (current_block
);
684 condition_block
.connect (current_block
);
685 foreach (Statement section_stmt
in section
.get_statements ()) {
686 section_stmt
.accept (this
);
689 if (section
.has_default_label ()) {
690 has_default_label
= true;
693 if (current_block
!= null) {
694 // end of switch section reachable
695 // we don't allow fall-through
697 Report
.error (section
.source_reference
, "missing break statement at end of switch section");
698 section
.error
= true;
700 current_block
.connect (after_switch_block
);
704 if (!has_default_label
) {
705 condition_block
.connect (after_switch_block
);
710 if (after_switch_block
.get_predecessors ().size
> 0) {
711 current_block
= after_switch_block
;
716 jump_stack
.remove_at (jump_stack
.size
- 1);
719 public override void visit_loop (Loop stmt
) {
720 if (unreachable (stmt
)) {
724 var loop_block
= new
BasicBlock ();
725 all_basic_blocks
.add (loop_block
);
726 jump_stack
.add (new JumpTarget
.continue_target (loop_block
));
727 var after_loop_block
= new
BasicBlock ();
728 all_basic_blocks
.add (after_loop_block
);
729 jump_stack
.add (new JumpTarget
.break_target (after_loop_block
));
732 var last_block
= current_block
;
733 last_block
.connect (loop_block
);
734 current_block
= loop_block
;
736 stmt
.body
.accept (this
);
737 // end of loop block reachable?
738 if (current_block
!= null) {
739 current_block
.connect (loop_block
);
744 if (after_loop_block
.get_predecessors ().size
== 0) {
745 // after loop block not reachable
748 // after loop block reachable
749 current_block
= after_loop_block
;
752 jump_stack
.remove_at (jump_stack
.size
- 1);
753 jump_stack
.remove_at (jump_stack
.size
- 1);
756 public override void visit_foreach_statement (ForeachStatement stmt
) {
757 if (unreachable (stmt
)) {
762 current_block
.add_node (stmt
.collection
);
763 handle_errors (stmt
.collection
);
765 var loop_block
= new
BasicBlock ();
766 all_basic_blocks
.add (loop_block
);
767 jump_stack
.add (new JumpTarget
.continue_target (loop_block
));
768 var after_loop_block
= new
BasicBlock ();
769 all_basic_blocks
.add (after_loop_block
);
770 jump_stack
.add (new JumpTarget
.break_target (after_loop_block
));
773 var last_block
= current_block
;
774 last_block
.connect (loop_block
);
775 current_block
= loop_block
;
776 current_block
.add_node (stmt
);
777 stmt
.body
.accept (this
);
778 if (current_block
!= null) {
779 current_block
.connect (loop_block
);
783 last_block
.connect (after_loop_block
);
784 if (current_block
!= null) {
785 current_block
.connect (after_loop_block
);
787 current_block
= after_loop_block
;
789 jump_stack
.remove_at (jump_stack
.size
- 1);
790 jump_stack
.remove_at (jump_stack
.size
- 1);
793 public override void visit_break_statement (BreakStatement stmt
) {
794 if (unreachable (stmt
)) {
798 current_block
.add_node (stmt
);
800 for (int i
= jump_stack
.size
- 1; i
>= 0; i
--) {
801 var jump_target
= jump_stack
[i
];
802 if (jump_target
.is_break_target
) {
803 current_block
.connect (jump_target
.basic_block
);
806 } else if (jump_target
.is_finally_clause
) {
807 current_block
.connect (jump_target
.basic_block
);
808 current_block
= jump_target
.last_block
;
812 Report
.error (stmt
.source_reference
, "no enclosing loop or switch statement found");
816 public override void visit_continue_statement (ContinueStatement stmt
) {
817 if (unreachable (stmt
)) {
821 current_block
.add_node (stmt
);
823 for (int i
= jump_stack
.size
- 1; i
>= 0; i
--) {
824 var jump_target
= jump_stack
[i
];
825 if (jump_target
.is_continue_target
) {
826 current_block
.connect (jump_target
.basic_block
);
829 } else if (jump_target
.is_finally_clause
) {
830 current_block
.connect (jump_target
.basic_block
);
831 current_block
= jump_target
.last_block
;
835 Report
.error (stmt
.source_reference
, "no enclosing loop found");
839 public override void visit_return_statement (ReturnStatement stmt
) {
840 stmt
.accept_children (this
);
842 if (unreachable (stmt
)) {
846 current_block
.add_node (stmt
);
848 if (stmt
.return_expression
!= null) {
849 handle_errors (stmt
.return_expression
);
852 for (int i
= jump_stack
.size
- 1; i
>= 0; i
--) {
853 var jump_target
= jump_stack
[i
];
854 if (jump_target
.is_return_target
) {
855 current_block
.connect (jump_target
.basic_block
);
858 } else if (jump_target
.is_finally_clause
) {
859 current_block
.connect (jump_target
.basic_block
);
860 current_block
= jump_target
.last_block
;
864 Report
.error (stmt
.source_reference
, "no enclosing loop found");
868 private void handle_errors (CodeNode node
, bool always_fail
= false) {
869 if (node
.tree_can_fail
) {
870 var last_block
= current_block
;
872 // exceptional control flow
873 foreach (DataType error_data_type
in node
.get_error_types()) {
874 var error_type
= error_data_type as ErrorType
;
875 var error_class
= error_data_type
.data_type as Class
;
876 current_block
= last_block
;
877 unreachable_reported
= true;
879 for (int i
= jump_stack
.size
- 1; i
>= 0; i
--) {
880 var jump_target
= jump_stack
[i
];
881 if (jump_target
.is_exit_target
) {
882 current_block
.connect (jump_target
.basic_block
);
885 } else if (jump_target
.is_error_target
) {
886 if (context
.profile
== Profile
.GOBJECT
) {
887 if (jump_target
.error_domain
== null
888 || (jump_target
.error_domain
== error_type
.error_domain
889 && (jump_target
.error_code
== null
890 || jump_target
.error_code
== error_type
.error_code
))) {
891 // error can always be caught by this catch clause
892 // following catch clauses cannot be reached by this error
893 current_block
.connect (jump_target
.basic_block
);
896 } else if (error_type
.error_domain
== null
897 || (error_type
.error_domain
== jump_target
.error_domain
898 && (error_type
.error_code
== null
899 || error_type
.error_code
== jump_target
.error_code
))) {
900 // error might be caught by this catch clause
901 // unknown at compile time
902 // following catch clauses might still be reached by this error
903 current_block
.connect (jump_target
.basic_block
);
905 } else if (jump_target
.error_class
== null || jump_target
.error_class
== error_class
) {
906 // error can always be caught by this catch clause
907 // following catch clauses cannot be reached by this error
908 current_block
.connect (jump_target
.basic_block
);
911 } else if (jump_target
.error_class
.is_subtype_of (error_class
)) {
912 // error might be caught by this catch clause
913 // unknown at compile time
914 // following catch clauses might still be reached by this error
915 current_block
.connect (jump_target
.basic_block
);
917 } else if (jump_target
.is_finally_clause
) {
918 current_block
.connect (jump_target
.basic_block
);
919 current_block
= jump_target
.last_block
;
924 // normal control flow
926 current_block
= new
BasicBlock ();
927 all_basic_blocks
.add (current_block
);
928 last_block
.connect (current_block
);
933 public override void visit_yield_statement (YieldStatement stmt
) {
934 if (unreachable (stmt
)) {
938 stmt
.accept_children (this
);
941 public override void visit_throw_statement (ThrowStatement stmt
) {
942 if (unreachable (stmt
)) {
946 current_block
.add_node (stmt
);
947 handle_errors (stmt
, true);
950 public override void visit_try_statement (TryStatement stmt
) {
951 if (unreachable (stmt
)) {
955 var before_try_block
= current_block
;
956 var after_try_block
= new
BasicBlock ();
957 all_basic_blocks
.add (after_try_block
);
959 BasicBlock finally_block
= null;
960 if (stmt
.finally_body
!= null) {
961 finally_block
= new
BasicBlock ();
962 all_basic_blocks
.add (finally_block
);
963 current_block
= finally_block
;
965 // trap all forbidden jumps
966 var invalid_block
= new
BasicBlock ();
967 all_basic_blocks
.add (invalid_block
);
968 jump_stack
.add (new JumpTarget
.any_target (invalid_block
));
970 stmt
.finally_body
.accept (this
);
972 if (invalid_block
.get_predecessors ().size
> 0) {
973 // don't allow finally blocks with e.g. return statements
974 Report
.error (stmt
.source_reference
, "jump out of finally block not permitted");
978 jump_stack
.remove_at (jump_stack
.size
- 1);
980 jump_stack
.add (new JumpTarget
.finally_clause (finally_block
, current_block
));
983 int finally_jump_stack_size
= jump_stack
.size
;
985 var catch_clauses
= stmt
.get_catch_clauses ();
986 for (int i
= catch_clauses
.size
- 1; i
>= 0; i
--) {
987 var catch_clause
= catch_clauses
[i
];
988 var error_block
= new
BasicBlock ();
989 all_basic_blocks
.add (error_block
);
991 if (catch_clause
.error_type
!= null) {
992 if (context
.profile
== Profile
.GOBJECT
) {
993 var error_type
= (ErrorType
) catch_clause
.error_type
;
994 jump_stack
.add (new JumpTarget
.error_target (error_block
, catch_clause
, catch_clause
.error_type
.data_type as ErrorDomain
, error_type
.error_code
, null));
996 var error_class
= catch_clause
.error_type
.data_type as Class
;
997 jump_stack
.add (new JumpTarget
.error_target (error_block
, catch_clause
, null, null, error_class
));
1000 jump_stack
.add (new JumpTarget
.error_target (error_block
, catch_clause
, null, null, null));
1004 current_block
= before_try_block
;
1006 stmt
.body
.accept (this
);
1008 if (current_block
!= null) {
1009 if (finally_block
!= null) {
1010 current_block
.connect (finally_block
);
1011 current_block
= finally_block
;
1013 current_block
.connect (after_try_block
);
1016 // remove catch clauses from jump stack
1017 List
<JumpTarget
> catch_stack
= new ArrayList
<JumpTarget
> ();
1018 for (int i
= jump_stack
.size
- 1; i
>= finally_jump_stack_size
; i
--) {
1019 var jump_target
= jump_stack
.remove_at (i
);
1020 catch_stack
.add (jump_target
);
1023 foreach (JumpTarget jump_target
in catch_stack
) {
1025 foreach (JumpTarget prev_target
in catch_stack
) {
1026 if (prev_target
== jump_target
) {
1030 if (context
.profile
== Profile
.GOBJECT
) {
1031 if (prev_target
.error_domain
== jump_target
.error_domain
&&
1032 prev_target
.error_code
== jump_target
.error_code
) {
1033 Report
.error (stmt
.source_reference
, "double catch clause of same error detected");
1037 } else if (prev_target
.error_class
== jump_target
.error_class
) {
1038 Report
.error (stmt
.source_reference
, "double catch clause of same error detected");
1044 if (jump_target
.basic_block
.get_predecessors ().size
== 0) {
1046 Report
.warning (jump_target
.catch_clause
.source_reference
, "unreachable catch clause detected");
1048 current_block
= jump_target
.basic_block
;
1049 current_block
.add_node (jump_target
.catch_clause
);
1050 jump_target
.catch_clause
.body
.accept (this
);
1051 if (current_block
!= null) {
1052 if (finally_block
!= null) {
1053 current_block
.connect (finally_block
);
1054 current_block
= finally_block
;
1056 current_block
.connect (after_try_block
);
1061 if (finally_block
!= null) {
1062 jump_stack
.remove_at (jump_stack
.size
- 1);
1065 // after try statement
1067 if (after_try_block
.get_predecessors ().size
> 0) {
1068 current_block
= after_try_block
;
1070 stmt
.after_try_block_reachable
= false;
1071 mark_unreachable ();
1075 public override void visit_lock_statement (LockStatement stmt
) {
1076 if (unreachable (stmt
)) {
1081 public override void visit_unlock_statement (UnlockStatement stmt
) {
1082 if (unreachable (stmt
)) {
1087 public override void visit_expression (Expression expr
) {
1088 // lambda expression is handled separately
1089 if (!(expr is LambdaExpression
)) {
1090 expr
.accept_children (this
);
1094 private bool unreachable (CodeNode node
) {
1095 if (current_block
== null) {
1096 node
.unreachable
= true;
1097 if (!unreachable_reported
) {
1098 Report
.warning (node
.source_reference
, "unreachable code detected");
1099 unreachable_reported
= true;
1107 void mark_unreachable () {
1108 current_block
= null;
1109 unreachable_reported
= false;