all: support `[noreturn] fn abc() { for{} }`, mark panic/1 and exit/1with it too. (#10654)

pull/10668/head
Delyan Angelov 2021-07-04 20:24:19 +03:00 committed by GitHub
parent b0b4b8e65b
commit 6aecda3be8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 318 additions and 22 deletions

View File

@ -4763,6 +4763,21 @@ fn legacy_function() {}
fn inlined_function() {
}
// This function's calls will NOT be inlined.
[noinline]
fn function() {
}
// This function will NOT return to its callers.
// Such functions can be used at the end of or blocks,
// just like exit/1 or panic/1. Such functions can not
// have return types, and should end either in for{}, or
// by calling other `[noreturn]` functions.
[noreturn]
fn forever() {
for {}
}
// The following struct must be allocated on the heap. Therefore, it can only be used as a
// reference (`&Window`) or inside another reference (`&OuterStruct{ Window{...} }`).
[heap]

View File

@ -4,9 +4,16 @@ type FnExitCb = fn ()
fn C.atexit(f FnExitCb) int
[noreturn]
fn vhalt() {
for {}
}
// exit terminates execution immediately and returns exit `code` to the shell.
[noreturn]
pub fn exit(code int) {
C.exit(code)
vhalt()
}
fn vcommithash() string {
@ -17,6 +24,7 @@ fn vcommithash() string {
// recent versions of tcc print nicer backtraces automatically
// NB: the duplication here is because tcc_backtrace should be called directly
// inside the panic functions.
[noreturn]
fn panic_debug(line_no int, file string, mod string, fn_name string, s string) {
// NB: the order here is important for a stabler test output
// module is less likely to change than function, etc...
@ -52,14 +60,17 @@ fn panic_debug(line_no int, file string, mod string, fn_name string, s string) {
C.exit(1)
}
}
vhalt()
}
[noreturn]
pub fn panic_optional_not_set(s string) {
panic('optional not set ($s)')
}
// panic prints a nice error message, then exits the process with exit code of 1.
// It also shows a backtrace on most platforms.
[noreturn]
pub fn panic(s string) {
$if freestanding {
bare_panic(s)
@ -87,6 +98,7 @@ pub fn panic(s string) {
C.exit(1)
}
}
vhalt()
}
// eprintln prints a message with a line end, to stderr. Both stderr and stdout are flushed.

View File

@ -140,6 +140,7 @@ pub fn write(fd i64, buf &byte, count u64) i64 {
return x
}
[noreturn]
fn bare_panic(msg string) {
println('V panic' + msg)
exit(1)
@ -150,6 +151,7 @@ fn bare_backtrace() string {
}
[export: 'exit']
[noreturn]
fn __exit(code int) {
sys_exit(code)
}

View File

@ -303,8 +303,10 @@ fn sys_execve(filename &byte, argv []&byte, envp []&byte) int {
}
// 60 sys_exit
[noreturn]
fn sys_exit(ec int) {
sys_call1(60, u64(ec))
for {}
}
// 102 sys_getuid

View File

@ -363,6 +363,7 @@ pub:
is_pub bool
is_variadic bool
is_anon bool
is_noreturn bool // true, when [noreturn] is used on a fn
is_manualfree bool // true, when [manualfree] is used on a fn
is_main bool // true for `fn main()`
is_test bool // true for `fn test_abcde`
@ -421,6 +422,7 @@ pub mut:
is_method bool
is_field bool // temp hack, remove ASAP when re-impl CallExpr / Selector (joe)
is_keep_alive bool // GC must not free arguments before fn returns
is_noreturn bool // whether the function/method is marked as [noreturn]
args []CallArg
expected_arg_types []Type
language Language

View File

@ -73,6 +73,7 @@ pub:
generic_names []string
is_pub bool
is_deprecated bool // `[deprecated] fn abc(){}`
is_noreturn bool // `[noreturn] fn abc(){}`
is_unsafe bool // `[unsafe] fn abc(){}`
is_placeholder bool
is_main bool // `fn main(){}`

View File

@ -1936,6 +1936,7 @@ pub fn (mut c Checker) method_call(mut call_expr ast.CallExpr) ast.Type {
}
}
if has_method {
call_expr.is_noreturn = method.is_noreturn
if !method.is_pub && !c.pref.is_test && method.mod != c.mod {
// If a private method is called outside of the module
// its receiver type is defined in, show an error.
@ -2439,10 +2440,13 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type {
c.table.fns[fn_name].usages++
}
}
mut is_native_builtin := false
if !found && c.pref.backend == .native {
if fn_name in native.builtins {
c.table.fns[fn_name].usages++
return ast.void_type
found = true
func = c.table.fns[fn_name]
is_native_builtin = true
}
}
if !found && c.pref.is_vsh {
@ -2457,6 +2461,9 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type {
c.table.fns[os_name].usages++
}
}
if is_native_builtin {
return ast.void_type
}
// check for arg (var) of fn type
if !found {
if v := call_expr.scope.find_var(fn_name) {
@ -2493,6 +2500,7 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type {
c.error('unknown function: $fn_name', call_expr.pos)
return ast.void_type
}
call_expr.is_noreturn = func.is_noreturn
if !found_in_args {
if _ := call_expr.scope.find_var(fn_name) {
c.error('ambiguous call to: `$fn_name`, may refer to fn `$fn_name` or variable `$fn_name`',
@ -2906,13 +2914,13 @@ pub fn (mut c Checker) check_or_expr(or_expr ast.OrExpr, ret_type ast.Type, expr
c.expected_or_type = ast.void_type
type_fits := c.check_types(last_stmt_typ, ret_type)
&& last_stmt_typ.nr_muls() == ret_type.nr_muls()
is_panic_or_exit := is_expr_panic_or_exit(last_stmt.expr)
if type_fits || is_panic_or_exit {
is_noreturn := is_noreturn_callexpr(last_stmt.expr)
if type_fits || is_noreturn {
return
}
expected_type_name := c.table.type_to_str(ret_type.clear_flag(.optional))
if last_stmt.typ == ast.void_type {
c.error('`or` block must provide a default value of type `$expected_type_name`, or return/exit/continue/break/panic',
c.error('`or` block must provide a default value of type `$expected_type_name`, or return/continue/break or call a [noreturn] function like panic(err) or exit(1)',
last_stmt.pos)
} else {
type_name := c.table.type_to_str(last_stmt_typ)
@ -2942,7 +2950,7 @@ pub fn (mut c Checker) check_or_expr(or_expr ast.OrExpr, ret_type ast.Type, expr
if last_stmt.typ == ast.void_type {
return
}
if is_expr_panic_or_exit(last_stmt.expr) {
if is_noreturn_callexpr(last_stmt.expr) {
return
}
if c.check_types(last_stmt.typ, expr_return_type) {
@ -2959,11 +2967,11 @@ pub fn (mut c Checker) check_or_expr(or_expr ast.OrExpr, ret_type ast.Type, expr
}
}
fn is_expr_panic_or_exit(expr ast.Expr) bool {
match expr {
ast.CallExpr { return !expr.is_method && expr.name in ['panic', 'exit'] }
else { return false }
fn is_noreturn_callexpr(expr ast.Expr) bool {
if expr is ast.CallExpr {
return expr.is_noreturn
}
return false
}
pub fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type {
@ -7572,9 +7580,11 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
}
c.fn_scope = node.scope
c.stmts(node.stmts)
node.has_return = c.returns || has_top_return(node.stmts)
node_has_top_return := has_top_return(node.stmts)
node.has_return = c.returns || node_has_top_return
c.check_noreturn_fn_decl(mut node)
if node.language == .v && !node.no_body && node.return_type != ast.void_type && !node.has_return
&& (node.is_method || node.name !in ['panic', 'exit']) {
&& !node.is_noreturn {
if c.inside_anon_fn {
c.error('missing return at the end of an anonymous function', node.pos)
} else if !node.attrs.contains('_naked') {
@ -7594,20 +7604,27 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
node.source_file = c.file
}
// NB: has_top_return/1 should be called on *already checked* stmts,
// which do have their stmt.expr.is_noreturn set properly:
fn has_top_return(stmts []ast.Stmt) bool {
for stmt in stmts {
if stmt is ast.Return {
return true
} else if stmt is ast.Block {
if has_top_return(stmt.stmts) {
match stmt {
ast.Return {
return true
}
} else if stmt is ast.ExprStmt {
if stmt.expr is ast.CallExpr {
if !stmt.expr.is_method && stmt.expr.name in ['panic', 'exit'] {
ast.Block {
if has_top_return(stmt.stmts) {
return true
}
}
ast.ExprStmt {
if stmt.expr is ast.CallExpr {
if stmt.expr.is_noreturn {
return true
}
}
}
else {}
}
}
return false

View File

@ -0,0 +1,112 @@
module checker
import v.ast
fn (mut c Checker) check_noreturn_fn_decl(mut node ast.FnDecl) {
if !node.is_noreturn {
return
}
if node.no_body {
return
}
if uses_return_stmt(node.stmts) {
c.error('[noreturn] functions cannot use return statements', node.pos)
}
if node.return_type != ast.void_type {
c.error('[noreturn] functions cannot have return types', node.pos)
} else {
if node.stmts.len != 0 {
mut is_valid_end_of_noreturn_fn := false
last_stmt := node.stmts.last()
match last_stmt {
ast.ExprStmt {
if last_stmt.expr is ast.CallExpr {
if last_stmt.expr.should_be_skipped {
c.error('[noreturn] functions cannot end with a skippable `[if ..]` call',
last_stmt.pos)
}
if last_stmt.expr.is_noreturn {
is_valid_end_of_noreturn_fn = true
}
}
}
ast.ForStmt {
if last_stmt.is_inf && last_stmt.stmts.len == 0 {
is_valid_end_of_noreturn_fn = true
}
}
else {}
}
if !is_valid_end_of_noreturn_fn {
c.error('[noreturn] functions should end with a call to another [noreturn] function, or with an infinite `for {}` loop',
last_stmt.pos)
}
}
}
}
fn uses_return_stmt(stmts []ast.Stmt) bool {
if stmts.len == 0 {
return false
}
for stmt in stmts {
match stmt {
ast.Return {
return true
}
ast.Block {
if uses_return_stmt(stmt.stmts) {
return true
}
}
ast.ExprStmt {
match stmt.expr {
ast.CallExpr {
if uses_return_stmt(stmt.expr.or_block.stmts) {
return true
}
}
ast.MatchExpr {
for b in stmt.expr.branches {
if uses_return_stmt(b.stmts) {
return true
}
}
}
ast.SelectExpr {
for b in stmt.expr.branches {
if uses_return_stmt(b.stmts) {
return true
}
}
}
ast.IfExpr {
for b in stmt.expr.branches {
if uses_return_stmt(b.stmts) {
return true
}
}
}
else {}
}
}
ast.ForStmt {
if uses_return_stmt(stmt.stmts) {
return true
}
}
ast.ForCStmt {
if uses_return_stmt(stmt.stmts) {
return true
}
}
ast.ForInStmt {
if uses_return_stmt(stmt.stmts) {
return true
}
}
else {}
}
}
return false
}

View File

@ -0,0 +1,7 @@
vlib/v/checker/tests/noreturn_with_non_empty_loop_at_end.vv:4:6: error: [noreturn] functions should end with a call to another [noreturn] function, or with an infinite `for {}` loop
2 | fn another() {
3 | eprintln(@FN)
4 | for {
| ^
5 | break
6 | }

View File

@ -0,0 +1,19 @@
[noreturn]
fn another() {
eprintln(@FN)
for {
break
}
}
[noreturn]
fn abc() {
eprintln(@FN)
another()
}
fn main() {
eprintln('start')
abc()
eprintln('done')
}

View File

@ -0,0 +1,13 @@
vlib/v/checker/tests/noreturn_with_return.vv:2:1: error: [noreturn] functions cannot use return statements
1 | [noreturn]
2 | fn another() {
| ~~~~~~~~~~~~
3 | eprintln(@FN)
4 | // for{}
vlib/v/checker/tests/noreturn_with_return.vv:6:2: error: [noreturn] functions should end with a call to another [noreturn] function, or with an infinite `for {}` loop
4 | // for{}
5 | // exit(0)
6 | return
| ~~~~~~
7 | }
8 |

View File

@ -0,0 +1,19 @@
[noreturn]
fn another() {
eprintln(@FN)
// for{}
// exit(0)
return
}
[noreturn]
fn abc() {
eprintln(@FN)
another()
}
fn main() {
eprintln('start')
abc()
eprintln('done')
}

View File

@ -0,0 +1,7 @@
vlib/v/checker/tests/noreturn_without_loop_or_another_noreturn_at_end.vv:3:2: error: [noreturn] functions should end with a call to another [noreturn] function, or with an infinite `for {}` loop
1 | [noreturn]
2 | fn another() {
3 | eprintln(@FN)
| ~~~~~~~~~~~~~
4 | }
5 |

View File

@ -0,0 +1,16 @@
[noreturn]
fn another() {
eprintln(@FN)
}
[noreturn]
fn abc() {
eprintln(@FN)
another()
}
fn main() {
eprintln('start')
abc()
eprintln('done')
}

View File

@ -0,0 +1 @@
log_and_die: error: oh no

View File

@ -0,0 +1,14 @@
fn abc() ?int {
return error('oh no')
}
[noreturn]
fn log_and_die(e IError) {
eprintln('${@FN}: error: $e')
exit(77)
}
fn main() {
x := abc() or { log_and_die(err) }
println(x)
}

View File

@ -5164,10 +5164,12 @@ fn (mut g Gen) assoc(node ast.Assoc) {
}
}
[noreturn]
fn verror(s string) {
util.verror('cgen error', s)
}
[noreturn]
fn (g &Gen) error(s string, pos token.Position) {
ferror := util.formatted_error('cgen error:', s, g.file.path, pos)
eprintln(ferror)

View File

@ -165,11 +165,26 @@ const c_common_macros = '
#define _MOV
#endif
#if defined(__TINYC__) && defined(__has_include)
// tcc does not support has_include properly yet, turn it off completely
#if defined(__TINYC__) && defined(__has_include)
#undef __has_include
#endif
#if !defined(VNORETURN)
#if defined(__TINYC__)
#include <stdnoreturn.h>
#define VNORETURN noreturn
#endif
# if !defined(__TINYC__) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
# define VNORETURN _Noreturn
# elif defined(__GNUC__) && __GNUC__ >= 2
# define VNORETURN __attribute__((noreturn))
# endif
#ifndef VNORETURN
#define VNORETURN
#endif
#endif
//likely and unlikely macros
#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)
#define _likely_(x) __builtin_expect(x,1)

View File

@ -333,6 +333,9 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
prev_defer_stmts := g.defer_stmts
g.defer_stmts = []
g.stmts(node.stmts)
if node.is_noreturn {
g.writeln('\twhile(1);')
}
// clear g.fn_mut_arg_names
if !node.has_return {
@ -1315,10 +1318,23 @@ fn (mut g Gen) write_fn_attrs(attrs []ast.Attr) string {
'inline' {
g.write('inline ')
}
'no_inline' {
'noinline' {
// since these are supported by GCC, clang and MSVC, we can consider them officially supported.
g.write('__NOINLINE ')
}
'noreturn' {
// a `[noreturn]` tag tells the compiler, that a function
// *DOES NOT RETURN* to its callsites.
// See: https://en.cppreference.com/w/c/language/_Noreturn
// Such functions should have no return type. They can be used
// in places where `panic(err)` or `exit(0)` can be used.
// panic/1 and exit/0 themselves will also be marked as
// `[noreturn]` soon.
// These functions should have busy `for{}` loops injected
// at their end, when they do not end by calling other fns
// marked by `[noreturn]`.
g.write('VNORETURN ')
}
'irq_handler' {
g.write('__IRQHANDLER ')
}

View File

@ -428,7 +428,6 @@ fn (mut g Gen) postfix_expr(node ast.PostfixExpr) {
}
}
// not yet supported
[noreturn]
fn verror(s string) {
util.verror('native gen error', s)

View File

@ -185,8 +185,10 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
mut is_exported := false
mut is_unsafe := false
mut is_trusted := false
mut is_noreturn := false
for fna in p.attrs {
match fna.name {
'noreturn' { is_noreturn = true }
'manualfree' { is_manualfree = true }
'deprecated' { is_deprecated = true }
'direct_array_access' { is_direct_arr = true }
@ -408,6 +410,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
generic_names: generic_names
is_pub: is_pub
is_deprecated: is_deprecated
is_noreturn: is_noreturn
is_unsafe: is_unsafe
is_main: is_main
is_test: is_test
@ -449,6 +452,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
return_type: return_type
return_type_pos: return_type_pos
params: params
is_noreturn: is_noreturn
is_manualfree: is_manualfree
is_deprecated: is_deprecated
is_exported: is_exported

View File

@ -1,4 +1,4 @@
vlib/v/parser/tests/or_default_missing.vv:4:3: error: `or` block must provide a default value of type `int`, or return/exit/continue/break/panic
vlib/v/parser/tests/or_default_missing.vv:4:3: error: `or` block must provide a default value of type `int`, or return/continue/break or call a [noreturn] function like panic(err) or exit(1)
2 | m := [3, 4, 5]
3 | el := m[4] or {
4 | println('error')

View File

@ -141,6 +141,7 @@ pub fn source_context(kind string, source string, pos token.Position) []string {
return clines
}
[noreturn]
pub fn verror(kind string, s string) {
final_kind := bold(color(kind, kind))
eprintln('$final_kind: $s')