diff options
author | Frank Ch. Eigler <fche@elastic.org> | 2010-03-03 00:28:22 -0500 |
---|---|---|
committer | Frank Ch. Eigler <fche@elastic.org> | 2010-03-03 00:33:43 -0500 |
commit | f4fe2e932cc8f445e9e1bc52863e11b669e3afc9 (patch) | |
tree | ba062952c0a37f02ebcf0eb2f533d44ee41fdb25 | |
parent | d105f6642677bd9ef1b20d1ba180ba0163cb0fa6 (diff) | |
download | systemtap-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-- | NEWS | 6 | ||||
-rw-r--r-- | doc/langref.tex | 24 | ||||
-rw-r--r-- | elaborate.cxx | 54 | ||||
-rw-r--r-- | elaborate.h | 1 | ||||
-rw-r--r-- | parse.cxx | 42 | ||||
-rw-r--r-- | parse.h | 2 | ||||
-rw-r--r-- | stap.1.in | 11 | ||||
-rw-r--r-- | staptree.cxx | 69 | ||||
-rw-r--r-- | staptree.h | 16 | ||||
-rwxr-xr-x | testsuite/buildok/fortyone.stp | 37 | ||||
-rwxr-xr-x | testsuite/semko/fortynine.stp | 3 | ||||
-rw-r--r-- | testsuite/systemtap.base/trycatch.exp | 41 | ||||
-rw-r--r-- | translate.cxx | 58 |
13 files changed, 351 insertions, 13 deletions
@@ -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); @@ -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") @@ -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 (); @@ -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)); @@ -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) { |