Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 215 additions & 2 deletions compiler/crates/hy-check/src/loans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2006,7 +2006,10 @@ impl<'a> Checker<'a> {

/// A tuple call can carry a borrow in any result, not just the first.
fn expr_has_borrow_shape(&self, st: &FnState, e: &Expr) -> bool {
let inner = unwrap_parens(e);
let mut inner = unwrap_parens(e);
while let Expr::Try { operand, .. } = inner {
inner = unwrap_parens(operand);
}
if let Expr::Call { callee, .. } = inner {
if let Some(results) = self.call_result_tys(st, callee) {
return results.iter().any(|ty| {
Expand All @@ -2019,6 +2022,17 @@ impl<'a> Checker<'a> {
field_borrow_kind(&shape).is_some() || matches!(shape, TypeShape::Unknown)
}

/// A multi-return call can return shared GC storage in any result, even
/// when result 0 is primitive.
fn call_any_result_may_share(&self, st: &FnState, callee: &Expr) -> bool {
self.call_result_tys(st, callee).is_some_and(|results| {
results.iter().any(|ty| {
let shape = self.shape_of_type(ty);
self.shape_may_share(&shape) || matches!(shape, TypeShape::Unknown)
})
})
}

pub(crate) fn expr_carries_borrow(&self, st: &FnState, e: &Expr) -> bool {
let inner = unwrap_parens(e);
if as_addr_of(inner).is_some() || contains_addr_of_in_literal(inner) {
Expand Down Expand Up @@ -2234,7 +2248,10 @@ impl<'a> Checker<'a> {
}
Expr::Call { callee, args, .. } => {
let call_res = self.shape_of_expr(st, inner);
if self.shape_may_share(&call_res) || self.expr_has_borrow_shape(st, inner) {
if self.shape_may_share(&call_res)
|| self.expr_has_borrow_shape(st, inner)
|| self.call_any_result_may_share(st, callee)
{
for (idx, a) in args.iter().enumerate() {
if !matches!(unwrap_parens(&a.value), Expr::Move { .. }) {
let arg_shape = self.shape_of_expr(st, &a.value);
Expand Down Expand Up @@ -8647,5 +8664,201 @@ fn main() {
"expected HYB-E3103 for parenthesized &(x) stored in struct field"
);
}

#[test]
fn multi_return_auto_addr_pointer_receiver_and_param_share_storage() {
let src_recv_define = "\
struct buf {
data [2]int
}

fn (b *buf) slice_pair() (int, []int) {
return 0, b.data[:]
}

fn worker(s []int, done chan bool) {
s[0] = 99
done <- true
}

fn main() {
b := buf{data: [2]int{1, 2}}
_, s := b.slice_pair()
done := make(chan bool, 1)
task worker(move s, done)
b.data[0] = 42
<-done
}
";
assert!(
check_codes(src_recv_define).iter().any(|c| c == "HYB-E3003"),
"expected HYB-E3003 for write to b after moving slice from multi-return *T receiver call"
);

let src_param_define = "\
struct buf {
data [2]int
}

fn take_slice_pair(b *buf) (int, []int) {
return 0, b.data[:]
}

fn worker(s []int, done chan bool) {
s[0] = 99
done <- true
}

fn main() {
b := buf{data: [2]int{1, 2}}
_, s := take_slice_pair(b)
done := make(chan bool, 1)
task worker(move s, done)
b.data[0] = 42
<-done
}
";
assert!(
check_codes(src_param_define).iter().any(|c| c == "HYB-E3003"),
"expected HYB-E3003 for write to b after moving slice from multi-return *T param call"
);

let src_holder_assign = "\
struct buf {
data [2]int
}

struct holder {
s []int
}

fn (b *buf) holder_pair() (int, holder) {
return 0, holder{s: b.data[:]}
}

fn worker(h holder, done chan bool) {
h.s[0] = 99
done <- true
}

fn main() {
b := buf{data: [2]int{1, 2}}
var code int
var h holder
code, h = b.holder_pair()
_ = code
done := make(chan bool, 1)
task worker(move h, done)
b.data[0] = 42
<-done
}
";
assert!(
check_codes(src_holder_assign).iter().any(|c| c == "HYB-E3003"),
"expected HYB-E3003 for multi-value assign from *T receiver call returning struct with slice"
);

let src_param_launder = "\
struct buf {
data [2]int
}

fn (b *buf) slice_pair() (int, []int) {
return 0, b.data[:]
}

fn worker(s []int, done chan bool) {
s[0] = 99
done <- true
}

fn launder(b buf, done chan bool) {
_, s := b.slice_pair()
task worker(move s, done)
}
";
assert!(
check_codes(src_param_launder).iter().any(|c| c == "HYB-E3004"),
"expected HYB-E3004 for laundering non-owned parameter b via multi-return *T receiver call"
);

let src_const_write = "\
struct buf {
data [2]int
}

fn (b *buf) slice_pair() (int, []int) {
return 0, b.data[:]
}

fn main() {
const cb = buf{data: [2]int{10, 20}}
_, s := cb.slice_pair()
s[0] = 77
}
";
assert!(
check_codes(src_const_write).iter().any(|c| c == "HYB-E3006"),
"expected HYB-E3006 for mutating const composite via multi-return *T receiver call"
);

let src_try_ptr_pair = "\
struct cell {
v int
}

fn (c *cell) ptr_pair_try() (int, *cell, error) {
return 0, c, nil
}

fn worker(p *cell, done chan bool) {
p.v = 99
done <- true
}

fn run() error {
c := cell{v: 1}
_, p := c.ptr_pair_try()?
done := make(chan bool, 1)
task worker(move p, done)
c.v = 42
<-done
return nil
}
";
assert!(
check_codes(src_try_ptr_pair).iter().any(|c| c == "HYB-E3003"),
"expected HYB-E3003 for write to c after moving pointer from ?-unwrapped multi-return *T receiver call"
);

let src_nested_try = "\
struct cell {
v int
}

fn (c *cell) ptr_pair_try() (int, *cell, error) {
return 0, c, nil
}

fn worker(p *cell, done chan bool) {
p.v = 99
done <- true
}

fn run() error {
c := cell{v: 1}
_, p := ((c.ptr_pair_try())?)
done := make(chan bool, 1)
task worker(move p, done)
c.v = 42
<-done
return nil
}
";
assert!(
check_codes(src_nested_try).iter().any(|c| c == "HYB-E3003"),
"expected HYB-E3003 for write to c after moving pointer from nested ?-unwrapped multi-return *T receiver call"
);
}
}

Loading