checker: warn when using `goto` outside of `unsafe` (#8741)
parent
6781f732f4
commit
629d43caf5
12
doc/docs.md
12
doc/docs.md
|
@ -3910,21 +3910,25 @@ fn C.DefWindowProc(hwnd int, msg int, lparam int, wparam int)
|
|||
|
||||
V allows unconditionally jumping to a label with `goto`. The label name must be contained
|
||||
within the same function as the `goto` statement. A program may `goto` a label outside
|
||||
or deeper than the current scope, but it must not skip a variable initialization.
|
||||
or deeper than the current scope. `goto` allows jumping past variable initialization or
|
||||
jumping back to code that accesses memory that has already been freed, so it requires
|
||||
`unsafe`.
|
||||
|
||||
```v ignore
|
||||
if x {
|
||||
// ...
|
||||
if y {
|
||||
unsafe {
|
||||
goto my_label
|
||||
}
|
||||
}
|
||||
// ...
|
||||
}
|
||||
my_label:
|
||||
```
|
||||
`goto` should be avoided when `for` can be used instead. In particular,
|
||||
[labelled break](#labelled-break--continue) can be used to break out of
|
||||
a nested loop.
|
||||
`goto` should be avoided, particularly when `for` can be used instead.
|
||||
[Labelled break/continue](#labelled-break--continue) can be used to break out of
|
||||
a nested loop, and those do not risk violating memory-safety.
|
||||
|
||||
# Appendices
|
||||
|
||||
|
|
|
@ -362,8 +362,7 @@ pub fn (fs FlagParser) usage() string {
|
|||
if positive_min_arg || positive_max_arg || no_arguments {
|
||||
if no_arguments {
|
||||
use += 'This application does not expect any arguments\n\n'
|
||||
goto end_of_arguments_handling
|
||||
}
|
||||
} else {
|
||||
mut s := []string{}
|
||||
if positive_min_arg {
|
||||
s << 'at least $fs.min_free_args'
|
||||
|
@ -377,7 +376,7 @@ pub fn (fs FlagParser) usage() string {
|
|||
sargs := s.join(' and ')
|
||||
use += 'The arguments should be $sargs in number.\n\n'
|
||||
}
|
||||
end_of_arguments_handling:
|
||||
}
|
||||
if fs.flags.len > 0 {
|
||||
use += 'Options:\n'
|
||||
for f in fs.flags {
|
||||
|
|
|
@ -3178,6 +3178,10 @@ fn (mut c Checker) stmt(node ast.Stmt) {
|
|||
}
|
||||
ast.GotoLabel {}
|
||||
ast.GotoStmt {
|
||||
if !c.inside_unsafe {
|
||||
c.warn('`goto` requires `unsafe` (consider using labelled break/continue)',
|
||||
node.pos)
|
||||
}
|
||||
if node.name !in c.cur_fn.label_names {
|
||||
c.error('unknown label `$node.name`', node.pos)
|
||||
}
|
||||
|
|
|
@ -45,5 +45,5 @@ vlib/v/checker/tests/goto_label.vv:25:7: error: unknown label `undefined`
|
|||
24 | goto g1 // back
|
||||
25 | goto undefined
|
||||
| ~~~~~~~~~
|
||||
26 | }
|
||||
26 | }}
|
||||
27 |
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
fn f() {
|
||||
fn f() {unsafe {
|
||||
goto f1 // forward
|
||||
goto f2
|
||||
f1:
|
||||
|
@ -14,19 +14,20 @@ fn f() {
|
|||
goto a1
|
||||
goto f1 // back
|
||||
goto f2
|
||||
}
|
||||
}}
|
||||
|
||||
fn g() {
|
||||
fn g() {unsafe {
|
||||
goto g1 // forward
|
||||
g1:
|
||||
goto f1
|
||||
goto a1
|
||||
goto g1 // back
|
||||
goto undefined
|
||||
}
|
||||
}}
|
||||
|
||||
// implicit main
|
||||
unsafe {
|
||||
goto m1
|
||||
m1:
|
||||
goto m1
|
||||
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ fn (mut p Parser) lock_expr() ast.LockExpr {
|
|||
mut pos := p.tok.position()
|
||||
mut lockeds := []ast.Ident{}
|
||||
mut is_rlocked := []bool{}
|
||||
for {
|
||||
outer: for {
|
||||
if p.tok.kind == .lcbr {
|
||||
goto start_stmts
|
||||
break
|
||||
}
|
||||
is_rlock := p.tok.kind == .key_rlock
|
||||
if !is_rlock && p.tok.kind != .key_lock {
|
||||
|
@ -31,7 +31,7 @@ fn (mut p Parser) lock_expr() ast.LockExpr {
|
|||
is_rlocked << is_rlock
|
||||
p.next()
|
||||
if p.tok.kind == .lcbr {
|
||||
goto start_stmts
|
||||
break outer
|
||||
}
|
||||
if p.tok.kind == .semicolon {
|
||||
p.next()
|
||||
|
@ -40,7 +40,6 @@ fn (mut p Parser) lock_expr() ast.LockExpr {
|
|||
p.check(.comma)
|
||||
}
|
||||
}
|
||||
start_stmts:
|
||||
stmts := p.parse_block()
|
||||
pos.update_last_line(p.prev_tok.line_nr)
|
||||
return ast.LockExpr{
|
||||
|
|
Loading…
Reference in New Issue