a559a4d002dce99a59d4342a1fbed0e70e34c7fd
[roobuilder] / src / codegen / valagerrormodule.vala
1 /* valagerrormodule.vala
2  *
3  * Copyright (C) 2008-2010  Jürg Billeter
4  *
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.
9
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.
14
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
18  *
19  * Author:
20  *      Jürg Billeter <j@bitron.ch>
21  *      Thijs Vermeir <thijsvermeir@gmail.com>
22  */
23
24 using GLib;
25
26 public class Vala.GErrorModule : CCodeDelegateModule {
27         private bool is_in_catch = false;
28
29         public override void generate_error_domain_declaration (ErrorDomain edomain, CCodeFile decl_space) {
30                 if (add_symbol_declaration (decl_space, edomain, get_ccode_name (edomain))) {
31                         return;
32                 }
33
34                 generate_type_declaration (gquark_type, decl_space);
35
36                 var cenum = new CCodeEnum (get_ccode_name (edomain));
37
38                 foreach (ErrorCode ecode in edomain.get_codes ()) {
39                         if (ecode.value == null) {
40                                 cenum.add_value (new CCodeEnumValue (get_ccode_name (ecode)));
41                         } else {
42                                 ecode.value.emit (this);
43                                 cenum.add_value (new CCodeEnumValue (get_ccode_name (ecode), get_cvalue (ecode.value)));
44                         }
45                 }
46
47                 decl_space.add_type_definition (cenum);
48
49                 string quark_fun_name = get_ccode_lower_case_prefix (edomain) + "quark";
50
51                 var error_domain_define = new CCodeMacroReplacement (get_ccode_upper_case_name (edomain), quark_fun_name + " ()");
52                 decl_space.add_type_definition (error_domain_define);
53
54                 var cquark_fun = new CCodeFunction (quark_fun_name, get_ccode_name (gquark_type.type_symbol));
55                 cquark_fun.modifiers |= CCodeModifiers.EXTERN;
56                 requires_vala_extern = true;
57
58                 decl_space.add_function_declaration (cquark_fun);
59         }
60
61         public override void visit_error_domain (ErrorDomain edomain) {
62                 if (edomain.comment != null) {
63                         cfile.add_type_definition (new CCodeComment (edomain.comment.content));
64                 }
65
66                 generate_error_domain_declaration (edomain, cfile);
67
68                 if (!edomain.is_internal_symbol ()) {
69                         generate_error_domain_declaration (edomain, header_file);
70                 }
71                 if (!edomain.is_private_symbol ()) {
72                         generate_error_domain_declaration (edomain, internal_header_file);
73                 }
74
75                 edomain.accept_children (this);
76
77                 string quark_fun_name = get_ccode_lower_case_prefix (edomain) + "quark";
78
79                 var cquark_fun = new CCodeFunction (quark_fun_name, get_ccode_name (gquark_type.type_symbol));
80                 push_function (cquark_fun);
81
82                 var cquark_call = new CCodeFunctionCall (new CCodeIdentifier ("g_quark_from_static_string"));
83                 cquark_call.add_argument (new CCodeConstant ("\"" + get_ccode_quark_name (edomain) + "\""));
84
85                 ccode.add_return (cquark_call);
86
87                 pop_function ();
88                 cfile.add_function (cquark_fun);
89         }
90
91         public override void visit_throw_statement (ThrowStatement stmt) {
92                 // method will fail
93                 current_method_inner_error = true;
94                 ccode.add_assignment (get_inner_error_cexpression (), get_cvalue (stmt.error_expression));
95
96                 add_simple_check (stmt, true);
97         }
98
99         public virtual void return_with_exception (CCodeExpression error_expr) {
100                 var cpropagate = new CCodeFunctionCall (new CCodeIdentifier ("g_propagate_error"));
101                 cpropagate.add_argument (new CCodeIdentifier ("error"));
102                 cpropagate.add_argument (error_expr);
103
104                 ccode.add_expression (cpropagate);
105
106                 // free local variables
107                 append_local_free (current_symbol);
108
109                 // free possibly already assigned out-parameter
110                 append_out_param_free (current_method);
111
112                 if (current_method is CreationMethod && current_method.parent_symbol is Class) {
113                         var cl = (Class) current_method.parent_symbol;
114                         ccode.add_expression (destroy_value (new GLibValue (new ObjectType (cl), new CCodeIdentifier ("self"), true)));
115                         ccode.add_return (new CCodeConstant ("NULL"));
116                 } else if (is_in_coroutine ()) {
117                         ccode.add_return (new CCodeConstant ("FALSE"));
118                 } else {
119                         return_default_value (current_return_type, true);
120                 }
121         }
122
123         void uncaught_error_statement (CCodeExpression inner_error, bool unexpected = false, CodeNode? start_at = null) {
124                 // free local variables
125                 if (start_at is TryStatement) {
126                         append_local_free (start_at.parent_node as Block);
127                 } else {
128                         append_local_free (current_symbol);
129                 }
130
131                 // free possibly already assigned out-parameter
132                 append_out_param_free (current_method);
133
134                 cfile.add_include ("glib.h");
135
136                 var ccritical = new CCodeFunctionCall (new CCodeIdentifier ("g_critical"));
137                 ccritical.add_argument (new CCodeConstant (unexpected ? "\"file %s: line %d: unexpected error: %s (%s, %d)\"" : "\"file %s: line %d: uncaught error: %s (%s, %d)\""));
138                 ccritical.add_argument (new CCodeConstant ("__FILE__"));
139                 ccritical.add_argument (new CCodeConstant ("__LINE__"));
140                 ccritical.add_argument (new CCodeMemberAccess.pointer (inner_error, "message"));
141                 var domain_name = new CCodeFunctionCall (new CCodeIdentifier ("g_quark_to_string"));
142                 domain_name.add_argument (new CCodeMemberAccess.pointer (inner_error, "domain"));
143                 ccritical.add_argument (domain_name);
144                 ccritical.add_argument (new CCodeMemberAccess.pointer (inner_error, "code"));
145
146                 var cclear = new CCodeFunctionCall (new CCodeIdentifier ("g_clear_error"));
147                 cclear.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, inner_error));
148
149                 // print critical message
150                 ccode.add_expression (ccritical);
151                 ccode.add_expression (cclear);
152
153                 if (is_in_constructor () || is_in_destructor ()) {
154                         // just print critical, do not return prematurely
155                 } else if (current_method is CreationMethod) {
156                         if (current_method.parent_symbol is Struct) {
157                                 ccode.add_return ();
158                         } else {
159                                 ccode.add_return (new CCodeConstant ("NULL"));
160                         }
161                 } else if (is_in_coroutine ()) {
162                         var unref = new CCodeFunctionCall (new CCodeIdentifier ("g_object_unref"));
163                         unref.add_argument (get_variable_cexpression ("_async_result"));
164                         ccode.add_expression (unref);
165                         ccode.add_return (new CCodeConstant ("FALSE"));
166                 } else if (current_return_type != null) {
167                         return_default_value (current_return_type, true);
168                 }
169         }
170
171         bool in_finally_block (CodeNode node) {
172                 var current_node = node;
173                 while (current_node != null) {
174                         var try_stmt = current_node.parent_node as TryStatement;
175                         if (try_stmt != null && try_stmt.finally_body == current_node) {
176                                 return true;
177                         }
178                         current_node = current_node.parent_node;
179                 }
180                 return false;
181         }
182
183         public override void add_simple_check (CodeNode node, bool always_fails = false) {
184                 current_method_inner_error = true;
185
186                 if (always_fails) {
187                         // inner_error is always set, avoid unnecessary if statement
188                         // eliminates C warnings
189                 } else {
190                         var ccond = new CCodeBinaryExpression (CCodeBinaryOperator.INEQUALITY, get_inner_error_cexpression (), new CCodeConstant ("NULL"));
191                         var unlikely = new CCodeFunctionCall (new CCodeIdentifier ("G_UNLIKELY"));
192                         unlikely.add_argument (ccond);
193                         ccode.open_if (unlikely);
194                 }
195
196                 if (current_try != null) {
197                         // surrounding try found
198
199                         // free local variables
200                         if (is_in_catch) {
201                                 append_local_free (current_symbol, null, current_catch);
202                         } else {
203                                 append_local_free (current_symbol, null, current_try);
204                         }
205
206                         var error_types = new ArrayList<DataType> ();
207                         node.get_error_types (error_types);
208
209                         bool has_general_catch_clause = false;
210
211                         if (!is_in_catch) {
212                                 var handled_error_types = new ArrayList<DataType> ();
213                                 foreach (CatchClause clause in current_try.get_catch_clauses ()) {
214                                         // keep track of unhandled error types
215                                         foreach (DataType node_error_type in error_types) {
216                                                 if (clause.error_type == null || node_error_type.compatible (clause.error_type)) {
217                                                         handled_error_types.add (node_error_type);
218                                                 }
219                                         }
220                                         foreach (DataType handled_error_type in handled_error_types) {
221                                                 error_types.remove (handled_error_type);
222                                         }
223                                         handled_error_types.clear ();
224
225                                         if (clause.error_type.equals (gerror_type)) {
226                                                 // general catch clause, this should be the last one
227                                                 has_general_catch_clause = true;
228                                                 ccode.add_goto (clause.get_attribute_string ("CCode", "cname"));
229                                                 break;
230                                         } else {
231                                                 unowned ErrorType catch_type = (ErrorType) clause.error_type;
232
233                                                 if (catch_type.error_code != null) {
234                                                         /* catch clause specifies a specific error code */
235                                                         var error_match = new CCodeFunctionCall (new CCodeIdentifier ("g_error_matches"));
236                                                         error_match.add_argument (get_inner_error_cexpression ());
237                                                         error_match.add_argument (new CCodeIdentifier (get_ccode_upper_case_name (catch_type.error_domain)));
238                                                         error_match.add_argument (new CCodeIdentifier (get_ccode_name (catch_type.error_code)));
239
240                                                         ccode.open_if (error_match);
241                                                 } else {
242                                                         /* catch clause specifies a full error domain */
243                                                         var ccond = new CCodeBinaryExpression (CCodeBinaryOperator.EQUALITY,
244                                                                         new CCodeMemberAccess.pointer (get_inner_error_cexpression (), "domain"), new CCodeIdentifier
245                                                                         (get_ccode_upper_case_name (catch_type.error_domain)));
246
247                                                         ccode.open_if (ccond);
248                                                 }
249
250                                                 // go to catch clause if error domain matches
251                                                 ccode.add_goto (clause.get_attribute_string ("CCode", "cname"));
252                                                 ccode.close ();
253                                         }
254                                 }
255                         }
256
257                         if (has_general_catch_clause) {
258                                 // every possible error is already caught
259                                 // as there is a general catch clause
260                                 // no need to do anything else
261                         } else if (error_types.size > 0) {
262                                 // go to finally clause if no catch clause matches
263                                 // and there are still unhandled error types
264                                 ccode.add_goto ("__finally%d".printf (current_try_id));
265                         } else if (in_finally_block (node)) {
266                                 // do not check unexpected errors happening within finally blocks
267                                 // as jump out of finally block is not supported
268                         } else {
269                                 // should never happen with correct bindings
270                                 uncaught_error_statement (get_inner_error_cexpression (), true, current_try);
271                         }
272                 } else if (current_method != null && current_method.tree_can_fail) {
273                         // current method can fail, propagate error
274                         CCodeBinaryExpression ccond = null;
275
276                         var error_types = new ArrayList<DataType> ();
277                         current_method.get_error_types (error_types);
278                         foreach (DataType error_type in error_types) {
279                                 // If GLib.Error is allowed we propagate everything
280                                 if (error_type.equals (gerror_type)) {
281                                         ccond = null;
282                                         break;
283                                 }
284
285                                 // Check the allowed error domains to propagate
286                                 var domain_check = new CCodeBinaryExpression (CCodeBinaryOperator.EQUALITY, new CCodeMemberAccess.pointer
287                                         (get_inner_error_cexpression (), "domain"), new CCodeIdentifier (get_ccode_upper_case_name (((ErrorType) error_type).error_domain)));
288                                 if (ccond == null) {
289                                         ccond = domain_check;
290                                 } else {
291                                         ccond = new CCodeBinaryExpression (CCodeBinaryOperator.OR, ccond, domain_check);
292                                 }
293                         }
294
295                         if (ccond != null) {
296                                 ccode.open_if (ccond);
297                                 return_with_exception (get_inner_error_cexpression ());
298
299                                 ccode.add_else ();
300                                 uncaught_error_statement (get_inner_error_cexpression ());
301                                 ccode.close ();
302                         } else {
303                                 return_with_exception (get_inner_error_cexpression ());
304                         }
305                 } else {
306                         uncaught_error_statement (get_inner_error_cexpression ());
307                 }
308
309                 if (!always_fails) {
310                         ccode.close ();
311                 }
312         }
313
314         public override void visit_try_statement (TryStatement stmt) {
315                 int this_try_id = next_try_id++;
316
317                 var old_try = current_try;
318                 var old_try_id = current_try_id;
319                 var old_is_in_catch = is_in_catch;
320                 var old_catch = current_catch;
321                 current_try = stmt;
322                 current_try_id = this_try_id;
323                 is_in_catch = true;
324
325                 foreach (CatchClause clause in stmt.get_catch_clauses ()) {
326                         clause.set_attribute_string ("CCode", "cname", "__catch%d_%s".printf (this_try_id, get_ccode_lower_case_name (clause.error_type)));
327                 }
328
329                 is_in_catch = false;
330                 stmt.body.emit (this);
331                 is_in_catch = true;
332
333                 foreach (CatchClause clause in stmt.get_catch_clauses ()) {
334                         current_catch = clause;
335                         ccode.add_goto ("__finally%d".printf (this_try_id));
336                         clause.emit (this);
337                 }
338
339                 current_try = old_try;
340                 current_try_id = old_try_id;
341                 is_in_catch = old_is_in_catch;
342                 current_catch = old_catch;
343
344                 ccode.add_label ("__finally%d".printf (this_try_id));
345                 if (stmt.finally_body != null) {
346                         // use a dedicated inner_error variable, if there
347                         // is some error handling happening in finally-block
348                         current_inner_error_id++;
349                         stmt.finally_body.emit (this);
350                         current_inner_error_id--;
351                 }
352
353                 // check for errors not handled by this try statement
354                 // may be handled by outer try statements or propagated
355                 add_simple_check (stmt, !stmt.after_try_block_reachable);
356         }
357
358         public override void visit_catch_clause (CatchClause clause) {
359                 current_method_inner_error = true;
360
361                 var error_type = (ErrorType) clause.error_type;
362                 if (error_type.error_domain != null) {
363                         generate_error_domain_declaration (error_type.error_domain, cfile);
364                 }
365
366                 ccode.add_label (clause.get_attribute_string ("CCode", "cname"));
367
368                 ccode.open_block ();
369
370                 if (clause.error_variable != null && clause.error_variable.used) {
371                         visit_local_variable (clause.error_variable);
372                         ccode.add_assignment (get_variable_cexpression (get_local_cname (clause.error_variable)), get_inner_error_cexpression ());
373                         ccode.add_assignment (get_inner_error_cexpression (), new CCodeConstant ("NULL"));
374                 } else {
375                         if (clause.error_variable != null) {
376                                 clause.error_variable.unreachable = true;
377                         }
378                         // error object is not used within catch statement, clear it
379                         cfile.add_include ("glib.h");
380                         var cclear = new CCodeFunctionCall (new CCodeIdentifier ("g_clear_error"));
381                         cclear.add_argument (new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, get_inner_error_cexpression ()));
382                         ccode.add_expression (cclear);
383                 }
384
385                 clause.body.emit (this);
386
387                 ccode.close ();
388         }
389
390         protected override void append_scope_free (Symbol sym, CodeNode? stop_at = null) {
391                 base.append_scope_free (sym, stop_at);
392
393                 if (!(stop_at is TryStatement || stop_at is CatchClause)) {
394                         var finally_block = (Block) null;
395                         if (sym.parent_node is TryStatement) {
396                                 finally_block = ((TryStatement) sym.parent_node).finally_body;
397                         } else if (sym.parent_node is CatchClause) {
398                                 finally_block = ((TryStatement) sym.parent_node.parent_node).finally_body;
399                         }
400
401                         if (finally_block != null && finally_block != sym) {
402                                 finally_block.emit (this);
403                         }
404                 }
405         }
406 }
407
408 // vim:sw=8 noet