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