From: Jürg Billeter Date: Sat, 6 Feb 2010 13:25:41 +0000 (+0100) Subject: dova: Add initial support for error handling X-Git-Tag: 0.9.2~60 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=31cb78817cb9cc87c18f0d9b12e6a4efa4ec5222;p=thirdparty%2Fvala.git dova: Add initial support for error handling --- diff --git a/codegen/Makefile.am b/codegen/Makefile.am index c62b6f6eb..7581eb61f 100644 --- a/codegen/Makefile.am +++ b/codegen/Makefile.am @@ -39,6 +39,7 @@ libvala_la_VALASOURCES = \ valadovabasemodule.vala \ valadovacontrolflowmodule.vala \ valadovadelegatemodule.vala \ + valadovaerrormodule.vala \ valadovamemberaccessmodule.vala \ valadovamethodcallmodule.vala \ valadovamethodmodule.vala \ diff --git a/codegen/valaccodegenerator.vala b/codegen/valaccodegenerator.vala index e2155cf74..b32ba3ccb 100644 --- a/codegen/valaccodegenerator.vala +++ b/codegen/valaccodegenerator.vala @@ -65,8 +65,9 @@ public class Vala.CCodeGenerator : CodeGenerator { head = new DovaArrayModule (this, head); head = new DovaObjectModule (this, head); head = new DovaValueModule (this, head); - */ head = new DovaDelegateModule (this, head); + */ + head = new DovaErrorModule (this, head); } else { /* included by inheritance head = new CCodeBaseModule (this, head); diff --git a/codegen/valadovabasemodule.vala b/codegen/valadovabasemodule.vala index f58a52540..01c3fb775 100644 --- a/codegen/valadovabasemodule.vala +++ b/codegen/valadovabasemodule.vala @@ -130,6 +130,7 @@ internal class Vala.DovaBaseModule : CCodeModule { public Class value_class; public Class array_class; public Class delegate_class; + public Class error_class; Set generated_external_symbols; @@ -208,6 +209,7 @@ internal class Vala.DovaBaseModule : CCodeModule { value_class = (Class) dova_ns.scope.lookup ("Value"); array_class = (Class) dova_ns.scope.lookup ("Array"); delegate_class = (Class) dova_ns.scope.lookup ("Delegate"); + error_class = (Class) dova_ns.scope.lookup ("Error"); header_declarations = new CCodeDeclarationSpace (); internal_header_declarations = new CCodeDeclarationSpace (); diff --git a/codegen/valadovaerrormodule.vala b/codegen/valadovaerrormodule.vala new file mode 100644 index 000000000..9da609aa4 --- /dev/null +++ b/codegen/valadovaerrormodule.vala @@ -0,0 +1,343 @@ +/* valadovaerrormodule.vala + * + * Copyright (C) 2008-2010 Jürg Billeter + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: + * Jürg Billeter + * Thijs Vermeir + */ + +using GLib; + +internal class Vala.DovaErrorModule : DovaDelegateModule { + private int current_try_id = 0; + private int next_try_id = 0; + private bool is_in_catch = false; + + public DovaErrorModule (CCodeGenerator codegen, CCodeModule? next) { + base (codegen, next); + } + + public override void visit_throw_statement (ThrowStatement stmt) { + stmt.accept_children (codegen); + + var cfrag = new CCodeFragment (); + + // method will fail + current_method_inner_error = true; + var cassign = new CCodeAssignment (get_variable_cexpression ("_inner_error_"), (CCodeExpression) stmt.error_expression.ccodenode); + cfrag.append (new CCodeExpressionStatement (cassign)); + + head.add_simple_check (stmt, cfrag, true); + + stmt.ccodenode = cfrag; + + create_temp_decl (stmt, stmt.error_expression.temp_vars); + } + + public virtual CCodeStatement return_with_exception () { + // propagate error + var cerror_block = new CCodeBlock (); + cerror_block.add_statement (new CCodeExpressionStatement (new CCodeAssignment (new CCodeUnaryExpression (CCodeUnaryOperator.POINTER_INDIRECTION, new CCodeIdentifier ("error")), get_variable_cexpression ("_inner_error_")))); + cerror_block.add_statement (new CCodeExpressionStatement (new CCodeAssignment (get_variable_cexpression ("_inner_error_"), new CCodeConstant ("NULL")))); + + // free local variables + var free_frag = new CCodeFragment (); + append_local_free (current_symbol, free_frag, false); + cerror_block.add_statement (free_frag); + + if (current_method is CreationMethod) { + var cl = current_method.parent_symbol as Class; + var unref_call = new CCodeFunctionCall (new CCodeIdentifier (cl.get_unref_function ())); + unref_call.add_argument (new CCodeIdentifier ("self")); + cerror_block.add_statement (new CCodeExpressionStatement (unref_call)); + cerror_block.add_statement (new CCodeReturnStatement (new CCodeConstant ("NULL"))); + } else if (current_return_type is VoidType) { + cerror_block.add_statement (new CCodeReturnStatement ()); + } else { + cerror_block.add_statement (new CCodeReturnStatement (default_value_for_type (current_return_type, false))); + } + + return cerror_block; + } + + CCodeStatement uncaught_error_statement (CCodeExpression inner_error, CCodeBlock? block = null, bool unexpected = false) { + var cerror_block = block; + if (cerror_block == null) { + cerror_block = new CCodeBlock (); + } + + // free local variables + var free_frag = new CCodeFragment (); + append_local_free (current_symbol, free_frag, false); + cerror_block.add_statement (free_frag); + + // TODO log uncaught error as critical warning + + if (current_method is CreationMethod) { + cerror_block.add_statement (new CCodeReturnStatement (new CCodeConstant ("NULL"))); + } else if (current_return_type is VoidType) { + cerror_block.add_statement (new CCodeReturnStatement ()); + } else if (current_return_type != null) { + cerror_block.add_statement (new CCodeReturnStatement (default_value_for_type (current_return_type, false))); + } + + return cerror_block; + } + + bool in_finally_block (CodeNode node) { + var current_node = node; + while (current_node != null) { + var try_stmt = current_node.parent_node as TryStatement; + if (try_stmt != null && try_stmt.finally_body == current_node) { + return true; + } + current_node = current_node.parent_node; + } + return false; + } + + public override void add_simple_check (CodeNode node, CCodeFragment cfrag, bool always_fails = false) { + current_method_inner_error = true; + + var inner_error = get_variable_cexpression ("_inner_error_"); + + CCodeStatement cerror_handler = null; + + if (current_try != null) { + // surrounding try found + var cerror_block = new CCodeBlock (); + + // free local variables + var free_frag = new CCodeFragment (); + append_error_free (current_symbol, free_frag, current_try); + cerror_block.add_statement (free_frag); + + var error_types = new ArrayList (); + foreach (DataType node_error_type in node.get_error_types ()) { + error_types.add (node_error_type); + } + + bool has_general_catch_clause = false; + + if (!is_in_catch) { + var handled_error_types = new ArrayList (); + foreach (CatchClause clause in current_try.get_catch_clauses ()) { + // keep track of unhandled error types + foreach (DataType node_error_type in error_types) { + if (clause.error_type == null || node_error_type.compatible (clause.error_type)) { + handled_error_types.add (node_error_type); + } + } + foreach (DataType handled_error_type in handled_error_types) { + error_types.remove (handled_error_type); + } + handled_error_types.clear (); + + // go to catch clause if error domain matches + var cgoto_stmt = new CCodeGotoStatement (clause.clabel_name); + + if (clause.error_type.equals (new ObjectType (error_class))) { + // general catch clause, this should be the last one + has_general_catch_clause = true; + cerror_block.add_statement (cgoto_stmt); + break; + } else { + var catch_type = clause.error_type as ObjectType; + var cgoto_block = new CCodeBlock (); + cgoto_block.add_statement (cgoto_stmt); + + var type_check = new CCodeFunctionCall (new CCodeIdentifier ("dova_object_is_a")); + type_check.add_argument (inner_error); + type_check.add_argument (new CCodeFunctionCall (new CCodeIdentifier ("%s_type_get".printf (catch_type.type_symbol.get_lower_case_cname ())))); + + cerror_block.add_statement (new CCodeIfStatement (type_check, cgoto_block)); + } + } + } + + if (has_general_catch_clause) { + // every possible error is already caught + // as there is a general catch clause + // no need to do anything else + } else if (error_types.size > 0) { + // go to finally clause if no catch clause matches + // and there are still unhandled error types + cerror_block.add_statement (new CCodeGotoStatement ("__finally%d".printf (current_try_id))); + } else if (in_finally_block (node)) { + // do not check unexpected errors happening within finally blocks + // as jump out of finally block is not supported + } else { + // should never happen with correct bindings + uncaught_error_statement (inner_error, cerror_block, true); + } + + cerror_handler = cerror_block; + } else if (current_method != null && current_method.get_error_types ().size > 0) { + // current method can fail, propagate error + CCodeExpression ccond = null; + + foreach (DataType error_type in current_method.get_error_types ()) { + // If Dova.Error is allowed we propagate everything + if (error_type.equals (new ObjectType (error_class))) { + ccond = null; + break; + } + + // Check the allowed error domains to propagate + var type_check = new CCodeFunctionCall (new CCodeIdentifier ("dova_object_is_a")); + type_check.add_argument (inner_error); + type_check.add_argument (new CCodeFunctionCall (new CCodeIdentifier ("%s_type_get".printf (error_class.get_lower_case_cname ())))); + if (ccond == null) { + ccond = type_check; + } else { + ccond = new CCodeBinaryExpression (CCodeBinaryOperator.OR, ccond, type_check); + } + } + + if (ccond == null) { + cerror_handler = return_with_exception (); + } else { + var cerror_block = new CCodeBlock (); + cerror_block.add_statement (new CCodeIfStatement (ccond, + return_with_exception (), + uncaught_error_statement (inner_error))); + cerror_handler = cerror_block; + } + } else { + cerror_handler = uncaught_error_statement (inner_error); + } + + if (always_fails) { + // inner_error is always set, avoid unnecessary if statement + // eliminates C warnings + cfrag.append (cerror_handler); + } else { + var ccond = new CCodeBinaryExpression (CCodeBinaryOperator.INEQUALITY, inner_error, new CCodeConstant ("NULL")); + cfrag.append (new CCodeIfStatement (ccond, cerror_handler)); + } + } + + public override void visit_try_statement (TryStatement stmt) { + int this_try_id = next_try_id++; + + var old_try = current_try; + var old_try_id = current_try_id; + var old_is_in_catch = is_in_catch; + current_try = stmt; + current_try_id = this_try_id; + is_in_catch = true; + + foreach (CatchClause clause in stmt.get_catch_clauses ()) { + clause.clabel_name = "__catch%d_%s".printf (this_try_id, clause.error_type.get_lower_case_cname ()); + } + + if (stmt.finally_body != null) { + stmt.finally_body.accept (codegen); + } + + is_in_catch = false; + stmt.body.accept (codegen); + is_in_catch = true; + + foreach (CatchClause clause in stmt.get_catch_clauses ()) { + clause.accept (codegen); + } + + current_try = old_try; + current_try_id = old_try_id; + is_in_catch = old_is_in_catch; + + var cfrag = new CCodeFragment (); + cfrag.append (stmt.body.ccodenode); + + foreach (CatchClause clause in stmt.get_catch_clauses ()) { + cfrag.append (new CCodeGotoStatement ("__finally%d".printf (this_try_id))); + cfrag.append (clause.ccodenode); + } + + cfrag.append (new CCodeLabel ("__finally%d".printf (this_try_id))); + if (stmt.finally_body != null) { + cfrag.append (stmt.finally_body.ccodenode); + } + + // check for errors not handled by this try statement + // may be handled by outer try statements or propagated + add_simple_check (stmt, cfrag, !stmt.after_try_block_reachable); + + stmt.ccodenode = cfrag; + } + + public override void visit_catch_clause (CatchClause clause) { + if (clause.error_variable != null) { + clause.error_variable.active = true; + } + + current_method_inner_error = true; + + generate_type_declaration (clause.error_type, source_declarations); + + clause.accept_children (codegen); + + var cfrag = new CCodeFragment (); + cfrag.append (new CCodeLabel (clause.clabel_name)); + + var cblock = new CCodeBlock (); + + string variable_name; + if (clause.variable_name != null) { + variable_name = get_variable_cname (clause.variable_name); + } else { + variable_name = "__err"; + } + + if (clause.variable_name != null) { + var cdecl = new CCodeDeclaration (clause.error_type.get_cname ()); + cdecl.add_declarator (new CCodeVariableDeclarator (variable_name, get_variable_cexpression ("_inner_error_"))); + cblock.add_statement (cdecl); + } else { + // error object is not used within catch statement, clear it + var cclear = new CCodeFunctionCall (new CCodeIdentifier ("dova_object_unref")); + cclear.add_argument (get_variable_cexpression ("_inner_error_")); + cblock.add_statement (new CCodeExpressionStatement (cclear)); + } + cblock.add_statement (new CCodeExpressionStatement (new CCodeAssignment (get_variable_cexpression ("_inner_error_"), new CCodeConstant ("NULL")))); + + cblock.add_statement (clause.body.ccodenode); + + cfrag.append (cblock); + + clause.ccodenode = cfrag; + } + + public override void append_local_free (Symbol sym, CCodeFragment cfrag, bool stop_at_loop = false) { + var finally_block = (Block) null; + if (sym.parent_node is TryStatement) { + finally_block = (sym.parent_node as TryStatement).finally_body; + } else if (sym.parent_node is CatchClause) { + finally_block = (sym.parent_node.parent_node as TryStatement).finally_body; + } + + if (finally_block != null) { + cfrag.append (finally_block.ccodenode); + } + + base.append_local_free (sym, cfrag, stop_at_loop); + } +} + +// vim:sw=8 noet diff --git a/codegen/valadovamethodcallmodule.vala b/codegen/valadovamethodcallmodule.vala index aca957071..53817ffad 100644 --- a/codegen/valadovamethodcallmodule.vala +++ b/codegen/valadovamethodcallmodule.vala @@ -224,6 +224,13 @@ internal class Vala.DovaMethodCallModule : DovaAssignmentModule { ccall_expr = ccomma; } + if (expr.tree_can_fail) { + // method can fail + current_method_inner_error = true; + // add &inner_error before the ellipsis arguments + ccall.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, get_variable_cexpression ("_inner_error_"))); + } + expr.ccodenode = ccall_expr; } } diff --git a/codegen/valadovaobjectmodule.vala b/codegen/valadovaobjectmodule.vala index 276cbf582..30eb4da2c 100644 --- a/codegen/valadovaobjectmodule.vala +++ b/codegen/valadovaobjectmodule.vala @@ -1060,6 +1060,8 @@ internal class Vala.DovaObjectModule : DovaArrayModule { m.accept_children (codegen); + bool inner_error = current_method_inner_error; + current_symbol = old_symbol; current_method_inner_error = old_method_inner_error; next_temp_var_id = old_next_temp_var_id; @@ -1125,6 +1127,12 @@ internal class Vala.DovaObjectModule : DovaArrayModule { function.block.add_statement (new CCodeReturnStatement (new CCodeIdentifier ("result"))); } + if (inner_error) { + var cdecl = new CCodeDeclaration ("DovaError *"); + cdecl.add_declarator (new CCodeVariableDeclarator ("_inner_error_", new CCodeConstant ("NULL"))); + cinit.append (cdecl); + } + var st = m.parent_symbol as Struct; if (m is CreationMethod && st != null && (st.is_boolean_type () || st.is_integer_type () || st.is_floating_type ())) { var cdecl = new CCodeDeclaration (st.get_cname ()); @@ -1491,6 +1499,17 @@ internal class Vala.DovaObjectModule : DovaArrayModule { generate_type_declaration (m.return_type, decl_space); } + + if (m.get_error_types ().size > 0 || (m.base_method != null && m.base_method.get_error_types ().size > 0)) { + var cparam = new CCodeFormalParameter ("error", "DovaError**"); + func.add_parameter (cparam); + if (vdeclarator != null) { + vdeclarator.add_parameter (cparam); + } + if (vcall != null) { + vcall.add_argument (new CCodeIdentifier ("error")); + } + } } public override void visit_element_access (ElementAccess expr) { diff --git a/vala/valacatchclause.vala b/vala/valacatchclause.vala index 4ac98be3f..efe71d076 100644 --- a/vala/valacatchclause.vala +++ b/vala/valacatchclause.vala @@ -124,7 +124,12 @@ public class Vala.CatchClause : CodeNode { error_variable.checked = true; } else { - error_type = new ErrorType (null, null, source_reference); + // generic catch clause + if (analyzer.context.profile == Profile.GOBJECT) { + error_type = new ErrorType (null, null, source_reference); + } else { + error_type = analyzer.error_type; + } } error_type.check (analyzer); diff --git a/vala/valaflowanalyzer.vala b/vala/valaflowanalyzer.vala index d95ef5f66..37fc7ea47 100644 --- a/vala/valaflowanalyzer.vala +++ b/vala/valaflowanalyzer.vala @@ -33,6 +33,7 @@ public class Vala.FlowAnalyzer : CodeVisitor { public bool is_error_target { get; set; } public ErrorDomain? error_domain { get; set; } public ErrorCode? error_code { get; set; } + public Class? error_class { get; set; } public bool is_finally_clause { get; set; } public BasicBlock basic_block { get; set; } public BasicBlock? last_block { get; set; } @@ -53,11 +54,12 @@ public class Vala.FlowAnalyzer : CodeVisitor { is_return_target = true; } - public JumpTarget.error_target (BasicBlock basic_block, CatchClause catch_clause, ErrorDomain? error_domain, ErrorCode? error_code) { + public JumpTarget.error_target (BasicBlock basic_block, CatchClause catch_clause, ErrorDomain? error_domain, ErrorCode? error_code, Class? error_class) { this.basic_block = basic_block; this.catch_clause = catch_clause; this.error_domain = error_domain; this.error_code = error_code; + this.error_class = error_class; is_error_target = true; } @@ -836,6 +838,7 @@ public class Vala.FlowAnalyzer : CodeVisitor { // exceptional control flow foreach (DataType error_data_type in node.get_error_types()) { var error_type = error_data_type as ErrorType; + var error_class = error_data_type.data_type as Class; current_block = last_block; unreachable_reported = true; @@ -847,20 +850,34 @@ public class Vala.FlowAnalyzer : CodeVisitor { unreachable_reported = false; break; } else if (jump_target.is_error_target) { - if (jump_target.error_domain == null - || (jump_target.error_domain == error_type.error_domain - && (jump_target.error_code == null - || jump_target.error_code == error_type.error_code))) { + if (context.profile == Profile.GOBJECT) { + if (jump_target.error_domain == null + || (jump_target.error_domain == error_type.error_domain + && (jump_target.error_code == null + || jump_target.error_code == error_type.error_code))) { + // error can always be caught by this catch clause + // following catch clauses cannot be reached by this error + current_block.connect (jump_target.basic_block); + current_block = null; + unreachable_reported = false; + break; + } else if (error_type.error_domain == null + || (error_type.error_domain == jump_target.error_domain + && (error_type.error_code == null + || error_type.error_code == jump_target.error_code))) { + // error might be caught by this catch clause + // unknown at compile time + // following catch clauses might still be reached by this error + current_block.connect (jump_target.basic_block); + } + } else if (jump_target.error_class == null || jump_target.error_class == error_class) { // error can always be caught by this catch clause // following catch clauses cannot be reached by this error current_block.connect (jump_target.basic_block); current_block = null; unreachable_reported = false; break; - } else if (error_type.error_domain == null - || (error_type.error_domain == jump_target.error_domain - && (error_type.error_code == null - || error_type.error_code == jump_target.error_code))) { + } else if (jump_target.error_class.is_subtype_of (error_class)) { // error might be caught by this catch clause // unknown at compile time // following catch clauses might still be reached by this error @@ -934,10 +951,15 @@ public class Vala.FlowAnalyzer : CodeVisitor { for (int i = catch_clauses.size - 1; i >= 0; i--) { var catch_clause = catch_clauses[i]; if (catch_clause.error_type != null) { - var error_type = catch_clause.error_type as ErrorType; - jump_stack.add (new JumpTarget.error_target (new BasicBlock (), catch_clause, catch_clause.error_type.data_type as ErrorDomain, error_type.error_code)); + if (context.profile == Profile.GOBJECT) { + var error_type = catch_clause.error_type as ErrorType; + jump_stack.add (new JumpTarget.error_target (new BasicBlock (), catch_clause, catch_clause.error_type.data_type as ErrorDomain, error_type.error_code, null)); + } else { + var error_class = catch_clause.error_type.data_type as Class; + jump_stack.add (new JumpTarget.error_target (new BasicBlock (), catch_clause, null, null, error_class)); + } } else { - jump_stack.add (new JumpTarget.error_target (new BasicBlock (), catch_clause, null, null)); + jump_stack.add (new JumpTarget.error_target (new BasicBlock (), catch_clause, null, null, null)); } } @@ -968,8 +990,14 @@ public class Vala.FlowAnalyzer : CodeVisitor { break; } - if (prev_target.error_domain == jump_target.error_domain && - prev_target.error_code == jump_target.error_code) { + if (context.profile == Profile.GOBJECT) { + if (prev_target.error_domain == jump_target.error_domain && + prev_target.error_code == jump_target.error_code) { + Report.error (stmt.source_reference, "double catch clause of same error detected"); + stmt.error = true; + return; + } + } else if (prev_target.error_class == jump_target.error_class) { Report.error (stmt.source_reference, "double catch clause of same error detected"); stmt.error = true; return; diff --git a/vala/valamethod.vala b/vala/valamethod.vala index 36196385e..61a5aa168 100644 --- a/vala/valamethod.vala +++ b/vala/valamethod.vala @@ -919,7 +919,8 @@ public class Vala.Method : Member { can_propagate_error = true; } } - if (!can_propagate_error && !((ErrorType) body_error_type).dynamic_error) { + bool is_dynamic_error = body_error_type is ErrorType && ((ErrorType) body_error_type).dynamic_error; + if (!can_propagate_error && !is_dynamic_error) { Report.warning (body_error_type.source_reference, "unhandled error `%s'".printf (body_error_type.to_string())); } } diff --git a/vala/valasemanticanalyzer.vala b/vala/valasemanticanalyzer.vala index 49817386b..70685dc5a 100644 --- a/vala/valasemanticanalyzer.vala +++ b/vala/valasemanticanalyzer.vala @@ -155,6 +155,7 @@ public class Vala.SemanticAnalyzer : CodeVisitor { public Class gerror_type; public DataType list_type; public DataType tuple_type; + public DataType error_type; public int next_lambda_id = 0; @@ -224,6 +225,7 @@ public class Vala.SemanticAnalyzer : CodeVisitor { type_type = new ObjectType ((Class) dova_ns.scope.lookup ("Type")); list_type = new ObjectType ((Class) dova_ns.scope.lookup ("List")); tuple_type = new ObjectType ((Class) dova_ns.scope.lookup ("Tuple")); + error_type = new ObjectType ((Class) dova_ns.scope.lookup ("Error")); } current_symbol = root_symbol; diff --git a/vala/valathrowstatement.vala b/vala/valathrowstatement.vala index 2a90ba37e..e3d5da9eb 100644 --- a/vala/valathrowstatement.vala +++ b/vala/valathrowstatement.vala @@ -1,6 +1,6 @@ /* valathrowstatement.vala * - * Copyright (C) 2007-2009 Jürg Billeter + * Copyright (C) 2007-2010 Jürg Billeter * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -94,7 +94,7 @@ public class Vala.ThrowStatement : CodeNode, Statement { return false; } - if (!(error_expression.value_type is ErrorType)) { + if (analyzer.context.profile == Profile.GOBJECT && !(error_expression.value_type is ErrorType)) { Report.error (error_expression.source_reference, "`%s' is not an error type".printf (error_expression.value_type.to_string ())); error = true; return false;