Insert "%s" argument in printf calls with non-literal format string
[vala-lang.git] / vala / valaflowanalyzer.vala
bloba932b97393a421856a2ec68eee5c174183625b59
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
19 * Author:
20 * Jürg Billeter <j@bitron.ch>
23 using GLib;
24 using Gee;
26 /**
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 () {
92 /**
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) {
104 file.accept (this);
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) {
147 return;
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");
167 m.error = true;
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);
182 return list;
185 void depth_first_traverse (BasicBlock current, Gee.List<BasicBlock> list) {
186 if (current in list) {
187 return;
189 list.add (current);
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) {
202 block_set.add (b);
204 dom.set (block, block_set);
207 // set dom(E) = {E}
208 var entry_dom_set = new HashSet<BasicBlock> ();
209 entry_dom_set.add (entry_block);
210 dom.set (entry_block, entry_dom_set);
212 bool changes = true;
213 while (changes) {
214 changes = false;
215 foreach (BasicBlock block in block_list) {
216 // intersect dom(pred) forall pred: pred = predecessor(s)
217 var dom_set = new HashSet<BasicBlock> ();
218 bool first = true;
219 foreach (BasicBlock pred in block.get_predecessors ()) {
220 var pred_dom_set = dom.get (pred);
221 if (first) {
222 foreach (BasicBlock dom_block in pred_dom_set) {
223 dom_set.add (dom_block);
225 first = false;
226 } else {
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);
238 // unite with s
239 dom_set.add (block);
241 // check for changes
242 if (dom.get (block).size != dom_set.size) {
243 changes = true;
244 } else {
245 foreach (BasicBlock dom_block in dom.get (block)) {
246 if (!(dom_block in dom_set)) {
247 changes = true;
251 // update set in map
252 dom.set (block, dom_set);
256 // build tree
257 foreach (BasicBlock block in block_list) {
258 if (block == entry_block) {
259 continue;
262 BasicBlock immediate_dominator = null;
263 foreach (BasicBlock dominator in dom.get (block)) {
264 if (dominator == block) {
265 continue;
268 if (immediate_dominator == null) {
269 immediate_dominator = dominator;
270 } else {
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);
323 return map;
326 void insert_phi_functions (BasicBlock entry_block) {
327 var assign = get_assignment_map (entry_block);
329 int counter = 0;
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);
336 phi.set (block, 0);
339 foreach (LocalVariable local in assign.get_keys ()) {
340 counter++;
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);
381 if (phi != null) {
382 foreach (LocalVariable local in phi.operands) {
383 if (local == null) {
384 Report.error (used_var.source_reference, "use of possibly unassigned local variable `%s'".printf (used_var.name));
385 continue;
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));
412 continue;
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 ()) {
430 int j = 0;
431 foreach (BasicBlock pred in succ.get_predecessors ()) {
432 if (pred == block) {
433 break;
435 j++;
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) {
477 visit_method (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) {
486 return;
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
504 if (acc.readable) {
505 Report.error (acc.source_reference, "missing return statement at end of property getter body");
506 acc.error = true;
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)) {
524 return;
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)) {
541 return;
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;
554 return;
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)) {
571 return;
574 // condition
575 current_block.add_node (stmt.condition);
577 handle_errors (stmt.condition);
579 // true block
580 var last_block = current_block;
581 if (always_false (stmt.condition)) {
582 current_block = null;
583 unreachable_reported = false;
584 } else {
585 current_block = new BasicBlock ();
586 last_block.connect (current_block);
588 stmt.true_statement.accept (this);
590 // false block
591 var last_true_block = current_block;
592 if (always_true (stmt.condition)) {
593 current_block = null;
594 unreachable_reported = false;
595 } else {
596 current_block = new BasicBlock ();
597 last_block.connect (current_block);
599 if (stmt.false_statement != null) {
600 stmt.false_statement.accept (this);
603 // after if/else
604 var last_false_block = current_block;
605 // reachable?
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)) {
619 return;
622 var after_switch_block = new BasicBlock ();
623 jump_stack.add (new JumpTarget.break_target (after_switch_block));
625 // condition
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);
659 // after switch
660 // reachable?
661 if (after_switch_block.get_predecessors ().size > 0) {
662 current_block = after_switch_block;
663 } else {
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)) {
673 return;
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));
681 // 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);
692 // after loop
693 // reachable?
694 if (after_loop_block.get_predecessors ().size == 0) {
695 // after loop block not reachable
696 current_block = null;
697 unreachable_reported = false;
698 } else {
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)) {
709 return;
712 // collection
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));
721 // 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);
731 // after loop
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)) {
744 return;
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;
755 return;
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");
763 stmt.error = true;
766 public override void visit_continue_statement (ContinueStatement stmt) {
767 if (unreachable (stmt)) {
768 return;
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;
779 return;
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");
787 stmt.error = true;
790 public override void visit_return_statement (ReturnStatement stmt) {
791 if (unreachable (stmt)) {
792 return;
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;
807 return;
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");
815 stmt.error = true;
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;
834 break;
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;
843 break;
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
853 if (!always_fail) {
854 current_block = new BasicBlock ();
855 last_block.connect (current_block);
860 public override void visit_yield_statement (YieldStatement stmt) {
861 if (unreachable (stmt)) {
862 return;
865 stmt.accept_children (this);
868 public override void visit_throw_statement (ThrowStatement stmt) {
869 if (unreachable (stmt)) {
870 return;
873 current_block.add_node (stmt);
874 handle_errors (stmt, true);
877 public override void visit_try_statement (TryStatement stmt) {
878 if (unreachable (stmt)) {
879 return;
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");
899 stmt.error = true;
900 return;
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));
915 } else {
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) {
944 break;
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");
950 stmt.error = true;
951 return;
955 if (jump_target.basic_block.get_predecessors ().size == 0) {
956 // unreachable
957 Report.warning (jump_target.catch_clause.source_reference, "unreachable catch clause detected");
958 } else {
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
977 // reachable?
978 if (after_try_block.get_predecessors ().size > 0) {
979 current_block = after_try_block;
980 } else {
981 current_block = null;
982 unreachable_reported = false;
986 public override void visit_lock_statement (LockStatement stmt) {
987 if (unreachable (stmt)) {
988 return;
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;
1000 return true;
1003 return false;