summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFrank Ch. Eigler <fche@elastic.org>2010-03-03 00:28:22 -0500
committerFrank Ch. Eigler <fche@elastic.org>2010-03-03 00:33:43 -0500
commitf4fe2e932cc8f445e9e1bc52863e11b669e3afc9 (patch)
treeba062952c0a37f02ebcf0eb2f533d44ee41fdb25
parentd105f6642677bd9ef1b20d1ba180ba0163cb0fa6 (diff)
downloadsystemtap-steved-f4fe2e932cc8f445e9e1bc52863e11b669e3afc9.tar.gz
systemtap-steved-f4fe2e932cc8f445e9e1bc52863e11b669e3afc9.tar.xz
systemtap-steved-f4fe2e932cc8f445e9e1bc52863e11b669e3afc9.zip
PR11004: try / catch error-handling script syntax
* parse.h (try_block): New class. Update basic visitors. * staptree.cxx: Implement basic visitors. * parse.cxx (expect_kw): Fix to actually look for keywords. (parse_try_block): New function. (lexer ctor): Designate 'try' and 'catch' as keywords. * elaborate.cxx (dead_assignment_remover, dead_statmtexpr_remover): Optimize. (other visitors): Implement. * translate.cxx (c_unparser): Implement via super-handy __local__ labels. (emit_probe, emit_function): Make outer out: label also __local__. * testsuite/buildok/fortyone.stp, semko/fortynine.stp, systemtap.base/trycatch.exp: Test it. * NEWS, doc/langref.txt, stap.1.in: Document it.
-rw-r--r--NEWS6
-rw-r--r--doc/langref.tex24
-rw-r--r--elaborate.cxx54
-rw-r--r--elaborate.h1
-rw-r--r--parse.cxx42
-rw-r--r--parse.h2
-rw-r--r--stap.1.in11
-rw-r--r--staptree.cxx69
-rw-r--r--staptree.h16
-rwxr-xr-xtestsuite/buildok/fortyone.stp37
-rwxr-xr-xtestsuite/semko/fortynine.stp3
-rw-r--r--testsuite/systemtap.base/trycatch.exp41
-rw-r--r--translate.cxx58
13 files changed, 351 insertions, 13 deletions
diff --git a/NEWS b/NEWS
index d9f70676..8e33631d 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,11 @@
* What's new
+- A new construct for error handling is available. It is similar to c++
+ exception catching, using try and catch as new keywords. Within a handler
+ or function, the following is valid and may be nested:
+ try { /* arbitrary statements */ }
+ catch (er) { /* e.g. println("caught error ", er) */ }
+
- A new command line flag '-W' forces systemtap to abort translation of
a script if any warnings are produced. It is similar to gcc's -Werror.
(If '-w' is also supplied to suppress warnings, it wins.)
diff --git a/doc/langref.tex b/doc/langref.tex
index 0f5b92d2..27ac1c33 100644
--- a/doc/langref.tex
+++ b/doc/langref.tex
@@ -52,7 +52,7 @@
\newpage{}
This document was derived from other documents contributed to the SystemTap project by employees of Red Hat, IBM and Intel.\newline
-Copyright \copyright\space 2007-2009 Red Hat Inc.\newline
+Copyright \copyright\space 2007-2010 Red Hat Inc.\newline
Copyright \copyright\space 2007-2009 IBM Corp.\newline
Copyright \copyright\space 2007 Intel Corporation.\newline
@@ -1869,6 +1869,28 @@ nesting loop statement, such as within a \texttt{while, for,} or \texttt{foreach
statement. The syntax and semantics are the same as those used in C.
+\subsection{try/catch}
+\index{try}
+\index{catch}
+Use \texttt{try}/\texttt{catch} to handle most kinds of run-time errors within the script
+instead of aborting the probe handler in progress. The semantics are similar
+to C++ in that try/catch blocks may be nested. The error string may be captured
+by optionally naming a variable which is to receive it.
+
+\begin{vindent}
+\begin{verbatim}
+try {
+ /* do something */
+ /* trigger error like kread(0), or divide by zero, or error("foo") */
+} catch (msg) { /* omit (msg) entirely if not interested */
+ /* println("caught error ", msg) */
+ /* handle error */
+}
+/* execution continues */
+\end{verbatim}
+\end{vindent}
+
+
\subsection{delete}
\index{delete}
\texttt{delete} removes an element.
diff --git a/elaborate.cxx b/elaborate.cxx
index a2ab8522..72f12baf 100644
--- a/elaborate.cxx
+++ b/elaborate.cxx
@@ -2206,6 +2206,7 @@ struct dead_assignment_remover: public update_visitor
session(s), relaxed_p(r), vut(v) {}
void visit_assignment (assignment* e);
+ void visit_try_block (try_block *s);
};
@@ -2262,6 +2263,27 @@ dead_assignment_remover::visit_assignment (assignment* e)
provide (e);
}
+
+void
+dead_assignment_remover::visit_try_block (try_block *s)
+{
+ replace (s->try_block);
+ if (s->catch_error_var)
+ {
+ vardecl* errvar = s->catch_error_var->referent;
+ if (vut.read.find(errvar) == vut.read.end()) // never read?
+ {
+ if (session.verbose>2)
+ clog << "Eliding unused error string catcher " << errvar->name
+ << " at " << *s->tok << endl;
+ s->catch_error_var = 0;
+ }
+ }
+ replace (s->catch_block);
+ provide (s);
+}
+
+
// Let's remove assignments to variables that are never read. We
// rewrite "(foo = expr)" as "(expr)". This makes foo a candidate to
// be optimized away as an unused variable, and expr a candidate to be
@@ -2301,6 +2323,7 @@ struct dead_stmtexpr_remover: public update_visitor
session(s), relaxed_p(r) {}
void visit_block (block *s);
+ void visit_try_block (try_block *s);
void visit_null_statement (null_statement *s);
void visit_if_statement (if_statement* s);
void visit_foreach_loop (foreach_loop *s);
@@ -2363,6 +2386,22 @@ dead_stmtexpr_remover::visit_block (block *s)
provide (s);
}
+
+void
+dead_stmtexpr_remover::visit_try_block (try_block *s)
+{
+ replace (s->try_block, true);
+ replace (s->catch_block, true); // null catch{} is ok and useful
+ if (s->try_block == 0)
+ {
+ if (session.verbose>2)
+ clog << "Eliding empty try {} block " << *s->tok << endl;
+ s = 0;
+ }
+ provide (s);
+}
+
+
void
dead_stmtexpr_remover::visit_if_statement (if_statement *s)
{
@@ -4262,6 +4301,21 @@ typeresolution_info::visit_block (block* e)
void
+typeresolution_info::visit_try_block (try_block* e)
+{
+ if (e->try_block)
+ e->try_block->visit (this);
+ if (e->catch_error_var)
+ {
+ t = pe_string;
+ e->catch_error_var->visit (this);
+ }
+ if (e->catch_block)
+ e->catch_block->visit (this);
+}
+
+
+void
typeresolution_info::visit_embeddedcode (embeddedcode*)
{
}
diff --git a/elaborate.h b/elaborate.h
index d5ec34d9..ec42ed7b 100644
--- a/elaborate.h
+++ b/elaborate.h
@@ -71,6 +71,7 @@ struct typeresolution_info: public visitor
exp_type t; // implicit parameter for nested visit call; may clobber
void visit_block (block* s);
+ void visit_try_block (try_block* s);
void visit_embeddedcode (embeddedcode* s);
void visit_null_statement (null_statement* s);
void visit_expr_statement (expr_statement* s);
diff --git a/parse.cxx b/parse.cxx
index 9d32a8cb..c517cd1a 100644
--- a/parse.cxx
+++ b/parse.cxx
@@ -1,5 +1,5 @@
// recursive descent parser for systemtap scripts
-// Copyright (C) 2005-2009 Red Hat Inc.
+// Copyright (C) 2005-2010 Red Hat Inc.
// Copyright (C) 2006 Intel Corporation.
// Copyright (C) 2007 Bull S.A.S
//
@@ -606,7 +606,7 @@ parser::expect_op (std::string const & expected)
const token*
parser::expect_kw (std::string const & expected)
{
- return expect_known (tok_identifier, expected);
+ return expect_known (tok_keyword, expected);
}
const token*
@@ -703,6 +703,8 @@ lexer::lexer (istream& input, const string& in, systemtap_session& s):
keywords.insert("next");
keywords.insert("string");
keywords.insert("long");
+ keywords.insert("try");
+ keywords.insert("catch");
}
}
@@ -1283,6 +1285,40 @@ parser::parse_stmt_block ()
}
+try_block*
+parser::parse_try_block ()
+{
+ try_block* pb = new try_block;
+
+ pb->tok = expect_kw ("try");
+ pb->try_block = parse_stmt_block();
+ expect_kw ("catch");
+
+ const token* t = peek ();
+ if (t->type == tok_operator && t->content == "(")
+ {
+ next (); // swallow the '('
+
+ t = next();
+ if (! (t->type == tok_identifier))
+ throw parse_error ("expected identifier");
+ symbol* sym = new symbol;
+ sym->tok = t;
+ sym->name = t->content;
+ pb->catch_error_var = sym;
+
+ expect_op (")");
+ }
+ else
+ pb->catch_error_var = 0;
+
+ pb->catch_block = parse_stmt_block();
+
+ return pb;
+}
+
+
+
statement*
parser::parse_statement ()
{
@@ -1292,6 +1328,8 @@ parser::parse_statement ()
return new null_statement (next ());
else if (t && t->type == tok_operator && t->content == "{")
return parse_stmt_block (); // Don't squash semicolons.
+ else if (t && t->type == tok_keyword && t->content == "try")
+ return parse_try_block (); // Don't squash semicolons.
else if (t && t->type == tok_keyword && t->content == "if")
return parse_if_statement (); // Don't squash semicolons.
else if (t && t->type == tok_keyword && t->content == "for")
diff --git a/parse.h b/parse.h
index 8f34442a..2b2dd3ac 100644
--- a/parse.h
+++ b/parse.h
@@ -102,6 +102,7 @@ struct embeddedcode;
struct probe_point;
struct literal;
struct block;
+struct try_block;
struct for_loop;
struct statement;
struct if_statement;
@@ -175,6 +176,7 @@ private: // nonterminals
probe_point* parse_probe_point ();
literal* parse_literal ();
block* parse_stmt_block ();
+ try_block* parse_try_block ();
statement* parse_statement ();
if_statement* parse_if_statement ();
for_loop* parse_for_loop ();
diff --git a/stap.1.in b/stap.1.in
index 6ef39ba5..531b0f88 100644
--- a/stap.1.in
+++ b/stap.1.in
@@ -493,7 +493,16 @@ not taken anywhere, then a return statement is not needed, and the
function will have a special "unknown" type with no return value.
.TP
.BR next
-Return now from enclosing probe handler.
+Return now from enclosing probe handler. This is especially useful in
+probe aliases that apply event filtering predicates.
+.TP
+.BR try " { STMT1 } " catch " { STMT2 }"
+Run the statements in the first block. Upon any run-time errors, abort
+STMT1 and start executing STMT2. Any errors in STMT2 will propagate to
+outer try/catch blocks, if any.
+.TP
+.BR try " { STMT1 } " catch "(VAR) { STMT2 }"
+Same as above, plus assign the error message to the string scalar variable VAR.
.TP
.BR delete " ARRAY[INDEX1, INDEX2, ...]"
Remove from ARRAY the element specified by the index tuple. The value will no
diff --git a/staptree.cxx b/staptree.cxx
index 9276586b..cca79981 100644
--- a/staptree.cxx
+++ b/staptree.cxx
@@ -1,5 +1,5 @@
// parse tree functions
-// Copyright (C) 2005-2009 Red Hat Inc.
+// Copyright (C) 2005-2010 Red Hat Inc.
//
// This file is part of systemtap, and is free software. You can
// redistribute it and/or modify it under the terms of the GNU General
@@ -903,6 +903,18 @@ block::block (statement* car, statement* cdr)
+void try_block::print (ostream& o) const
+{
+ o << "try {" << endl;
+ if (try_block) o << *try_block << endl;
+ o << "} catch ";
+ if (catch_error_var) o << "(" << *catch_error_var << ") ";
+ o << "{" << endl;
+ if (catch_block) o << *catch_block << endl;
+ o << "}" << endl;
+}
+
+
void for_loop::print (ostream& o) const
{
o << "for (";
@@ -1140,6 +1152,13 @@ block::visit (visitor* u)
void
+try_block::visit (visitor* u)
+{
+ u->visit_try_block (this);
+}
+
+
+void
embeddedcode::visit (visitor* u)
{
u->visit_embeddedcode (this);
@@ -1525,6 +1544,17 @@ traversing_visitor::visit_block (block* s)
}
void
+traversing_visitor::visit_try_block (try_block* s)
+{
+ if (s->try_block)
+ s->try_block->visit (this);
+ if (s->catch_error_var)
+ s->catch_error_var->visit (this);
+ if (s->catch_block)
+ s->catch_block->visit (this);
+}
+
+void
traversing_visitor::visit_embeddedcode (embeddedcode*)
{
}
@@ -1778,6 +1808,21 @@ functioncall_traversing_visitor::visit_functioncall (functioncall* e)
void
+varuse_collecting_visitor::visit_try_block (try_block *s)
+{
+ if (s->try_block)
+ s->try_block->visit (this);
+ if (s->catch_error_var)
+ written.insert (s->catch_error_var->referent);
+ if (s->catch_block)
+ s->catch_block->visit (this);
+
+ // NB: don't functioncall_traversing_visitor::visit_try_block (s);
+ // since that would count s->catch_error_var as a read also.
+}
+
+
+void
varuse_collecting_visitor::visit_embeddedcode (embeddedcode *s)
{
assert (current_function); // only they get embedded code
@@ -2067,6 +2112,13 @@ throwing_visitor::visit_block (block* s)
}
void
+throwing_visitor::visit_try_block (try_block* s)
+{
+ throwone (s->tok);
+}
+
+
+void
throwing_visitor::visit_embeddedcode (embeddedcode* s)
{
throwone (s->tok);
@@ -2279,6 +2331,15 @@ update_visitor::visit_block (block* s)
}
void
+update_visitor::visit_try_block (try_block* s)
+{
+ replace (s->try_block);
+ replace (s->catch_error_var);
+ replace (s->catch_block);
+ provide (s);
+}
+
+void
update_visitor::visit_embeddedcode (embeddedcode* s)
{
provide (s);
@@ -2556,6 +2617,12 @@ deep_copy_visitor::visit_block (block* s)
}
void
+deep_copy_visitor::visit_try_block (try_block* s)
+{
+ update_visitor::visit_try_block(new try_block(*s));
+}
+
+void
deep_copy_visitor::visit_embeddedcode (embeddedcode* s)
{
update_visitor::visit_embeddedcode(new embeddedcode(*s));
diff --git a/staptree.h b/staptree.h
index 88973979..f357eb28 100644
--- a/staptree.h
+++ b/staptree.h
@@ -527,6 +527,16 @@ struct block: public statement
};
+struct try_block: public statement
+{
+ block* try_block; // may be 0
+ block* catch_block; // may be 0
+ symbol* catch_error_var; // may be 0
+ void print (std::ostream& o) const;
+ void visit (visitor* u);
+};
+
+
struct expr_statement;
struct for_loop: public statement
{
@@ -698,6 +708,7 @@ struct visitor
virtual ~visitor () {}
virtual void visit_block (block *s) = 0;
+ virtual void visit_try_block (try_block *s) = 0;
virtual void visit_embeddedcode (embeddedcode *s) = 0;
virtual void visit_null_statement (null_statement *s) = 0;
virtual void visit_expr_statement (expr_statement *s) = 0;
@@ -740,6 +751,7 @@ struct visitor
struct traversing_visitor: public visitor
{
void visit_block (block *s);
+ void visit_try_block (try_block *s);
void visit_embeddedcode (embeddedcode *s);
void visit_null_statement (null_statement *s);
void visit_expr_statement (expr_statement *s);
@@ -805,6 +817,7 @@ struct varuse_collecting_visitor: public functioncall_traversing_visitor
current_lvalue(0),
current_lrvalue(0) {}
void visit_embeddedcode (embeddedcode *s);
+ void visit_try_block (try_block *s);
void visit_delete_statement (delete_statement *s);
void visit_print_format (print_format *e);
void visit_assignment (assignment *e);
@@ -834,6 +847,7 @@ struct throwing_visitor: public visitor
virtual void throwone (const token* t);
void visit_block (block *s);
+ void visit_try_block (try_block *s);
void visit_embeddedcode (embeddedcode *s);
void visit_null_statement (null_statement *s);
void visit_expr_statement (expr_statement *s);
@@ -901,6 +915,7 @@ struct update_visitor: public visitor
virtual ~update_visitor() { assert(targets.empty()); }
virtual void visit_block (block *s);
+ virtual void visit_try_block (try_block *s);
virtual void visit_embeddedcode (embeddedcode *s);
virtual void visit_null_statement (null_statement *s);
virtual void visit_expr_statement (expr_statement *s);
@@ -957,6 +972,7 @@ struct deep_copy_visitor: public update_visitor
}
virtual void visit_block (block *s);
+ virtual void visit_try_block (try_block *s);
virtual void visit_embeddedcode (embeddedcode *s);
virtual void visit_null_statement (null_statement *s);
virtual void visit_expr_statement (expr_statement *s);
diff --git a/testsuite/buildok/fortyone.stp b/testsuite/buildok/fortyone.stp
new file mode 100755
index 00000000..7a1fceb8
--- /dev/null
+++ b/testsuite/buildok/fortyone.stp
@@ -0,0 +1,37 @@
+#! stap -p4
+
+# NB: also used for systemtap.base/trycatch.exp
+
+function foo () {
+ try { error("foo") println("KO 1") } catch { println("OK 1") error ("bar") }
+}
+
+function koo () {
+ try { println(1/(0*foo())) println("KO 2") } catch (er) { println("OK 2 ",er) error("baz") }
+}
+
+probe begin {
+ try { koo () println("KO 3") } catch { println("OK 3") }
+ try { /* empty */ } catch { log("KO 4") }
+ try { log("OK 4") } catch { /* empty */ }
+ try { /* empty */ } catch { /* empty */ }
+ println("OK 5")
+
+ // check for proper loop / try/catch nesting
+ for (i=0;i<5;i++) try { error("me") } catch { break }
+ println ((i==0) ? "OK 7" : "KO 7")
+
+ for (j=0;j<5;j++) { for (i=0;i<5;i++) try { break } catch { continue } }
+ println ((i==0 && j==5) ? "OK 7a" : "KO 7a")
+
+ for (i=0;i<5;i++) try { error("me") } catch { continue }
+ println ((i==5) ? "OK 8" : "KO 8")
+
+ for (j=0;j<5;j++) { for (i=0;i<5;i++) try { continue } catch { break } }
+ println ((i==5 && j==5) ? "OK 8a" : "KO 8a")
+
+ // check that MAXACTIONS cannot be bypassed, e.g. with nested catch {}'s
+ try { for (i=0; i<100000; i++) ; println("KO 6") } catch { println("KO 5") }
+ println("KO 6")
+ // will result in MAXACTION error
+}
diff --git a/testsuite/semko/fortynine.stp b/testsuite/semko/fortynine.stp
new file mode 100755
index 00000000..01e5bf49
--- /dev/null
+++ b/testsuite/semko/fortynine.stp
@@ -0,0 +1,3 @@
+#! stap -up2
+
+probe begin { try {} catch (er) {println(er+2)} }
diff --git a/testsuite/systemtap.base/trycatch.exp b/testsuite/systemtap.base/trycatch.exp
new file mode 100644
index 00000000..f0a133e2
--- /dev/null
+++ b/testsuite/systemtap.base/trycatch.exp
@@ -0,0 +1,41 @@
+set test "trycatch"
+
+if {! [installtest_p]} { untested $test; return }
+
+set ok 0
+set ko 0
+spawn stap $srcdir/buildok/fortyone.stp
+expect {
+ -timeout 30
+ -re {^ERROR: MAXACTION[^\r\n]*\r\n} { incr ok; exp_continue }
+ -re {^WARNING: Number of errors[^\r\n]*\r\n} { incr ok; exp_continue }
+ -re {^Pass 5: run failed[^\r\n]*\r\n} { incr ok; exp_continue }
+ -re {^OK[^\r\n]*\r\n} { incr ok; exp_continue }
+ -re {^KO[^\r\n]*\r\n} { incr ko; exp_continue }
+ timeout { fail "$test (timeout)" }
+ eof { }
+}
+wait; catch { close }
+if {$ok == 12 && $ko == 0} then {pass $test} else {fail "$test ($ok $ko)"}
+
+
+set test "trycatch -u"
+
+set ok 0
+set ko 0
+spawn stap -u $srcdir/buildok/fortyone.stp
+expect {
+ -timeout 30
+ -re {^ERROR: MAXACTION[^\r\n]*\r\n} { incr ok; exp_continue }
+ -re {^WARNING: Number of errors[^\r\n]*\r\n} { incr ok; exp_continue }
+ -re {^Pass 5: run failed[^\r\n]*\r\n} { incr ok; exp_continue }
+ -re {^OK[^\r\n]*\r\n} { incr ok; exp_continue }
+ -re {^KO[^\r\n]*\r\n} { incr ko; exp_continue }
+ timeout { fail "$test (timeout)" }
+ eof { }
+}
+wait; catch { close }
+if {$ok == 12 && $ko == 0} then {pass $test} else {fail "$test ($ok $ko)"}
+
+
+
diff --git a/translate.cxx b/translate.cxx
index c1d1383a..fa3d1ba0 100644
--- a/translate.cxx
+++ b/translate.cxx
@@ -124,6 +124,7 @@ struct c_unparser: public unparser, public visitor
void record_actions (unsigned actions, bool update=false);
void visit_block (block* s);
+ void visit_try_block (try_block* s);
void visit_embeddedcode (embeddedcode* s);
void visit_null_statement (null_statement* s);
void visit_expr_statement (expr_statement* s);
@@ -1486,16 +1487,15 @@ c_unparser::emit_function (functiondecl* v)
this->tmpvar_counter = 0;
this->action_counter = 0;
+ o->newline() << "__label__ out;";
o->newline()
<< "struct function_" << c_varname (v->name) << "_locals * "
- << " __restrict__ l =";
- o->newline(1)
+ << " __restrict__ l = "
<< "& c->locals[c->nesting+1].function_" << c_varname (v->name) // NB: nesting+1
<< ";";
- o->newline(-1) << "(void) l;"; // make sure "l" is marked used
+ o->newline() << "(void) l;"; // make sure "l" is marked used
o->newline() << "#define CONTEXT c";
o->newline() << "#define THIS l";
- o->newline() << "if (0) goto out;"; // make sure out: is marked used
// set this, in case embedded-c code sets last_error but doesn't otherwise identify itself
o->newline() << "c->last_stmt = " << lex_cast_qstring(*v->tok) << ";";
@@ -1546,7 +1546,7 @@ c_unparser::emit_function (functiondecl* v)
}
o->newline(-1) << "out:";
- o->newline(1) << ";";
+ o->newline(1) << "if (0) goto out;"; // make sure out: is marked used
// Function prologue: this is why we redirect the "return" above.
// Decrement nesting level.
@@ -1651,6 +1651,8 @@ c_unparser::emit_probe (derived_probe* v)
probe_contents[oss.str()] = v->name;
+ o->newline() << "__label__ out;";
+
// emit static read/write lock decls for global variables
varuse_collecting_visitor vut(*session);
if (v->needs_global_locks ())
@@ -1660,9 +1662,9 @@ c_unparser::emit_probe (derived_probe* v)
}
// initialize frame pointer
- o->newline() << "struct " << v->name << "_locals * __restrict__ l =";
- o->newline(1) << "& c->probe_locals." << v->name << ";";
- o->newline(-1) << "(void) l;"; // make sure "l" is marked used
+ o->newline() << "struct " << v->name << "_locals * __restrict__ l = "
+ << "& c->probe_locals." << v->name << ";";
+ o->newline() << "(void) l;"; // make sure "l" is marked used
// Emit runtime safety net for unprivileged mode.
v->emit_unprivileged_assertion (o);
@@ -2355,6 +2357,46 @@ c_unparser::visit_block (block *s)
}
+void c_unparser::visit_try_block (try_block *s)
+{
+ o->newline() << "{";
+ o->newline(1) << "__label__ normal_out;";
+ o->newline(1) << "{";
+ o->newline() << "__label__ out;";
+
+ assert (!session->unoptimized || s->try_block); // dead_stmtexpr_remover would zap it
+ if (s->try_block)
+ s->try_block->visit (this);
+
+ o->newline() << "if (likely(c->last_error == NULL)) goto normal_out;";
+
+ o->newline() << "if (0) goto out;"; // to prevent 'unused label' warnings
+ o->newline() << "out:";
+ if (s->catch_error_var)
+ {
+ var cev(getvar(s->catch_error_var->referent, s->catch_error_var->tok));
+ c_strcpy (cev.value(), "c->last_error");
+ }
+ o->newline() << "c->last_error = NULL;";
+
+ // Close the scope of the above nested 'out' label, to make sure
+ // that the catch block, should it encounter errors, does not resolve
+ // a 'goto out;' to the above label, causing infinite looping.
+ o->newline(-1) << "}";
+
+ // Prevent the catch{} handler from even starting if MAXACTIONS have
+ // already been used up.
+ record_actions(0, true);
+
+ if (s->catch_block)
+ s->catch_block->visit (this);
+
+ o->newline() << "normal_out:";
+ o->newline() << ";"; // to have _some_ statement
+ o->newline(-1) << "}";
+}
+
+
void
c_unparser::visit_embeddedcode (embeddedcode *s)
{