Commit 31cb7881 authored by Jürg Billeter's avatar Jürg Billeter

dova: Add initial support for error handling

parent b402c6bf
......@@ -39,6 +39,7 @@ libvala_la_VALASOURCES = \
valadovabasemodule.vala \
valadovacontrolflowmodule.vala \
valadovadelegatemodule.vala \
valadovaerrormodule.vala \
valadovamemberaccessmodule.vala \
valadovamethodcallmodule.vala \
valadovamethodmodule.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);
......
......@@ -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<Symbol> 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 ();
......
/* 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 <j@bitron.ch>
* Thijs Vermeir <thijsvermeir@gmail.com>
*/
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<DataType> ();
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<DataType> ();
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
......@@ -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;
}
}
......
......@@ -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) {
......
......@@ -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);
......
......@@ -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;
......
......@@ -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()));
}
}
......
......@@ -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;
......
/* 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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment