cgen: fix deadlock when returning/breaking in `lock` (#10079)
parent
066374bae4
commit
8361f714dd
|
@ -743,6 +743,7 @@ pub mut:
|
|||
lockeds []Ident // `x`, `y` in `lock x, y {`
|
||||
is_expr bool
|
||||
typ Type
|
||||
scope &Scope
|
||||
}
|
||||
|
||||
pub struct MatchExpr {
|
||||
|
|
|
@ -193,7 +193,7 @@ pub fn (s &Scope) innermost(pos int) &Scope {
|
|||
}
|
||||
|
||||
[inline]
|
||||
fn (s &Scope) contains(pos int) bool {
|
||||
pub fn (s &Scope) contains(pos int) bool {
|
||||
return pos >= s.start_pos && pos <= s.end_pos
|
||||
}
|
||||
|
||||
|
|
|
@ -3843,6 +3843,9 @@ fn (mut c Checker) stmt(node ast.Stmt) {
|
|||
node.idx_in_fn = c.table.cur_fn.defer_stmts.len
|
||||
c.table.cur_fn.defer_stmts << unsafe { &node }
|
||||
}
|
||||
if c.locked_names.len != 0 || c.rlocked_names.len != 0 {
|
||||
c.error('defers are not allowed in lock statements', node.pos)
|
||||
}
|
||||
c.stmts(node.stmts)
|
||||
}
|
||||
ast.EnumDecl {
|
||||
|
|
|
@ -83,10 +83,14 @@ mut:
|
|||
optionals []string // to avoid duplicates TODO perf, use map
|
||||
chan_pop_optionals []string // types for `x := <-ch or {...}`
|
||||
chan_push_optionals []string // types for `ch <- x or {...}`
|
||||
shareds []int // types with hidden mutex for which decl has been emitted
|
||||
inside_ternary int // ?: comma separated statements on a single line
|
||||
inside_map_postfix bool // inside map++/-- postfix expr
|
||||
inside_map_infix bool // inside map<</+=/-= infix expr
|
||||
cur_lock ast.LockExpr
|
||||
mtxs string // array of mutexes if the `lock` has multiple variables
|
||||
labeled_loops map[string]&ast.Stmt
|
||||
inner_loop &ast.Stmt
|
||||
shareds []int // types with hidden mutex for which decl has been emitted
|
||||
inside_ternary int // ?: comma separated statements on a single line
|
||||
inside_map_postfix bool // inside map++/-- postfix expr
|
||||
inside_map_infix bool // inside map<</+=/-= infix expr
|
||||
inside_map_index bool
|
||||
inside_opt_data bool
|
||||
inside_if_optional bool
|
||||
|
@ -217,6 +221,7 @@ pub fn gen(files []ast.File, table &ast.Table, pref &pref.Preferences) string {
|
|||
indent: -1
|
||||
module_built: module_built
|
||||
timers: util.new_timers(timers_should_print)
|
||||
inner_loop: &ast.EmptyStmt{}
|
||||
}
|
||||
g.timers.start('cgen init')
|
||||
for mod in g.table.modules {
|
||||
|
@ -1106,7 +1111,30 @@ fn (mut g Gen) stmt(node ast.Stmt) {
|
|||
}
|
||||
ast.BranchStmt {
|
||||
g.write_v_source_line_info(node.pos)
|
||||
|
||||
if node.label != '' {
|
||||
x := g.labeled_loops[node.label] or {
|
||||
panic('$node.label doesn\'t exist $g.file.path, $node.pos')
|
||||
}
|
||||
match x {
|
||||
ast.ForCStmt {
|
||||
if x.scope.contains(g.cur_lock.pos.pos) {
|
||||
g.unlock_locks()
|
||||
}
|
||||
}
|
||||
ast.ForInStmt {
|
||||
if x.scope.contains(g.cur_lock.pos.pos) {
|
||||
g.unlock_locks()
|
||||
}
|
||||
}
|
||||
ast.ForStmt {
|
||||
if x.scope.contains(g.cur_lock.pos.pos) {
|
||||
g.unlock_locks()
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
if node.kind == .key_break {
|
||||
g.writeln('goto ${node.label}__break;')
|
||||
} else {
|
||||
|
@ -1114,6 +1142,25 @@ fn (mut g Gen) stmt(node ast.Stmt) {
|
|||
g.writeln('goto ${node.label}__continue;')
|
||||
}
|
||||
} else {
|
||||
inner_loop := g.inner_loop
|
||||
match inner_loop {
|
||||
ast.ForCStmt {
|
||||
if inner_loop.scope.contains(g.cur_lock.pos.pos) {
|
||||
g.unlock_locks()
|
||||
}
|
||||
}
|
||||
ast.ForInStmt {
|
||||
if inner_loop.scope.contains(g.cur_lock.pos.pos) {
|
||||
g.unlock_locks()
|
||||
}
|
||||
}
|
||||
ast.ForStmt {
|
||||
if inner_loop.scope.contains(g.cur_lock.pos.pos) {
|
||||
g.unlock_locks()
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
// continue or break
|
||||
if g.is_autofree && !g.is_builtin_mod {
|
||||
g.trace_autofree('// free before continue/break')
|
||||
|
@ -1192,23 +1239,44 @@ fn (mut g Gen) stmt(node ast.Stmt) {
|
|||
ast.ForCStmt {
|
||||
prev_branch_parent_pos := g.branch_parent_pos
|
||||
g.branch_parent_pos = node.pos.pos
|
||||
save_inner_loop := g.inner_loop
|
||||
g.inner_loop = unsafe { &node }
|
||||
if node.label != '' {
|
||||
g.labeled_loops[node.label] = unsafe { &node }
|
||||
}
|
||||
g.write_v_source_line_info(node.pos)
|
||||
g.for_c_stmt(node)
|
||||
g.branch_parent_pos = prev_branch_parent_pos
|
||||
g.labeled_loops.delete(node.label)
|
||||
g.inner_loop = save_inner_loop
|
||||
}
|
||||
ast.ForInStmt {
|
||||
prev_branch_parent_pos := g.branch_parent_pos
|
||||
g.branch_parent_pos = node.pos.pos
|
||||
save_inner_loop := g.inner_loop
|
||||
g.inner_loop = unsafe { &node }
|
||||
if node.label != '' {
|
||||
g.labeled_loops[node.label] = unsafe { &node }
|
||||
}
|
||||
g.write_v_source_line_info(node.pos)
|
||||
g.for_in_stmt(node)
|
||||
g.branch_parent_pos = prev_branch_parent_pos
|
||||
g.labeled_loops.delete(node.label)
|
||||
g.inner_loop = save_inner_loop
|
||||
}
|
||||
ast.ForStmt {
|
||||
prev_branch_parent_pos := g.branch_parent_pos
|
||||
g.branch_parent_pos = node.pos.pos
|
||||
save_inner_loop := g.inner_loop
|
||||
g.inner_loop = unsafe { &node }
|
||||
if node.label != '' {
|
||||
g.labeled_loops[node.label] = unsafe { &node }
|
||||
}
|
||||
g.write_v_source_line_info(node.pos)
|
||||
g.for_stmt(node)
|
||||
g.branch_parent_pos = prev_branch_parent_pos
|
||||
g.labeled_loops.delete(node.label)
|
||||
g.inner_loop = save_inner_loop
|
||||
}
|
||||
ast.GlobalDecl {
|
||||
g.global_decl(node)
|
||||
|
@ -3872,6 +3940,12 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
|
|||
}
|
||||
|
||||
fn (mut g Gen) lock_expr(node ast.LockExpr) {
|
||||
g.cur_lock = unsafe { node } // is ok because it is discarded at end of fn
|
||||
defer {
|
||||
g.cur_lock = ast.LockExpr{
|
||||
scope: 0
|
||||
}
|
||||
}
|
||||
tmp_result := if node.is_expr { g.new_tmp_var() } else { '' }
|
||||
mut cur_line := ''
|
||||
if node.is_expr {
|
||||
|
@ -3920,30 +3994,19 @@ fn (mut g Gen) lock_expr(node ast.LockExpr) {
|
|||
g.writeln('\t\tsync__RwMutex_lock((sync__RwMutex*)_arr_$mtxs[$mtxs]);')
|
||||
g.writeln('}')
|
||||
}
|
||||
println('')
|
||||
g.mtxs = mtxs
|
||||
defer {
|
||||
g.mtxs = ''
|
||||
}
|
||||
|
||||
g.writeln('/*lock*/ {')
|
||||
g.stmts_with_tmp_var(node.stmts, tmp_result)
|
||||
if node.is_expr {
|
||||
g.writeln(';')
|
||||
}
|
||||
g.writeln('}')
|
||||
if node.lockeds.len == 0 {
|
||||
// this should not happen
|
||||
} else if node.lockeds.len == 1 {
|
||||
id := node.lockeds[0]
|
||||
name := id.name
|
||||
deref := if id.is_mut { '->' } else { '.' }
|
||||
lock_prefix := if node.is_rlock[0] { 'r' } else { '' }
|
||||
g.writeln('sync__RwMutex_${lock_prefix}unlock(&$name${deref}mtx);')
|
||||
} else {
|
||||
// unlock in reverse order
|
||||
g.writeln('for (int $mtxs=${node.lockeds.len - 1}; $mtxs>=0; $mtxs--) {')
|
||||
g.writeln('\tif ($mtxs && _arr_$mtxs[$mtxs] == _arr_$mtxs[$mtxs-1]) continue;')
|
||||
g.writeln('\tif (_isrlck_$mtxs[$mtxs])')
|
||||
g.writeln('\t\tsync__RwMutex_runlock((sync__RwMutex*)_arr_$mtxs[$mtxs]);')
|
||||
g.writeln('\telse')
|
||||
g.writeln('\t\tsync__RwMutex_unlock((sync__RwMutex*)_arr_$mtxs[$mtxs]);')
|
||||
g.write('}')
|
||||
}
|
||||
g.unlock_locks()
|
||||
if node.is_expr {
|
||||
g.writeln('')
|
||||
g.write(cur_line)
|
||||
|
@ -3951,6 +4014,25 @@ fn (mut g Gen) lock_expr(node ast.LockExpr) {
|
|||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) unlock_locks() {
|
||||
if g.cur_lock.lockeds.len == 0 {
|
||||
} else if g.cur_lock.lockeds.len == 1 {
|
||||
id := g.cur_lock.lockeds[0]
|
||||
name := id.name
|
||||
deref := if id.is_mut { '->' } else { '.' }
|
||||
lock_prefix := if g.cur_lock.is_rlock[0] { 'r' } else { '' }
|
||||
g.writeln('sync__RwMutex_${lock_prefix}unlock(&$name${deref}mtx);')
|
||||
} else {
|
||||
g.writeln('for (int $g.mtxs=${g.cur_lock.lockeds.len - 1}; $g.mtxs>=0; $g.mtxs--) {')
|
||||
g.writeln('\tif ($g.mtxs && _arr_$g.mtxs[$g.mtxs] == _arr_$g.mtxs[$g.mtxs-1]) continue;')
|
||||
g.writeln('\tif (_isrlck_$g.mtxs[$g.mtxs])')
|
||||
g.writeln('\t\tsync__RwMutex_runlock((sync__RwMutex*)_arr_$g.mtxs[$g.mtxs]);')
|
||||
g.writeln('\telse')
|
||||
g.writeln('\t\tsync__RwMutex_unlock((sync__RwMutex*)_arr_$g.mtxs[$g.mtxs]);')
|
||||
g.write('}')
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) need_tmp_var_in_match(node ast.MatchExpr) bool {
|
||||
if node.is_expr && node.return_type != ast.void_type && node.return_type != 0 {
|
||||
sym := g.table.get_type_symbol(node.return_type)
|
||||
|
@ -4760,6 +4842,7 @@ fn (mut g Gen) return_stmt(node ast.Return) {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
g.inside_return = true
|
||||
defer {
|
||||
g.inside_return = false
|
||||
|
|
|
@ -347,6 +347,8 @@ fn (g &Gen) defer_flag_var(stmt &ast.DeferStmt) string {
|
|||
}
|
||||
|
||||
fn (mut g Gen) write_defer_stmts_when_needed() {
|
||||
// unlock all mutexes, in case we are in a lock statement. defers are not allowed in lock statements
|
||||
g.unlock_locks()
|
||||
if g.defer_stmts.len > 0 {
|
||||
g.write_defer_stmts()
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import v.ast
|
|||
fn (mut p Parser) lock_expr() ast.LockExpr {
|
||||
// TODO Handle aliasing sync
|
||||
p.register_auto_import('sync')
|
||||
p.open_scope()
|
||||
mut pos := p.tok.position()
|
||||
mut lockeds := []ast.Ident{}
|
||||
mut is_rlocked := []bool{}
|
||||
|
@ -39,12 +40,15 @@ fn (mut p Parser) lock_expr() ast.LockExpr {
|
|||
p.check(.comma)
|
||||
}
|
||||
}
|
||||
stmts := p.parse_block()
|
||||
stmts := p.parse_block_no_scope(false)
|
||||
scope := p.scope
|
||||
p.close_scope()
|
||||
pos.update_last_line(p.prev_tok.line_nr)
|
||||
return ast.LockExpr{
|
||||
lockeds: lockeds
|
||||
stmts: stmts
|
||||
is_rlock: is_rlocked
|
||||
pos: pos
|
||||
scope: scope
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import time
|
||||
|
||||
struct AA {
|
||||
mut:
|
||||
b string
|
||||
}
|
||||
|
||||
const (
|
||||
sleep_time = time.millisecond * 50
|
||||
)
|
||||
|
||||
fn test_return_lock() {
|
||||
shared s := AA{'3'}
|
||||
go printer(shared s)
|
||||
go fn (shared s AA) {
|
||||
start := time.now()
|
||||
for {
|
||||
reader(shared s)
|
||||
if time.now() - start > sleep_time {
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}(shared s)
|
||||
time.sleep(sleep_time * 2)
|
||||
assert false
|
||||
}
|
||||
|
||||
fn printer(shared s AA) {
|
||||
start := time.now()
|
||||
for {
|
||||
lock s {
|
||||
assert s.b in ['0', '1', '2', '3', '4', '5']
|
||||
}
|
||||
if time.now() - start > time.millisecond * 50 {
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reader(shared s AA) {
|
||||
mut i := 0
|
||||
for {
|
||||
i++
|
||||
x := i.str()
|
||||
lock s {
|
||||
s.b = x
|
||||
if s.b == '5' {
|
||||
// this test checks if cgen unlocks the mutex here
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import time
|
||||
|
||||
struct AA {
|
||||
mut:
|
||||
b string
|
||||
}
|
||||
|
||||
const (
|
||||
sleep_time = time.millisecond * 50
|
||||
)
|
||||
|
||||
fn test_return_lock() {
|
||||
shared s := AA{'3'}
|
||||
go printer(shared s)
|
||||
go fn (shared s AA) {
|
||||
start := time.now()
|
||||
for {
|
||||
reader(shared s)
|
||||
if time.now() - start > sleep_time {
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}(shared s)
|
||||
time.sleep(sleep_time * 2)
|
||||
assert false
|
||||
}
|
||||
|
||||
fn printer(shared s AA) {
|
||||
start := time.now()
|
||||
for {
|
||||
lock s {
|
||||
assert s.b in ['0', '1', '2', '3', '4', '5']
|
||||
}
|
||||
if time.now() - start > time.millisecond * 50 {
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reader(shared s AA) {
|
||||
mut i := 0
|
||||
for {
|
||||
i++
|
||||
x := i.str()
|
||||
lock s {
|
||||
s.b = x
|
||||
if s.b == '5' {
|
||||
// this test checks if cgen unlocks the mutex here
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue