* [PATCH] Add support for return statements without values
@ 2018-08-25 1:09 Yichun Zhang (agentzh)
2018-08-28 20:53 ` [PATCH v2] " Yichun Zhang (agentzh)
0 siblings, 1 reply; 5+ messages in thread
From: Yichun Zhang (agentzh) @ 2018-08-25 1:09 UTC (permalink / raw)
To: systemtap; +Cc: Yichun Zhang (agentzh)
For user-defined stap functions which do not return any values, it can
be convenient to allow *bare* return statements to take shortcuts in
the control flow of the function body.
Added tests to cover various cases like use of plain `return` in a
function actually returning some values. Both the "kernel" and "dyninst"
runtimes are covered.
A fix for the bpf translator is also included.
---
bpf-translate.cxx | 3 +-
elaborate.cxx | 8 ++
parse.cxx | 7 +-
staptree.cxx | 8 +-
testsuite/systemtap.base/return_no_val.exp | 135 +++++++++++++++++++++++++++
testsuite/systemtap.base/return_no_val_1.stp | 11 +++
testsuite/systemtap.base/return_no_val_2.stp | 11 +++
testsuite/systemtap.base/return_no_val_3.stp | 10 ++
translate.cxx | 11 ++-
9 files changed, 198 insertions(+), 6 deletions(-)
create mode 100644 testsuite/systemtap.base/return_no_val.exp
create mode 100644 testsuite/systemtap.base/return_no_val_1.stp
create mode 100644 testsuite/systemtap.base/return_no_val_2.stp
create mode 100644 testsuite/systemtap.base/return_no_val_3.stp
diff --git a/bpf-translate.cxx b/bpf-translate.cxx
index 95c68ce8d..26ceb9fcf 100644
--- a/bpf-translate.cxx
+++ b/bpf-translate.cxx
@@ -915,7 +915,8 @@ bpf_unparser::visit_return_statement (return_statement* s)
if (func_return.empty ())
throw SEMANTIC_ERROR (_("cannot 'return' outside function"), s->tok);
assert (!func_return_val.empty ());
- emit_mov (func_return_val.back (), emit_expr (s->value));
+ if (s->value)
+ emit_mov (func_return_val.back (), emit_expr (s->value));
emit_jmp (func_return.back ());
}
diff --git a/elaborate.cxx b/elaborate.cxx
index c55818f02..cd03df408 100644
--- a/elaborate.cxx
+++ b/elaborate.cxx
@@ -6892,6 +6892,14 @@ typeresolution_info::visit_return_statement (return_statement* e)
exp_type& e_type = current_function->type;
t = current_function->type;
+
+ if (!e->value)
+ {
+ if (e_type != pe_unknown)
+ mismatch (e->tok, pe_unknown, current_function);
+ return;
+ }
+
e->value->visit (this);
if (e_type != pe_unknown && e->value->type != pe_unknown
diff --git a/parse.cxx b/parse.cxx
index 751f56add..95abfeeea 100644
--- a/parse.cxx
+++ b/parse.cxx
@@ -2868,7 +2868,12 @@ parser::parse_return_statement ()
throw PARSE_ERROR (_("found 'return' not in function context"));
return_statement* s = new return_statement;
s->tok = t;
- s->value = parse_expression ();
+
+ t = peek ();
+ if (t->type == tok_operator && (t->content == ";" || t->content == "}"))
+ s->value = NULL; // no return value
+ else
+ s->value = parse_expression ();
return s;
}
diff --git a/staptree.cxx b/staptree.cxx
index d1193e336..25cc9fe7c 100644
--- a/staptree.cxx
+++ b/staptree.cxx
@@ -1313,7 +1313,10 @@ void expr_statement::print (ostream& o) const
void return_statement::print (ostream& o) const
{
- o << "return " << *value;
+ if (value)
+ o << "return " << *value;
+ else
+ o << "return";
}
@@ -1964,7 +1967,8 @@ traversing_visitor::visit_foreach_loop (foreach_loop* s)
void
traversing_visitor::visit_return_statement (return_statement* s)
{
- s->value->visit (this);
+ if (s->value)
+ s->value->visit (this);
}
void
diff --git a/testsuite/systemtap.base/return_no_val.exp b/testsuite/systemtap.base/return_no_val.exp
new file mode 100644
index 000000000..96af59286
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val.exp
@@ -0,0 +1,135 @@
+set test "return_no_val"
+set testpath "$srcdir/$subdir"
+
+if {! [installtest_p]} { untested "$test"; return }
+
+# --- TEST 1 ---
+
+set subtest1 "TEST 1: return no value in void type func"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest1 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_1.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f\nexit\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set stderr_pat "\\AWARNING: statement will never be reached: identifier 'println' at \[^\\n\]*?\\.stp:4:5\\n source: println\\(\"leave f\"\\);\\n \\^\\n\\Z"
+ regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+ if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 2 ---
+
+set subtest2 "TEST 2: return nothing in long type func (inferred by another return stmt)"
+set test_name "$test: $subtest2"
+
+set cmd "stap '$srcdir/$subdir/${test}_2.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set exp_out ""
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+if {$failed} {
+ pass "${test_name}: exit code should be non-zero"
+} else {
+ fail "${test_name}: exit code should be non-zero but is zero"
+}
+
+set stderr_pat "\\Asemantic error: type mismatch \\(unknown\\): keyword at \[^\\n\]*?\\.stp:3:9\\n source: return;\\n \\^\\n\\nsemantic error: type was first inferred here \\(long\\): number '1' at :5:12\\n source: return 1;\\n \\^\\n"
+regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+} else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+}
+
+# --- TEST 3 ---
+
+set subtest3 "TEST 3: return nothing in long type func (inferred by caller)"
+set test_name "$test: $subtest3"
+
+set cmd "stap '$srcdir/$subdir/${test}_3.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set exp_out ""
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+if {$failed} {
+ pass "${test_name}: exit code should be non-zero"
+} else {
+ fail "${test_name}: exit code should be non-zero but is zero"
+}
+
+set stderr_pat "\\Asemantic error: type mismatch \\(unknown\\): keyword at \[^\\n\]*?\\.stp:3:9\\n source: return;\\n \\^\\n\\nsemantic error: type was first inferred here \\(long\\): identifier 'f' at :8:17\\n source: println\\(1 \\+ f\\(3\\)\\)\\n \\^\\n\\n"
+regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+} else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+}
diff --git a/testsuite/systemtap.base/return_no_val_1.stp b/testsuite/systemtap.base/return_no_val_1.stp
new file mode 100644
index 000000000..34e6b5525
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_1.stp
@@ -0,0 +1,11 @@
+function f() {
+ println("enter f");
+ return;
+ println("leave f");
+}
+
+probe begin {
+ f();
+ printf("exit\n");
+ exit();
+}
diff --git a/testsuite/systemtap.base/return_no_val_2.stp b/testsuite/systemtap.base/return_no_val_2.stp
new file mode 100644
index 000000000..98a15f85f
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_2.stp
@@ -0,0 +1,11 @@
+function f(a) {
+ if (a > 0) {
+ return;
+ }
+ return 1;
+}
+
+probe begin {
+ println(f(3))
+ exit();
+}
diff --git a/testsuite/systemtap.base/return_no_val_3.stp b/testsuite/systemtap.base/return_no_val_3.stp
new file mode 100644
index 000000000..0d008f249
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_3.stp
@@ -0,0 +1,10 @@
+function f(a) {
+ if (a > 0) {
+ return;
+ }
+}
+
+probe begin {
+ println(1 + f(3))
+ exit();
+}
diff --git a/translate.cxx b/translate.cxx
index c7e7aa53f..8c78df8a1 100644
--- a/translate.cxx
+++ b/translate.cxx
@@ -4405,11 +4405,18 @@ c_unparser::visit_return_statement (return_statement* s)
if (current_function == 0)
throw SEMANTIC_ERROR (_("cannot 'return' from probe"), s->tok);
- if (s->value->type != current_function->type)
+ if (s->value)
+ {
+ if (s->value->type != current_function->type)
+ throw SEMANTIC_ERROR (_("return type mismatch"), current_function->tok,
+ s->tok);
+
+ c_assign ("l->__retvalue", s->value, "return value");
+ }
+ else if (current_function->type != pe_unknown)
throw SEMANTIC_ERROR (_("return type mismatch"), current_function->tok,
s->tok);
- c_assign ("l->__retvalue", s->value, "return value");
record_actions(1, s->tok, true);
o->newline() << "goto out;";
}
--
2.11.0.295.gd7dffce
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2] Add support for return statements without values
2018-08-25 1:09 [PATCH] Add support for return statements without values Yichun Zhang (agentzh)
@ 2018-08-28 20:53 ` Yichun Zhang (agentzh)
2018-08-28 22:25 ` [PATCH v3] " Yichun Zhang (agentzh)
0 siblings, 1 reply; 5+ messages in thread
From: Yichun Zhang (agentzh) @ 2018-08-28 20:53 UTC (permalink / raw)
To: systemtap; +Cc: Yichun Zhang (agentzh)
For user-defined stap functions which do not return any values, it can
be convenient to allow *bare* return statements to take shortcuts in
the control flow of the function body.
Added tests to cover various cases like use of plain `return` in a
function actually returning some values. Both the "kernel" and "dyninst"
runtimes are covered.
A fix for the bpf translator is also included.
---
NEWS | 4 +
bpf-translate.cxx | 3 +-
elaborate.cxx | 8 ++
parse.cxx | 7 +-
staptree.cxx | 8 +-
testsuite/systemtap.base/return_no_val.exp | 182 +++++++++++++++++++++++++++
testsuite/systemtap.base/return_no_val_1.stp | 11 ++
testsuite/systemtap.base/return_no_val_2.stp | 11 ++
testsuite/systemtap.base/return_no_val_3.stp | 11 ++
testsuite/systemtap.base/return_no_val_4.stp | 10 ++
translate.cxx | 11 +-
11 files changed, 260 insertions(+), 6 deletions(-)
create mode 100644 testsuite/systemtap.base/return_no_val.exp
create mode 100644 testsuite/systemtap.base/return_no_val_1.stp
create mode 100644 testsuite/systemtap.base/return_no_val_2.stp
create mode 100644 testsuite/systemtap.base/return_no_val_3.stp
create mode 100644 testsuite/systemtap.base/return_no_val_4.stp
diff --git a/NEWS b/NEWS
index ab20986bf..1046cbccf 100644
--- a/NEWS
+++ b/NEWS
@@ -34,6 +34,10 @@
'+=', just like the C language. The original operator precedence can
be restored by the '--compatible 3.3' option.
+- The script language now supports use of bare 'return' statements
+ (without any return values) inside functions which do not return any
+ values.
+
* What's new in version 3.3, 2018-06-08
- A new "stap --example FOO.stp" mode searches the example scripts
diff --git a/bpf-translate.cxx b/bpf-translate.cxx
index 95c68ce8d..26ceb9fcf 100644
--- a/bpf-translate.cxx
+++ b/bpf-translate.cxx
@@ -915,7 +915,8 @@ bpf_unparser::visit_return_statement (return_statement* s)
if (func_return.empty ())
throw SEMANTIC_ERROR (_("cannot 'return' outside function"), s->tok);
assert (!func_return_val.empty ());
- emit_mov (func_return_val.back (), emit_expr (s->value));
+ if (s->value)
+ emit_mov (func_return_val.back (), emit_expr (s->value));
emit_jmp (func_return.back ());
}
diff --git a/elaborate.cxx b/elaborate.cxx
index c55818f02..cd03df408 100644
--- a/elaborate.cxx
+++ b/elaborate.cxx
@@ -6892,6 +6892,14 @@ typeresolution_info::visit_return_statement (return_statement* e)
exp_type& e_type = current_function->type;
t = current_function->type;
+
+ if (!e->value)
+ {
+ if (e_type != pe_unknown)
+ mismatch (e->tok, pe_unknown, current_function);
+ return;
+ }
+
e->value->visit (this);
if (e_type != pe_unknown && e->value->type != pe_unknown
diff --git a/parse.cxx b/parse.cxx
index 7904e0dff..a7b755f35 100644
--- a/parse.cxx
+++ b/parse.cxx
@@ -2868,7 +2868,12 @@ parser::parse_return_statement ()
throw PARSE_ERROR (_("found 'return' not in function context"));
return_statement* s = new return_statement;
s->tok = t;
- s->value = parse_expression ();
+
+ t = peek ();
+ if (t->type == tok_operator && (t->content == ";" || t->content == "}"))
+ s->value = NULL; // no return value
+ else
+ s->value = parse_expression ();
return s;
}
diff --git a/staptree.cxx b/staptree.cxx
index d1193e336..25cc9fe7c 100644
--- a/staptree.cxx
+++ b/staptree.cxx
@@ -1313,7 +1313,10 @@ void expr_statement::print (ostream& o) const
void return_statement::print (ostream& o) const
{
- o << "return " << *value;
+ if (value)
+ o << "return " << *value;
+ else
+ o << "return";
}
@@ -1964,7 +1967,8 @@ traversing_visitor::visit_foreach_loop (foreach_loop* s)
void
traversing_visitor::visit_return_statement (return_statement* s)
{
- s->value->visit (this);
+ if (s->value)
+ s->value->visit (this);
}
void
diff --git a/testsuite/systemtap.base/return_no_val.exp b/testsuite/systemtap.base/return_no_val.exp
new file mode 100644
index 000000000..7b1d12198
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val.exp
@@ -0,0 +1,182 @@
+set test "return_no_val"
+set testpath "$srcdir/$subdir"
+
+if {! [installtest_p]} { untested "$test"; return }
+
+# --- TEST 1 ---
+
+set subtest1 "TEST 1: return no value in void type func (in the middle)"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest1 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_1.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f\nexit\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set stderr_pat "\\AWARNING: statement will never be reached: identifier 'println' at \[^\\n\]*?\\.stp:4:5\\n source: println\\(\"leave f\"\\);\\n \\^\\n\\Z"
+ regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+ if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 2 ---
+
+set subtest2 "TEST 2: return no value in void type func (at the end, no-op)"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest2 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_2.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f\nleave f\nexit\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set exp_stderr ""
+ regsub -all -- {\n} $exp_stderr {\n} escaped_exp_stderr
+ if {$stderr eq $exp_stderr} {
+ pass "${test_name}: stderr matches \"$escaped_exp_stderr\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_exp_stderr\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 3 ---
+
+set subtest3 "TEST 3: return nothing in long type func (inferred by another return stmt)"
+set test_name "$test: $subtest3"
+
+set cmd "stap '$srcdir/$subdir/${test}_3.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set exp_out ""
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+if {$failed} {
+ pass "${test_name}: exit code should be non-zero"
+} else {
+ fail "${test_name}: exit code should be non-zero but is zero"
+}
+
+set stderr_pat "\\Asemantic error: type mismatch \\(unknown\\): keyword at \[^\\n\]*?\\.stp:3:9\\n source: return;\\n \\^\\n\\nsemantic error: type was first inferred here \\(long\\): number '1' at :5:12\\n source: return 1;\\n \\^\\n"
+regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+} else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+}
+
+# --- TEST 4 ---
+
+set subtest4 "TEST 4: return nothing in long type func (inferred by caller)"
+set test_name "$test: $subtest4"
+
+set cmd "stap '$srcdir/$subdir/${test}_4.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set exp_out ""
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+if {$failed} {
+ pass "${test_name}: exit code should be non-zero"
+} else {
+ fail "${test_name}: exit code should be non-zero but is zero"
+}
+
+set stderr_pat "\\Asemantic error: type mismatch \\(unknown\\): keyword at \[^\\n\]*?\\.stp:3:9\\n source: return;\\n \\^\\n\\nsemantic error: type was first inferred here \\(long\\): identifier 'f' at :8:17\\n source: println\\(1 \\+ f\\(3\\)\\)\\n \\^\\n\\n"
+regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+} else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+}
diff --git a/testsuite/systemtap.base/return_no_val_1.stp b/testsuite/systemtap.base/return_no_val_1.stp
new file mode 100644
index 000000000..34e6b5525
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_1.stp
@@ -0,0 +1,11 @@
+function f() {
+ println("enter f");
+ return;
+ println("leave f");
+}
+
+probe begin {
+ f();
+ printf("exit\n");
+ exit();
+}
diff --git a/testsuite/systemtap.base/return_no_val_2.stp b/testsuite/systemtap.base/return_no_val_2.stp
new file mode 100644
index 000000000..71868e569
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_2.stp
@@ -0,0 +1,11 @@
+function f() {
+ println("enter f");
+ println("leave f");
+ return;
+}
+
+probe begin {
+ f();
+ printf("exit\n");
+ exit();
+}
diff --git a/testsuite/systemtap.base/return_no_val_3.stp b/testsuite/systemtap.base/return_no_val_3.stp
new file mode 100644
index 000000000..98a15f85f
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_3.stp
@@ -0,0 +1,11 @@
+function f(a) {
+ if (a > 0) {
+ return;
+ }
+ return 1;
+}
+
+probe begin {
+ println(f(3))
+ exit();
+}
diff --git a/testsuite/systemtap.base/return_no_val_4.stp b/testsuite/systemtap.base/return_no_val_4.stp
new file mode 100644
index 000000000..0d008f249
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_4.stp
@@ -0,0 +1,10 @@
+function f(a) {
+ if (a > 0) {
+ return;
+ }
+}
+
+probe begin {
+ println(1 + f(3))
+ exit();
+}
diff --git a/translate.cxx b/translate.cxx
index c7e7aa53f..8c78df8a1 100644
--- a/translate.cxx
+++ b/translate.cxx
@@ -4405,11 +4405,18 @@ c_unparser::visit_return_statement (return_statement* s)
if (current_function == 0)
throw SEMANTIC_ERROR (_("cannot 'return' from probe"), s->tok);
- if (s->value->type != current_function->type)
+ if (s->value)
+ {
+ if (s->value->type != current_function->type)
+ throw SEMANTIC_ERROR (_("return type mismatch"), current_function->tok,
+ s->tok);
+
+ c_assign ("l->__retvalue", s->value, "return value");
+ }
+ else if (current_function->type != pe_unknown)
throw SEMANTIC_ERROR (_("return type mismatch"), current_function->tok,
s->tok);
- c_assign ("l->__retvalue", s->value, "return value");
record_actions(1, s->tok, true);
o->newline() << "goto out;";
}
--
2.11.0.295.gd7dffce
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3] Add support for return statements without values
2018-08-28 20:53 ` [PATCH v2] " Yichun Zhang (agentzh)
@ 2018-08-28 22:25 ` Yichun Zhang (agentzh)
2018-08-31 21:53 ` [PATCH v4] " Yichun Zhang (agentzh)
0 siblings, 1 reply; 5+ messages in thread
From: Yichun Zhang (agentzh) @ 2018-08-28 22:25 UTC (permalink / raw)
To: systemtap; +Cc: Yichun Zhang (agentzh)
For user-defined stap functions which do not return any values, it can
be convenient to allow *bare* return statements to take shortcuts in
the control flow of the function body.
The parser treats a following semicolon (';'), a closing curly bracket
('}'), or a statement prefix keyword like 'if', 'while', and 'delete',
as a terminator for such bare return statements.
Added tests to cover various cases like use of plain 'return' in a
function actually returning some values. Also added tests to make sure
the pretty-printer adds a trailing semicolon for such bare return
stateuments for various arrangements. Both the "kernel" and "dyninst"
runtimes are covered.
A fix for the bpf translator is also included.
Updated NEWS to document this new feature.
---
NEWS | 8 +
bpf-translate.cxx | 3 +-
elaborate.cxx | 8 +
parse.cxx | 17 +-
staptree.cxx | 8 +-
testsuite/systemtap.base/return_no_val.exp | 874 ++++++++++++++++++++++++++
testsuite/systemtap.base/return_no_val_1.stp | 10 +
testsuite/systemtap.base/return_no_val_10.stp | 10 +
testsuite/systemtap.base/return_no_val_11.stp | 12 +
testsuite/systemtap.base/return_no_val_12.stp | 14 +
testsuite/systemtap.base/return_no_val_13.stp | 12 +
testsuite/systemtap.base/return_no_val_14.stp | 12 +
testsuite/systemtap.base/return_no_val_15.stp | 7 +
testsuite/systemtap.base/return_no_val_16.stp | 10 +
testsuite/systemtap.base/return_no_val_17.stp | 10 +
testsuite/systemtap.base/return_no_val_18.stp | 10 +
testsuite/systemtap.base/return_no_val_19.stp | 9 +
testsuite/systemtap.base/return_no_val_2.stp | 10 +
testsuite/systemtap.base/return_no_val_20.stp | 11 +
testsuite/systemtap.base/return_no_val_3.stp | 10 +
testsuite/systemtap.base/return_no_val_4.stp | 9 +
testsuite/systemtap.base/return_no_val_5.stp | 12 +
testsuite/systemtap.base/return_no_val_6.stp | 13 +
testsuite/systemtap.base/return_no_val_7.stp | 13 +
testsuite/systemtap.base/return_no_val_8.stp | 15 +
testsuite/systemtap.base/return_no_val_9.stp | 10 +
translate.cxx | 11 +-
27 files changed, 1142 insertions(+), 6 deletions(-)
create mode 100644 testsuite/systemtap.base/return_no_val.exp
create mode 100644 testsuite/systemtap.base/return_no_val_1.stp
create mode 100644 testsuite/systemtap.base/return_no_val_10.stp
create mode 100644 testsuite/systemtap.base/return_no_val_11.stp
create mode 100644 testsuite/systemtap.base/return_no_val_12.stp
create mode 100644 testsuite/systemtap.base/return_no_val_13.stp
create mode 100644 testsuite/systemtap.base/return_no_val_14.stp
create mode 100644 testsuite/systemtap.base/return_no_val_15.stp
create mode 100644 testsuite/systemtap.base/return_no_val_16.stp
create mode 100644 testsuite/systemtap.base/return_no_val_17.stp
create mode 100644 testsuite/systemtap.base/return_no_val_18.stp
create mode 100644 testsuite/systemtap.base/return_no_val_19.stp
create mode 100644 testsuite/systemtap.base/return_no_val_2.stp
create mode 100644 testsuite/systemtap.base/return_no_val_20.stp
create mode 100644 testsuite/systemtap.base/return_no_val_3.stp
create mode 100644 testsuite/systemtap.base/return_no_val_4.stp
create mode 100644 testsuite/systemtap.base/return_no_val_5.stp
create mode 100644 testsuite/systemtap.base/return_no_val_6.stp
create mode 100644 testsuite/systemtap.base/return_no_val_7.stp
create mode 100644 testsuite/systemtap.base/return_no_val_8.stp
create mode 100644 testsuite/systemtap.base/return_no_val_9.stp
diff --git a/NEWS b/NEWS
index ab20986bf..29c2e737d 100644
--- a/NEWS
+++ b/NEWS
@@ -34,6 +34,14 @@
'+=', just like the C language. The original operator precedence can
be restored by the '--compatible 3.3' option.
+- The script language now supports the use of bare 'return' statements
+ (without any return values) inside functions which do not return any
+ values. A trailing semincolon is recommended for such return
+ statements to avoid any potential ambiguity. The parser treats a
+ following semicolon (';'), a closing curly bracket ('}'), or a
+ statement prefix keyword like 'if', 'while', and 'delete', as a
+ terminator for such bare return statements.
+
* What's new in version 3.3, 2018-06-08
- A new "stap --example FOO.stp" mode searches the example scripts
diff --git a/bpf-translate.cxx b/bpf-translate.cxx
index 95c68ce8d..26ceb9fcf 100644
--- a/bpf-translate.cxx
+++ b/bpf-translate.cxx
@@ -915,7 +915,8 @@ bpf_unparser::visit_return_statement (return_statement* s)
if (func_return.empty ())
throw SEMANTIC_ERROR (_("cannot 'return' outside function"), s->tok);
assert (!func_return_val.empty ());
- emit_mov (func_return_val.back (), emit_expr (s->value));
+ if (s->value)
+ emit_mov (func_return_val.back (), emit_expr (s->value));
emit_jmp (func_return.back ());
}
diff --git a/elaborate.cxx b/elaborate.cxx
index c55818f02..cd03df408 100644
--- a/elaborate.cxx
+++ b/elaborate.cxx
@@ -6892,6 +6892,14 @@ typeresolution_info::visit_return_statement (return_statement* e)
exp_type& e_type = current_function->type;
t = current_function->type;
+
+ if (!e->value)
+ {
+ if (e_type != pe_unknown)
+ mismatch (e->tok, pe_unknown, current_function);
+ return;
+ }
+
e->value->visit (this);
if (e_type != pe_unknown && e->value->type != pe_unknown
diff --git a/parse.cxx b/parse.cxx
index 7904e0dff..5656d322e 100644
--- a/parse.cxx
+++ b/parse.cxx
@@ -2868,7 +2868,22 @@ parser::parse_return_statement ()
throw PARSE_ERROR (_("found 'return' not in function context"));
return_statement* s = new return_statement;
s->tok = t;
- s->value = parse_expression ();
+
+ t = peek ();
+ if ((t->type == tok_operator && (t->content == ";" || t->content == "}"))
+ || (t->type == tok_keyword && (t->content == "delete"
+ || t->content == "if"
+ || t->content == "for"
+ || t->content == "foreach"
+ || t->content == "return"
+ || t->content == "next"
+ || t->content == "try"
+ || t->content == "while"
+ || t->content == "break"
+ || t->content == "continue")))
+ s->value = NULL; // no return value
+ else
+ s->value = parse_expression ();
return s;
}
diff --git a/staptree.cxx b/staptree.cxx
index d1193e336..25cc9fe7c 100644
--- a/staptree.cxx
+++ b/staptree.cxx
@@ -1313,7 +1313,10 @@ void expr_statement::print (ostream& o) const
void return_statement::print (ostream& o) const
{
- o << "return " << *value;
+ if (value)
+ o << "return " << *value;
+ else
+ o << "return";
}
@@ -1964,7 +1967,8 @@ traversing_visitor::visit_foreach_loop (foreach_loop* s)
void
traversing_visitor::visit_return_statement (return_statement* s)
{
- s->value->visit (this);
+ if (s->value)
+ s->value->visit (this);
}
void
diff --git a/testsuite/systemtap.base/return_no_val.exp b/testsuite/systemtap.base/return_no_val.exp
new file mode 100644
index 000000000..c42289bee
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val.exp
@@ -0,0 +1,874 @@
+set test "return_no_val"
+set testpath "$srcdir/$subdir"
+
+if {! [installtest_p]} { untested "$test"; return }
+
+# --- TEST 1 ---
+
+set subtest1 "TEST 1: return no value in void type func (in the middle)"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest1 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_1.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f\nexit\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set stderr_pat "\\AWARNING: statement will never be reached: identifier 'println' at \[^\\n\]*?\\.stp:4:5\\n source: println\\(\"leave f\"\\);\\n \\^\\n\\Z"
+ regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+ if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 2 ---
+
+set subtest2 "TEST 2: return no value in void type func (at the end, no-op)"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest2 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_2.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f\nleave f\nexit\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set exp_stderr ""
+ regsub -all -- {\n} $exp_stderr {\n} escaped_exp_stderr
+ if {$stderr eq $exp_stderr} {
+ pass "${test_name}: stderr matches \"$escaped_exp_stderr\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_exp_stderr\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 3 ---
+
+set subtest3 "TEST 3: return nothing in long type func (inferred by another return stmt)"
+set test_name "$test: $subtest3"
+
+set cmd "stap '$srcdir/$subdir/${test}_3.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set exp_out ""
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+if {$failed} {
+ pass "${test_name}: exit code should be non-zero"
+} else {
+ fail "${test_name}: exit code should be non-zero but is zero"
+}
+
+set stderr_pat "\\Asemantic error: type mismatch \\(unknown\\): keyword at \[^\\n\]*?\\.stp:3:9\\n source: return;\\n \\^\\n\\nsemantic error: type was first inferred here \\(long\\): number '1' at :5:12\\n source: return 1;\\n \\^\\n"
+regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+} else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+}
+
+# --- TEST 4 ---
+
+set subtest4 "TEST 4: return nothing in long type func (inferred by caller)"
+set test_name "$test: $subtest4"
+
+set cmd "stap '$srcdir/$subdir/${test}_4.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set exp_out ""
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+if {$failed} {
+ pass "${test_name}: exit code should be non-zero"
+} else {
+ fail "${test_name}: exit code should be non-zero but is zero"
+}
+
+set stderr_pat "\\Asemantic error: type mismatch \\(unknown\\): keyword at \[^\\n\]*?\\.stp:3:9\\n source: return;\\n \\^\\n\\nsemantic error: type was first inferred here \\(long\\): identifier 'f' at :8:17\\n source: println\\(1 \\+ f\\(3\\)\\)\\n \\^\\n\\n"
+regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+} else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+}
+
+# --- TEST 5 ---
+
+set subtest5 "TEST 5: 'return' right before 'if'"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest5 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_5.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f\nexit\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set stderr_pat "\\AWARNING: statement will never be reached: identifier 'println' at \[^\\n\]*?\\.stp:5:9\\n source: println\\(\"leave f\"\\);\\n \\^\\n\\Z"
+ regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+ if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 6 ---
+
+set subtest6 "TEST 6: 'return' right before 'for'"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest6 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_6.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f\nexit\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set stderr_pat "\\AWARNING: statement will never be reached: keyword at \[^\\n\]*?\\.stp:4:5\\n source: for \\(;;\\) \\\{\\n \\^\\n\\Z"
+ regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+ if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 7 ---
+
+set subtest7 "TEST 7: 'return' right before 'while'"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest7 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_7.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f\nexit\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set stderr_pat "\\AWARNING: statement will never be reached: keyword at \[^\\n\]*?\\.stp:4:5\\n source: while \\(1\\) \\\{\\n \\^\\n\\Z"
+ regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+ if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 8 ---
+
+set subtest8 "TEST 8: 'return' right before 'foreach'"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest8 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_8.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f\na\[1\] = 3\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set stderr_pat "\\AWARNING: statement will never be reached: keyword at \[^\\n\]*?\\.stp:6:5\\n source: foreach \\(k in a\\) \\\{\\n \\^\\n\\Z"
+ regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+ if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 9 ---
+
+set subtest9 "TEST 9: 'return' right before 'return'"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest9 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_9.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f\nexit\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set stderr_pat "\\AWARNING: statement will never be reached: keyword at \[^\\n\]*?\\.stp:4:5\\n source: return\\n \\^\\n\\Z"
+ regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+ if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 10 ---
+
+set subtest10 "TEST 10: 'return' right before 'next'"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest10 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_10.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f\nexit\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set stderr_pat "\\AWARNING: statement will never be reached: keyword at \[^\\n\]*?\\.stp:4:5\\n source: next\\n \\^\\n\\Z"
+ regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+ if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 11 ---
+
+set subtest11 "TEST 11: 'return' right before 'delete'"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest11 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_11.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f\na\[1\] = 3\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set stderr_pat "\\AWARNING: statement will never be reached: keyword at \[^\\n\]*?\\.stp:6:5\\n source: delete a\\n \\^\\n\\Z"
+ regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+ if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 12 ---
+
+set subtest12 "TEST 12: 'return' right before 'try'"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest12 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_12.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f\na\[1\] = 3\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set stderr_pat "\\AWARNING: statement will never be reached: keyword at \[^\\n\]*?\\.stp:6:5\\n source: try \\\{\\n \\^\\n\\Z"
+ regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+ if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 13 ---
+
+set subtest13 "TEST 13: 'return' right before 'break'"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest13 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_13.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f\nexit\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set stderr_pat "\\AWARNING: statement will never be reached: keyword at \[^\\n\]*?\\.stp:5:9\\n source: break\\n \\^\\n\\Z"
+ regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+ if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 14 ---
+
+set subtest14 "TEST 14: 'return' right before 'continue'"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest14 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_14.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f\nexit\n"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set stderr_pat "\\AWARNING: statement will never be reached: keyword at \[^\\n\]*?\\.stp:5:9\\n source: continue\\n \\^\\n\\Z"
+ regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+ if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 15 ---
+
+set subtest15 "TEST 15: pretty-printer adds a semicolon (two return stmts in a row)"
+set test_name "$test: $subtest15"
+
+set cmd "stap -p1 '$srcdir/$subdir/${test}_15.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set out_pat "^return;\\nreturn;\\n"
+regsub -all -- {\n} $out_pat {\n} escaped_out_pat
+if {[regexp -linestop -lineanchor -- $out_pat $out]} {
+ pass "${test_name}: stdout matches \"$escaped_out_pat\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_out_pat\": got \"$out\""
+}
+
+if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+} else {
+ pass "${test_name}: exit code is zero"
+}
+if {$stderr ne ""} {
+ send_log "stderr:\n$stderr"
+}
+
+# --- TEST 16 ---
+
+set subtest16 "TEST 16: pretty-printer adds a semicolon (right before if)"
+set test_name "$test: $subtest16"
+
+set cmd "stap -p1 '$srcdir/$subdir/${test}_16.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set out_pat "^\\s*return;\\n"
+regsub -all -- {\n} $out_pat {\n} escaped_out_pat
+if {[regexp -linestop -lineanchor -- $out_pat $out]} {
+ pass "${test_name}: stdout matches \"$escaped_out_pat\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_out_pat\": got \"$out\""
+}
+
+if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+} else {
+ pass "${test_name}: exit code is zero"
+}
+if {$stderr ne ""} {
+ send_log "stderr:\n$stderr"
+}
+
+# --- TEST 17 ---
+
+set subtest17 "TEST 17: pretty-printer adds a semicolon (under if)"
+set test_name "$test: $subtest17"
+
+set cmd "stap -p1 '$srcdir/$subdir/${test}_17.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set out_pat "^if \\(1\\) return\n;\n"
+regsub -all -- {\n} $out_pat {\n} escaped_out_pat
+if {[regexp -linestop -lineanchor -- $out_pat $out]} {
+ pass "${test_name}: stdout matches \"$escaped_out_pat\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_out_pat\": got \"$out\""
+}
+
+if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+} else {
+ pass "${test_name}: exit code is zero"
+}
+if {$stderr ne ""} {
+ send_log "stderr:\n$stderr"
+}
+
+# --- TEST 18 ---
+
+set subtest18 "TEST 18: pretty-printer adds a semicolon (under for)"
+set test_name "$test: $subtest18"
+
+set cmd "stap -p1 '$srcdir/$subdir/${test}_18.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set out_pat "^for \\(; 1; \\) return;\n"
+regsub -all -- {\n} $out_pat {\n} escaped_out_pat
+if {[regexp -linestop -lineanchor -- $out_pat $out]} {
+ pass "${test_name}: stdout matches \"$escaped_out_pat\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_out_pat\": got \"$out\""
+}
+
+if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+} else {
+ pass "${test_name}: exit code is zero"
+}
+if {$stderr ne ""} {
+ send_log "stderr:\n$stderr"
+}
+
+# --- TEST 19 ---
+
+set subtest19 "TEST 19: pretty-printer adds a semicolon (under while)"
+set test_name "$test: $subtest19"
+
+set cmd "stap -p1 '$srcdir/$subdir/${test}_19.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set out_pat "^for \\(; 1; \\) return;\n"
+regsub -all -- {\n} $out_pat {\n} escaped_out_pat
+if {[regexp -linestop -lineanchor -- $out_pat $out]} {
+ pass "${test_name}: stdout matches \"$escaped_out_pat\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_out_pat\": got \"$out\""
+}
+
+if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+} else {
+ pass "${test_name}: exit code is zero"
+}
+if {$stderr ne ""} {
+ send_log "stderr:\n$stderr"
+}
+
+# --- TEST 20 ---
+
+set subtest20 "TEST 20: pretty-printer adds a semicolon (under foreach)"
+set test_name "$test: $subtest20"
+
+set cmd "stap -p1 '$srcdir/$subdir/${test}_20.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set out_pat "^foreach \\(\\\[k\\\] in a\\) return;\n"
+regsub -all -- {\n} $out_pat {\n} escaped_out_pat
+if {[regexp -linestop -lineanchor -- $out_pat $out]} {
+ pass "${test_name}: stdout matches \"$escaped_out_pat\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_out_pat\": got \"$out\""
+}
+
+if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+} else {
+ pass "${test_name}: exit code is zero"
+}
+if {$stderr ne ""} {
+ send_log "stderr:\n$stderr"
+}
diff --git a/testsuite/systemtap.base/return_no_val_1.stp b/testsuite/systemtap.base/return_no_val_1.stp
new file mode 100644
index 000000000..48c54fc73
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_1.stp
@@ -0,0 +1,10 @@
+function f() {
+ println("enter f");
+ return;
+ println("leave f");
+}
+
+probe oneshot {
+ f();
+ printf("exit\n");
+}
diff --git a/testsuite/systemtap.base/return_no_val_10.stp b/testsuite/systemtap.base/return_no_val_10.stp
new file mode 100644
index 000000000..b30ddc126
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_10.stp
@@ -0,0 +1,10 @@
+function f() {
+ println("enter f");
+ return
+ next
+}
+
+probe oneshot {
+ f();
+ printf("exit\n");
+}
diff --git a/testsuite/systemtap.base/return_no_val_11.stp b/testsuite/systemtap.base/return_no_val_11.stp
new file mode 100644
index 000000000..0de8a2c81
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_11.stp
@@ -0,0 +1,12 @@
+global a
+function f() {
+ println("enter f");
+ a[1] = 3
+ return
+ delete a
+}
+
+probe oneshot {
+ f();
+ printf("a[1] = %d\n", a[1]);
+}
diff --git a/testsuite/systemtap.base/return_no_val_12.stp b/testsuite/systemtap.base/return_no_val_12.stp
new file mode 100644
index 000000000..2f28e5df4
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_12.stp
@@ -0,0 +1,14 @@
+global a
+function f() {
+ println("enter f");
+ a[1] = 3
+ return
+ try {
+ delete a
+ } catch {}
+}
+
+probe oneshot {
+ f();
+ printf("a[1] = %d\n", a[1]);
+}
diff --git a/testsuite/systemtap.base/return_no_val_13.stp b/testsuite/systemtap.base/return_no_val_13.stp
new file mode 100644
index 000000000..cd64295c0
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_13.stp
@@ -0,0 +1,12 @@
+function f() {
+ while (1) {
+ println("enter f");
+ return
+ break
+ }
+}
+
+probe oneshot {
+ f();
+ printf("exit\n");
+}
diff --git a/testsuite/systemtap.base/return_no_val_14.stp b/testsuite/systemtap.base/return_no_val_14.stp
new file mode 100644
index 000000000..b4a5d942a
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_14.stp
@@ -0,0 +1,12 @@
+function f() {
+ while (1) {
+ println("enter f");
+ return
+ continue
+ }
+}
+
+probe oneshot {
+ f();
+ printf("exit\n");
+}
diff --git a/testsuite/systemtap.base/return_no_val_15.stp b/testsuite/systemtap.base/return_no_val_15.stp
new file mode 100644
index 000000000..60fb3b622
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_15.stp
@@ -0,0 +1,7 @@
+function f() {
+ return return
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_16.stp b/testsuite/systemtap.base/return_no_val_16.stp
new file mode 100644
index 000000000..3bcfc2d94
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_16.stp
@@ -0,0 +1,10 @@
+function f() {
+ return
+ if (1) {
+ println("leave f");
+ }
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_17.stp b/testsuite/systemtap.base/return_no_val_17.stp
new file mode 100644
index 000000000..4c2197676
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_17.stp
@@ -0,0 +1,10 @@
+function f() {
+ if (1)
+ return
+ while (1)
+ next
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_18.stp b/testsuite/systemtap.base/return_no_val_18.stp
new file mode 100644
index 000000000..cdd52826b
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_18.stp
@@ -0,0 +1,10 @@
+function f() {
+ for (;;)
+ return
+ while (1)
+ next
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_19.stp b/testsuite/systemtap.base/return_no_val_19.stp
new file mode 100644
index 000000000..318deeeae
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_19.stp
@@ -0,0 +1,9 @@
+function f() {
+ while (1)
+ return
+ next
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_2.stp b/testsuite/systemtap.base/return_no_val_2.stp
new file mode 100644
index 000000000..adda25b53
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_2.stp
@@ -0,0 +1,10 @@
+function f() {
+ println("enter f");
+ println("leave f");
+ return;
+}
+
+probe oneshot {
+ f();
+ printf("exit\n");
+}
diff --git a/testsuite/systemtap.base/return_no_val_20.stp b/testsuite/systemtap.base/return_no_val_20.stp
new file mode 100644
index 000000000..77f824a3b
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_20.stp
@@ -0,0 +1,11 @@
+global a
+function f() {
+ a[1] = 3
+ foreach (k in a)
+ return
+ next
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_3.stp b/testsuite/systemtap.base/return_no_val_3.stp
new file mode 100644
index 000000000..c3f4c910c
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_3.stp
@@ -0,0 +1,10 @@
+function f(a) {
+ if (a > 0) {
+ return;
+ }
+ return 1;
+}
+
+probe oneshot {
+ println(f(3))
+}
diff --git a/testsuite/systemtap.base/return_no_val_4.stp b/testsuite/systemtap.base/return_no_val_4.stp
new file mode 100644
index 000000000..3d93abef7
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_4.stp
@@ -0,0 +1,9 @@
+function f(a) {
+ if (a > 0) {
+ return;
+ }
+}
+
+probe oneshot {
+ println(1 + f(3))
+}
diff --git a/testsuite/systemtap.base/return_no_val_5.stp b/testsuite/systemtap.base/return_no_val_5.stp
new file mode 100644
index 000000000..2f6d42bd0
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_5.stp
@@ -0,0 +1,12 @@
+function f() {
+ println("enter f");
+ return
+ if (1) {
+ println("leave f");
+ }
+}
+
+probe oneshot {
+ f();
+ printf("exit\n");
+}
diff --git a/testsuite/systemtap.base/return_no_val_6.stp b/testsuite/systemtap.base/return_no_val_6.stp
new file mode 100644
index 000000000..1f6529ea8
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_6.stp
@@ -0,0 +1,13 @@
+function f() {
+ println("enter f");
+ return
+ for (;;) {
+ println("leave f");
+ return
+ }
+}
+
+probe oneshot {
+ f();
+ printf("exit\n");
+}
diff --git a/testsuite/systemtap.base/return_no_val_7.stp b/testsuite/systemtap.base/return_no_val_7.stp
new file mode 100644
index 000000000..4d243bf56
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_7.stp
@@ -0,0 +1,13 @@
+function f() {
+ println("enter f");
+ return
+ while (1) {
+ println("leave f");
+ return
+ }
+}
+
+probe oneshot {
+ f();
+ printf("exit\n");
+}
diff --git a/testsuite/systemtap.base/return_no_val_8.stp b/testsuite/systemtap.base/return_no_val_8.stp
new file mode 100644
index 000000000..6291e0c63
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_8.stp
@@ -0,0 +1,15 @@
+global a
+function f() {
+ println("enter f");
+ a[1] = 3
+ return
+ foreach (k in a) {
+ println("k: ", k);
+ return
+ }
+}
+
+probe oneshot {
+ f();
+ printf("a[1] = %d\n", a[1]);
+}
diff --git a/testsuite/systemtap.base/return_no_val_9.stp b/testsuite/systemtap.base/return_no_val_9.stp
new file mode 100644
index 000000000..b1d33d5c6
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_9.stp
@@ -0,0 +1,10 @@
+function f() {
+ println("enter f");
+ return
+ return
+}
+
+probe oneshot {
+ f();
+ printf("exit\n");
+}
diff --git a/translate.cxx b/translate.cxx
index c7e7aa53f..8c78df8a1 100644
--- a/translate.cxx
+++ b/translate.cxx
@@ -4405,11 +4405,18 @@ c_unparser::visit_return_statement (return_statement* s)
if (current_function == 0)
throw SEMANTIC_ERROR (_("cannot 'return' from probe"), s->tok);
- if (s->value->type != current_function->type)
+ if (s->value)
+ {
+ if (s->value->type != current_function->type)
+ throw SEMANTIC_ERROR (_("return type mismatch"), current_function->tok,
+ s->tok);
+
+ c_assign ("l->__retvalue", s->value, "return value");
+ }
+ else if (current_function->type != pe_unknown)
throw SEMANTIC_ERROR (_("return type mismatch"), current_function->tok,
s->tok);
- c_assign ("l->__retvalue", s->value, "return value");
record_actions(1, s->tok, true);
o->newline() << "goto out;";
}
--
2.11.0.295.gd7dffce
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v4] Add support for return statements without values
2018-08-28 22:25 ` [PATCH v3] " Yichun Zhang (agentzh)
@ 2018-08-31 21:53 ` Yichun Zhang (agentzh)
2018-08-31 23:47 ` Yichun Zhang
0 siblings, 1 reply; 5+ messages in thread
From: Yichun Zhang (agentzh) @ 2018-08-31 21:53 UTC (permalink / raw)
To: systemtap; +Cc: Yichun Zhang (agentzh)
For user-defined stap functions which do not return any values, it can
be convenient to allow *bare* return statements to take shortcuts in
the control flow of the function body.
The parser treats a following semicolon (';') or a closing curly bracket
('}') as a terminator for such bare return statements.
Added tests to cover various cases like use of plain 'return' in a
function actually returning some values. Also added tests to make sure
the pretty-printer adds a trailing semicolon for such bare return
stateuments for various arrangements. Both the "kernel" and "dyninst"
runtimes are covered.
A fix for the bpf translator is also included.
Updated NEWS to document this new feature.
---
NEWS | 7 +
bpf-translate.cxx | 3 +-
elaborate.cxx | 8 +
parse.cxx | 7 +-
staptree.cxx | 8 +-
testsuite/systemtap.base/return_no_val.exp | 395 ++++++++++++++++++++++++++
testsuite/systemtap.base/return_no_val_1.stp | 10 +
testsuite/systemtap.base/return_no_val_10.stp | 10 +
testsuite/systemtap.base/return_no_val_11.stp | 12 +
testsuite/systemtap.base/return_no_val_12.stp | 14 +
testsuite/systemtap.base/return_no_val_13.stp | 12 +
testsuite/systemtap.base/return_no_val_14.stp | 12 +
testsuite/systemtap.base/return_no_val_15.stp | 7 +
testsuite/systemtap.base/return_no_val_16.stp | 10 +
testsuite/systemtap.base/return_no_val_17.stp | 10 +
testsuite/systemtap.base/return_no_val_18.stp | 10 +
testsuite/systemtap.base/return_no_val_19.stp | 9 +
testsuite/systemtap.base/return_no_val_2.stp | 10 +
testsuite/systemtap.base/return_no_val_20.stp | 11 +
testsuite/systemtap.base/return_no_val_3.stp | 10 +
testsuite/systemtap.base/return_no_val_4.stp | 9 +
testsuite/systemtap.base/return_no_val_5.stp | 7 +
testsuite/systemtap.base/return_no_val_6.stp | 8 +
testsuite/systemtap.base/return_no_val_7.stp | 8 +
testsuite/systemtap.base/return_no_val_8.stp | 8 +
testsuite/systemtap.base/return_no_val_9.stp | 10 +
translate.cxx | 11 +-
27 files changed, 630 insertions(+), 6 deletions(-)
create mode 100644 testsuite/systemtap.base/return_no_val.exp
create mode 100644 testsuite/systemtap.base/return_no_val_1.stp
create mode 100644 testsuite/systemtap.base/return_no_val_10.stp
create mode 100644 testsuite/systemtap.base/return_no_val_11.stp
create mode 100644 testsuite/systemtap.base/return_no_val_12.stp
create mode 100644 testsuite/systemtap.base/return_no_val_13.stp
create mode 100644 testsuite/systemtap.base/return_no_val_14.stp
create mode 100644 testsuite/systemtap.base/return_no_val_15.stp
create mode 100644 testsuite/systemtap.base/return_no_val_16.stp
create mode 100644 testsuite/systemtap.base/return_no_val_17.stp
create mode 100644 testsuite/systemtap.base/return_no_val_18.stp
create mode 100644 testsuite/systemtap.base/return_no_val_19.stp
create mode 100644 testsuite/systemtap.base/return_no_val_2.stp
create mode 100644 testsuite/systemtap.base/return_no_val_20.stp
create mode 100644 testsuite/systemtap.base/return_no_val_3.stp
create mode 100644 testsuite/systemtap.base/return_no_val_4.stp
create mode 100644 testsuite/systemtap.base/return_no_val_5.stp
create mode 100644 testsuite/systemtap.base/return_no_val_6.stp
create mode 100644 testsuite/systemtap.base/return_no_val_7.stp
create mode 100644 testsuite/systemtap.base/return_no_val_8.stp
create mode 100644 testsuite/systemtap.base/return_no_val_9.stp
diff --git a/NEWS b/NEWS
index 273b791fc..fde7c34b0 100644
--- a/NEWS
+++ b/NEWS
@@ -34,6 +34,13 @@
'+=', just like the C language. The original operator precedence can
be restored by the '--compatible 3.3' option.
+- The script language now supports the use of bare 'return' statements
+ (without any return values) inside functions which do not return any
+ values. A trailing semincolon is recommended for such return
+ statements to avoid any potential ambiguity. The parser treats a
+ following semicolon (';') or a closing curly bracket ('}') as a
+ terminator for such bare return statements.
+
* What's new in version 3.3, 2018-06-08
- A new "stap --example FOO.stp" mode searches the example scripts
diff --git a/bpf-translate.cxx b/bpf-translate.cxx
index 95c68ce8d..26ceb9fcf 100644
--- a/bpf-translate.cxx
+++ b/bpf-translate.cxx
@@ -915,7 +915,8 @@ bpf_unparser::visit_return_statement (return_statement* s)
if (func_return.empty ())
throw SEMANTIC_ERROR (_("cannot 'return' outside function"), s->tok);
assert (!func_return_val.empty ());
- emit_mov (func_return_val.back (), emit_expr (s->value));
+ if (s->value)
+ emit_mov (func_return_val.back (), emit_expr (s->value));
emit_jmp (func_return.back ());
}
diff --git a/elaborate.cxx b/elaborate.cxx
index c55818f02..cd03df408 100644
--- a/elaborate.cxx
+++ b/elaborate.cxx
@@ -6892,6 +6892,14 @@ typeresolution_info::visit_return_statement (return_statement* e)
exp_type& e_type = current_function->type;
t = current_function->type;
+
+ if (!e->value)
+ {
+ if (e_type != pe_unknown)
+ mismatch (e->tok, pe_unknown, current_function);
+ return;
+ }
+
e->value->visit (this);
if (e_type != pe_unknown && e->value->type != pe_unknown
diff --git a/parse.cxx b/parse.cxx
index 7904e0dff..a7b755f35 100644
--- a/parse.cxx
+++ b/parse.cxx
@@ -2868,7 +2868,12 @@ parser::parse_return_statement ()
throw PARSE_ERROR (_("found 'return' not in function context"));
return_statement* s = new return_statement;
s->tok = t;
- s->value = parse_expression ();
+
+ t = peek ();
+ if (t->type == tok_operator && (t->content == ";" || t->content == "}"))
+ s->value = NULL; // no return value
+ else
+ s->value = parse_expression ();
return s;
}
diff --git a/staptree.cxx b/staptree.cxx
index d1193e336..25cc9fe7c 100644
--- a/staptree.cxx
+++ b/staptree.cxx
@@ -1313,7 +1313,10 @@ void expr_statement::print (ostream& o) const
void return_statement::print (ostream& o) const
{
- o << "return " << *value;
+ if (value)
+ o << "return " << *value;
+ else
+ o << "return";
}
@@ -1964,7 +1967,8 @@ traversing_visitor::visit_foreach_loop (foreach_loop* s)
void
traversing_visitor::visit_return_statement (return_statement* s)
{
- s->value->visit (this);
+ if (s->value)
+ s->value->visit (this);
}
void
diff --git a/testsuite/systemtap.base/return_no_val.exp b/testsuite/systemtap.base/return_no_val.exp
new file mode 100644
index 000000000..d3533712d
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val.exp
@@ -0,0 +1,395 @@
+set test "return_no_val"
+set testpath "$srcdir/$subdir"
+
+if {! [installtest_p]} { untested "$test"; return }
+
+# --- TEST 1 ---
+
+set subtest1 "TEST 1: return no value in void type func (in the middle)"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest1 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_1.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f
+exit
+"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set stderr_pat "\\AWARNING: statement will never be reached: identifier 'println' at \[^\\n\]*?\\.stp:4:5
+ source: println\\(\"leave f\"\\);
+ \\^
+\\Z"
+ regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+ if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 2 ---
+
+set subtest2 "TEST 2: return no value in void type func (at the end, no-op)"
+foreach runtime [get_runtime_list] {
+ if {$runtime eq ""} {
+ set runtime "kernel"
+ }
+ set test_name "$test: $subtest2 ($runtime)"
+
+ set cmd "stap --runtime=$runtime '$srcdir/$subdir/${test}_2.stp'"
+ send_log "executing: $cmd\n"
+ set pipe [open "| sh -c {$cmd}" r]
+ set out [read $pipe]
+ set failed 0
+ if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+ }
+
+ set exp_out "enter f
+leave f
+exit
+"
+ regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+ if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+ } else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+ }
+
+ set exp_stderr ""
+ regsub -all -- {\n} $exp_stderr {\n} escaped_exp_stderr
+ if {$stderr eq $exp_stderr} {
+ pass "${test_name}: stderr matches \"$escaped_exp_stderr\""
+ } else {
+ fail "${test_name}: stderr fails to match \"$escaped_exp_stderr\": got \"$stderr\""
+ }
+
+ if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+ } else {
+ pass "${test_name}: exit code is zero"
+ }
+}
+
+# --- TEST 3 ---
+
+set subtest3 "TEST 3: return nothing in long type func (inferred by another return stmt)"
+set test_name "$test: $subtest3"
+
+set cmd "stap '$srcdir/$subdir/${test}_3.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set exp_out ""
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+if {$failed} {
+ pass "${test_name}: exit code should be non-zero"
+} else {
+ fail "${test_name}: exit code should be non-zero but is zero"
+}
+
+set stderr_pat "\\Asemantic error: type mismatch \\(unknown\\): keyword at \[^\\n\]*?\\.stp:3:9
+ source: return;
+ \\^
+
+semantic error: type was first inferred here \\(long\\): number '1' at :5:12
+ source: return 1;
+ \\^
+"
+regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+} else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+}
+
+# --- TEST 4 ---
+
+set subtest4 "TEST 4: return nothing in long type func (inferred by caller)"
+set test_name "$test: $subtest4"
+
+set cmd "stap '$srcdir/$subdir/${test}_4.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set exp_out ""
+regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+if {$out eq $exp_out} {
+ pass "${test_name}: stdout matches \"$escaped_exp_out\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+}
+
+if {$failed} {
+ pass "${test_name}: exit code should be non-zero"
+} else {
+ fail "${test_name}: exit code should be non-zero but is zero"
+}
+
+set stderr_pat "\\Asemantic error: type mismatch \\(unknown\\): keyword at \[^\\n\]*?\\.stp:3:9
+ source: return;
+ \\^
+
+semantic error: type was first inferred here \\(long\\): identifier 'f' at :8:17
+ source: println\\(1 \\+ f\\(3\\)\\)
+ \\^
+
+"
+regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+if {[regexp -linestop -- $stderr_pat $stderr]} {
+ pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+} else {
+ fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+}
+
+# --- TEST 5 ---
+
+set subtest5 "TEST 5: pretty-printer adds a semicolon (two return stmts in a row)"
+set test_name "$test: $subtest5"
+
+set cmd "stap -p1 '$srcdir/$subdir/${test}_5.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set out_pat "^return;\\nreturn;\\n"
+regsub -all -- {\n} $out_pat {\n} escaped_out_pat
+if {[regexp -linestop -lineanchor -- $out_pat $out]} {
+ pass "${test_name}: stdout matches \"$escaped_out_pat\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_out_pat\": got \"$out\""
+}
+
+if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+} else {
+ pass "${test_name}: exit code is zero"
+}
+if {$stderr ne ""} {
+ send_log "stderr:\n$stderr"
+}
+
+# --- TEST 6 ---
+
+set subtest6 "TEST 6: pretty-printer adds a semicolon (under if)"
+set test_name "$test: $subtest6"
+
+set cmd "stap -p1 '$srcdir/$subdir/${test}_6.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set out_pat "^if \\(1\\) return
+;
+"
+regsub -all -- {\n} $out_pat {\n} escaped_out_pat
+if {[regexp -linestop -lineanchor -- $out_pat $out]} {
+ pass "${test_name}: stdout matches \"$escaped_out_pat\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_out_pat\": got \"$out\""
+}
+
+if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+} else {
+ pass "${test_name}: exit code is zero"
+}
+if {$stderr ne ""} {
+ send_log "stderr:\n$stderr"
+}
+
+# --- TEST 7 ---
+
+set subtest7 "TEST 7: pretty-printer adds a semicolon (under for)"
+set test_name "$test: $subtest7"
+
+set cmd "stap -p1 '$srcdir/$subdir/${test}_7.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set out_pat "^for \\(; 1; \\) return;
+"
+regsub -all -- {\n} $out_pat {\n} escaped_out_pat
+if {[regexp -linestop -lineanchor -- $out_pat $out]} {
+ pass "${test_name}: stdout matches \"$escaped_out_pat\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_out_pat\": got \"$out\""
+}
+
+if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+} else {
+ pass "${test_name}: exit code is zero"
+}
+if {$stderr ne ""} {
+ send_log "stderr:\n$stderr"
+}
+
+# --- TEST 8 ---
+
+set subtest8 "TEST 8: pretty-printer adds a semicolon (under while)"
+set test_name "$test: $subtest8"
+
+set cmd "stap -p1 '$srcdir/$subdir/${test}_8.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set out_pat "^for \\(; 1; \\) return;
+"
+regsub -all -- {\n} $out_pat {\n} escaped_out_pat
+if {[regexp -linestop -lineanchor -- $out_pat $out]} {
+ pass "${test_name}: stdout matches \"$escaped_out_pat\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_out_pat\": got \"$out\""
+}
+
+if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+} else {
+ pass "${test_name}: exit code is zero"
+}
+if {$stderr ne ""} {
+ send_log "stderr:\n$stderr"
+}
+
+# --- TEST 9 ---
+
+set subtest9 "TEST 9: pretty-printer adds a semicolon (under foreach)"
+set test_name "$test: $subtest9"
+
+set cmd "stap -p1 '$srcdir/$subdir/${test}_9.stp'"
+send_log "executing: $cmd\n"
+set pipe [open "| sh -c {$cmd}" r]
+set out [read $pipe]
+set failed 0
+if {[catch {close $pipe} stderr] != 0} {
+ if {$stderr ne "" && [string index $stderr end] ne "\n"} {
+ append stderr "\n"
+ }
+ global errorCode
+ if {"CHILDSTATUS" == [lindex $errorCode 0]} {
+ set failed [lindex $errorCode 2]
+ }
+}
+
+set out_pat "^foreach \\(\\\[k\\\] in a\\) return;
+"
+regsub -all -- {\n} $out_pat {\n} escaped_out_pat
+if {[regexp -linestop -lineanchor -- $out_pat $out]} {
+ pass "${test_name}: stdout matches \"$escaped_out_pat\""
+} else {
+ fail "${test_name}: stdout fails to match \"$escaped_out_pat\": got \"$out\""
+}
+
+if {$failed} {
+ fail "${test_name}: exit code should be zero but is $failed"
+} else {
+ pass "${test_name}: exit code is zero"
+}
+if {$stderr ne ""} {
+ send_log "stderr:\n$stderr"
+}
diff --git a/testsuite/systemtap.base/return_no_val_1.stp b/testsuite/systemtap.base/return_no_val_1.stp
new file mode 100644
index 000000000..48c54fc73
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_1.stp
@@ -0,0 +1,10 @@
+function f() {
+ println("enter f");
+ return;
+ println("leave f");
+}
+
+probe oneshot {
+ f();
+ printf("exit\n");
+}
diff --git a/testsuite/systemtap.base/return_no_val_10.stp b/testsuite/systemtap.base/return_no_val_10.stp
new file mode 100644
index 000000000..b30ddc126
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_10.stp
@@ -0,0 +1,10 @@
+function f() {
+ println("enter f");
+ return
+ next
+}
+
+probe oneshot {
+ f();
+ printf("exit\n");
+}
diff --git a/testsuite/systemtap.base/return_no_val_11.stp b/testsuite/systemtap.base/return_no_val_11.stp
new file mode 100644
index 000000000..0de8a2c81
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_11.stp
@@ -0,0 +1,12 @@
+global a
+function f() {
+ println("enter f");
+ a[1] = 3
+ return
+ delete a
+}
+
+probe oneshot {
+ f();
+ printf("a[1] = %d\n", a[1]);
+}
diff --git a/testsuite/systemtap.base/return_no_val_12.stp b/testsuite/systemtap.base/return_no_val_12.stp
new file mode 100644
index 000000000..2f28e5df4
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_12.stp
@@ -0,0 +1,14 @@
+global a
+function f() {
+ println("enter f");
+ a[1] = 3
+ return
+ try {
+ delete a
+ } catch {}
+}
+
+probe oneshot {
+ f();
+ printf("a[1] = %d\n", a[1]);
+}
diff --git a/testsuite/systemtap.base/return_no_val_13.stp b/testsuite/systemtap.base/return_no_val_13.stp
new file mode 100644
index 000000000..cd64295c0
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_13.stp
@@ -0,0 +1,12 @@
+function f() {
+ while (1) {
+ println("enter f");
+ return
+ break
+ }
+}
+
+probe oneshot {
+ f();
+ printf("exit\n");
+}
diff --git a/testsuite/systemtap.base/return_no_val_14.stp b/testsuite/systemtap.base/return_no_val_14.stp
new file mode 100644
index 000000000..b4a5d942a
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_14.stp
@@ -0,0 +1,12 @@
+function f() {
+ while (1) {
+ println("enter f");
+ return
+ continue
+ }
+}
+
+probe oneshot {
+ f();
+ printf("exit\n");
+}
diff --git a/testsuite/systemtap.base/return_no_val_15.stp b/testsuite/systemtap.base/return_no_val_15.stp
new file mode 100644
index 000000000..60fb3b622
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_15.stp
@@ -0,0 +1,7 @@
+function f() {
+ return return
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_16.stp b/testsuite/systemtap.base/return_no_val_16.stp
new file mode 100644
index 000000000..3bcfc2d94
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_16.stp
@@ -0,0 +1,10 @@
+function f() {
+ return
+ if (1) {
+ println("leave f");
+ }
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_17.stp b/testsuite/systemtap.base/return_no_val_17.stp
new file mode 100644
index 000000000..4c2197676
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_17.stp
@@ -0,0 +1,10 @@
+function f() {
+ if (1)
+ return
+ while (1)
+ next
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_18.stp b/testsuite/systemtap.base/return_no_val_18.stp
new file mode 100644
index 000000000..cdd52826b
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_18.stp
@@ -0,0 +1,10 @@
+function f() {
+ for (;;)
+ return
+ while (1)
+ next
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_19.stp b/testsuite/systemtap.base/return_no_val_19.stp
new file mode 100644
index 000000000..318deeeae
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_19.stp
@@ -0,0 +1,9 @@
+function f() {
+ while (1)
+ return
+ next
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_2.stp b/testsuite/systemtap.base/return_no_val_2.stp
new file mode 100644
index 000000000..adda25b53
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_2.stp
@@ -0,0 +1,10 @@
+function f() {
+ println("enter f");
+ println("leave f");
+ return;
+}
+
+probe oneshot {
+ f();
+ printf("exit\n");
+}
diff --git a/testsuite/systemtap.base/return_no_val_20.stp b/testsuite/systemtap.base/return_no_val_20.stp
new file mode 100644
index 000000000..77f824a3b
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_20.stp
@@ -0,0 +1,11 @@
+global a
+function f() {
+ a[1] = 3
+ foreach (k in a)
+ return
+ next
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_3.stp b/testsuite/systemtap.base/return_no_val_3.stp
new file mode 100644
index 000000000..c3f4c910c
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_3.stp
@@ -0,0 +1,10 @@
+function f(a) {
+ if (a > 0) {
+ return;
+ }
+ return 1;
+}
+
+probe oneshot {
+ println(f(3))
+}
diff --git a/testsuite/systemtap.base/return_no_val_4.stp b/testsuite/systemtap.base/return_no_val_4.stp
new file mode 100644
index 000000000..3d93abef7
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_4.stp
@@ -0,0 +1,9 @@
+function f(a) {
+ if (a > 0) {
+ return;
+ }
+}
+
+probe oneshot {
+ println(1 + f(3))
+}
diff --git a/testsuite/systemtap.base/return_no_val_5.stp b/testsuite/systemtap.base/return_no_val_5.stp
new file mode 100644
index 000000000..5d2314aec
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_5.stp
@@ -0,0 +1,7 @@
+function f() {
+ return; return
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_6.stp b/testsuite/systemtap.base/return_no_val_6.stp
new file mode 100644
index 000000000..3b614ad53
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_6.stp
@@ -0,0 +1,8 @@
+function f() {
+ if (1)
+ return
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_7.stp b/testsuite/systemtap.base/return_no_val_7.stp
new file mode 100644
index 000000000..ef5fc265d
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_7.stp
@@ -0,0 +1,8 @@
+function f() {
+ for (;;)
+ return
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_8.stp b/testsuite/systemtap.base/return_no_val_8.stp
new file mode 100644
index 000000000..50dbdbcf6
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_8.stp
@@ -0,0 +1,8 @@
+function f() {
+ while (1)
+ return
+}
+
+probe oneshot {
+ f();
+}
diff --git a/testsuite/systemtap.base/return_no_val_9.stp b/testsuite/systemtap.base/return_no_val_9.stp
new file mode 100644
index 000000000..da2fa5500
--- /dev/null
+++ b/testsuite/systemtap.base/return_no_val_9.stp
@@ -0,0 +1,10 @@
+global a
+function f() {
+ a[1] = 3
+ foreach (k in a)
+ return
+}
+
+probe oneshot {
+ f();
+}
diff --git a/translate.cxx b/translate.cxx
index a1b25d9b3..ad120db06 100644
--- a/translate.cxx
+++ b/translate.cxx
@@ -4405,11 +4405,18 @@ c_unparser::visit_return_statement (return_statement* s)
if (current_function == 0)
throw SEMANTIC_ERROR (_("cannot 'return' from probe"), s->tok);
- if (s->value->type != current_function->type)
+ if (s->value)
+ {
+ if (s->value->type != current_function->type)
+ throw SEMANTIC_ERROR (_("return type mismatch"), current_function->tok,
+ s->tok);
+
+ c_assign ("l->__retvalue", s->value, "return value");
+ }
+ else if (current_function->type != pe_unknown)
throw SEMANTIC_ERROR (_("return type mismatch"), current_function->tok,
s->tok);
- c_assign ("l->__retvalue", s->value, "return value");
record_actions(1, s->tok, true);
o->newline() << "goto out;";
}
--
2.11.0.295.gd7dffce
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v4] Add support for return statements without values
2018-08-31 21:53 ` [PATCH v4] " Yichun Zhang (agentzh)
@ 2018-08-31 23:47 ` Yichun Zhang
0 siblings, 0 replies; 5+ messages in thread
From: Yichun Zhang @ 2018-08-31 23:47 UTC (permalink / raw)
To: systemtap
Hello!
On Fri, Aug 31, 2018 at 2:52 PM Yichun Zhang (agentzh) wrote:
> For user-defined stap functions which do not return any values, it can
> be convenient to allow *bare* return statements to take shortcuts in
> the control flow of the function body.
>
Committed with more test cases covering syntax error cases, with
fche's green light on IRC.
Best,
Yichun
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2018-08-31 23:47 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-08-25 1:09 [PATCH] Add support for return statements without values Yichun Zhang (agentzh)
2018-08-28 20:53 ` [PATCH v2] " Yichun Zhang (agentzh)
2018-08-28 22:25 ` [PATCH v3] " Yichun Zhang (agentzh)
2018-08-31 21:53 ` [PATCH v4] " Yichun Zhang (agentzh)
2018-08-31 23:47 ` Yichun Zhang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).