Skip to content

Commit

Permalink
checker: add an interface check for mutability, fixes #1081, fixes #7038
Browse files Browse the repository at this point in the history
  • Loading branch information
alehander92 committed Oct 11, 2021
1 parent d0c961e commit 0386f2b
Show file tree
Hide file tree
Showing 40 changed files with 218 additions and 91 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -336,7 +336,7 @@ jobs:

- name: Build V UI examples
run: |
git clone --depth 1 https://github.com/vlang/ui
git clone --depth 1 https://github.com/vlang/ui
cd ui
mkdir -p ~/.vmodules
ln -s $(pwd) ~/.vmodules/ui
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/vinix-kernel.yml
Expand Up @@ -20,7 +20,8 @@ jobs:
- name: Build V
run: make
- name: Clone current Vinix
run: git clone https://github.com/vlang/vinix.git --depth=1
run: |
git clone https://github.com/vlang/vinix.git --depth 1
- name: Clone current mlibc
run: git clone https://github.com/managarm/mlibc.git --depth=1
- name: Patch mlibc for Vinix
Expand Down
22 changes: 15 additions & 7 deletions vlib/context/README.md
Expand Up @@ -39,9 +39,9 @@ fn example_with_cancel() {
// The callers of gen need to cancel the context once
// they are done consuming generated integers not to leak
// the internal routine started by gen.
gen := fn (ctx context.Context) chan int {
gen := fn (mut ctx context.Context) chan int {
dst := chan int{}
go fn (ctx context.Context, dst chan int) {
go fn (mut ctx context.Context, dst chan int) {
mut v := 0
ch := ctx.done()
for {
Expand All @@ -55,16 +55,20 @@ fn example_with_cancel() {
}
}
}
}(ctx, dst)
}(mut ctx, dst)
return dst
}
ctx, cancel := context.with_cancel(context.background())
mut background := context.background()
mut b := &background
mut ctx, cancel := context.with_cancel(mut b)
defer {
cancel()
}
ch := gen(ctx)
mut mut_ctx := ctx
mut ctx2 := &mut_ctx
ch := gen(mut ctx2)
for i in 0 .. 5 {
v := <-ch
assert i == v
Expand All @@ -87,7 +91,9 @@ const (
// function that it should abandon its work as soon as it gets to it.
fn example_with_deadline() {
dur := time.now().add(short_duration)
ctx, cancel := context.with_deadline(context.background(), dur)
mut background := context.background()
mut b := &background
mut ctx, cancel := context.with_deadline(mut b, dur)
defer {
// Even though ctx will be expired, it is good practice to call its
Expand Down Expand Up @@ -122,7 +128,9 @@ const (
fn example_with_timeout() {
// Pass a context with a timeout to tell a blocking function that it
// should abandon its work after the timeout elapses.
ctx, cancel := context.with_timeout(context.background(), short_duration)
mut background := context.background()
mut b := &background
mut ctx, cancel := context.with_timeout(mut b, short_duration)
defer {
cancel()
}
Expand Down
33 changes: 17 additions & 16 deletions vlib/context/_context.v
Expand Up @@ -43,6 +43,22 @@ pub interface Context {
// should be canceled. deadline returns none when no deadline is
// set. Successive calls to deadline return the same results.
deadline() ?time.Time
// Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with
// the same key returns the same result.
//
// Use context values only for request-scoped data that transits
// processes and API boundaries, not for passing optional parameters to
// functions.
//
// A key identifies a specific value in a Context. Functions that wish
// to store values in Context typically allocate a key in a global
// variable then use that key as the argument to context.with_value and
// Context.Value. A key can be any type that supports equality;
// packages should define keys as an unexported type to avoid
// collisions.
value(key Key) ?Any
str() string
// done returns a channel that's closed when work done on behalf of this
// context should be canceled. done may return nil if this context can
// never be canceled. Successive calls to done return the same value.
Expand All @@ -53,29 +69,14 @@ pub interface Context {
// with_deadline arranges for done to be closed when the deadline
// expires; with_timeout arranges for done to be closed when the timeout
// elapses.
mut:
done() chan int
// If done is not yet closed, err returns nil.
// If done is closed, err returns a non-nil error explaining why:
// canceled if the context was canceled
// or deadline_exceeded if the context's deadline passed.
// After err returns a non-nil error, successive calls to err return the same error.
err() IError
// Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with
// the same key returns the same result.
//
// Use context values only for request-scoped data that transits
// processes and API boundaries, not for passing optional parameters to
// functions.
//
// A key identifies a specific value in a Context. Functions that wish
// to store values in Context typically allocate a key in a global
// variable then use that key as the argument to context.with_value and
// Context.Value. A key can be any type that supports equality;
// packages should define keys as an unexported type to avoid
// collisions.
value(key Key) ?Any
str() string
}

// background returns an empty Context. It is never canceled, has no
Expand Down
27 changes: 15 additions & 12 deletions vlib/context/cancel.v
Expand Up @@ -12,12 +12,13 @@ pub type CancelFn = fn ()

pub interface Canceler {
id string
mut:
cancel(remove_from_parent bool, err IError)
done() chan int
}

[deprecated]
pub fn cancel(ctx Context) {
pub fn cancel(mut ctx Context) {
match mut ctx {
CancelContext {
ctx.cancel(true, canceled)
Expand Down Expand Up @@ -47,9 +48,9 @@ mut:
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
pub fn with_cancel(parent Context) (Context, CancelFn) {
pub fn with_cancel(mut parent Context) (Context, CancelFn) {
mut c := new_cancel_context(parent)
propagate_cancel(parent, c)
propagate_cancel(mut parent, mut c)
return Context(c), fn [mut c] () {
c.cancel(true, canceled)
}
Expand Down Expand Up @@ -116,18 +117,20 @@ fn (mut ctx CancelContext) cancel(remove_from_parent bool, err IError) {

for _, child in ctx.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
mut c := child
c.cancel(false, err)
}

ctx.children = map[string]Canceler{}
ctx.mutex.unlock()

if remove_from_parent {
remove_child(ctx.context, ctx)
mut cc := &ctx.context
remove_child(mut cc, ctx)
}
}

fn propagate_cancel(parent Context, child Canceler) {
fn propagate_cancel(mut parent Context, mut child Canceler) {
done := parent.done()
select {
_ := <-done {
Expand All @@ -136,15 +139,15 @@ fn propagate_cancel(parent Context, child Canceler) {
return
}
}
mut p := parent_cancel_context(parent) or {
go fn (parent Context, child Canceler) {
mut p := parent_cancel_context(mut parent) or {
go fn (mut parent Context, mut child Canceler) {
pdone := parent.done()
select {
_ := <-pdone {
child.cancel(false, parent.err())
}
}
}(parent, child)
}(mut parent, mut child)
return
}

Expand All @@ -162,7 +165,7 @@ fn propagate_cancel(parent Context, child Canceler) {
// parent.done() matches that CancelContext. (If not, the CancelContext
// has been wrapped in a custom implementation providing a
// different done channel, in which case we should not bypass it.)
fn parent_cancel_context(parent Context) ?&CancelContext {
fn parent_cancel_context(mut parent Context) ?&CancelContext {
done := parent.done()
if done.closed {
return none
Expand All @@ -181,7 +184,7 @@ fn parent_cancel_context(parent Context) ?&CancelContext {
}

// remove_child removes a context from its parent.
fn remove_child(parent Context, child Canceler) {
mut p := parent_cancel_context(parent) or { return }
fn remove_child(mut parent Context, child Canceler) {
mut p := parent_cancel_context(mut parent) or { return }
p.children.delete(child.id)
}
14 changes: 9 additions & 5 deletions vlib/context/cancel_test.v
Expand Up @@ -10,9 +10,9 @@ fn test_with_cancel() {
// The callers of gen need to cancel the context once
// they are done consuming generated integers not to leak
// the internal routine started by gen.
gen := fn (ctx context.Context) chan int {
gen := fn (mut ctx context.Context) chan int {
dst := chan int{}
go fn (ctx context.Context, dst chan int) {
go fn (mut ctx context.Context, dst chan int) {
mut v := 0
ch := ctx.done()
for {
Expand All @@ -26,16 +26,20 @@ fn test_with_cancel() {
}
}
}
}(ctx, dst)
}(mut ctx, dst)
return dst
}

ctx, cancel := context.with_cancel(context.background())
mut background := context.background()
mut b := &background
mut ctx, cancel := context.with_cancel(mut b)
defer {
cancel()
}

ch := gen(ctx)
mut mut_ctx := ctx
mut ctx2 := &mut_ctx
ch := gen(mut ctx2)
for i in 0 .. 5 {
v := <-ch
assert i == v
Expand Down
13 changes: 7 additions & 6 deletions vlib/context/deadline.v
Expand Up @@ -26,12 +26,12 @@ mut:
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
pub fn with_deadline(parent Context, d time.Time) (Context, CancelFn) {
pub fn with_deadline(mut parent Context, d time.Time) (Context, CancelFn) {
id := rand.uuid_v4()
if cur := parent.deadline() {
if cur < d {
// The current deadline is already sooner than the new one.
return with_cancel(parent)
return with_cancel(mut parent)
}
}
cancel_ctx := new_cancel_context(parent)
Expand All @@ -40,7 +40,7 @@ pub fn with_deadline(parent Context, d time.Time) (Context, CancelFn) {
deadline: d
id: id
}
propagate_cancel(parent, ctx)
propagate_cancel(mut parent, mut ctx)
dur := d - time.now()
if dur.nanoseconds() <= 0 {
ctx.cancel(true, deadline_exceeded) // deadline has already passed
Expand All @@ -64,8 +64,8 @@ pub fn with_deadline(parent Context, d time.Time) (Context, CancelFn) {
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete
pub fn with_timeout(parent Context, timeout time.Duration) (Context, CancelFn) {
return with_deadline(parent, time.now().add(timeout))
pub fn with_timeout(mut parent Context, timeout time.Duration) (Context, CancelFn) {
return with_deadline(mut parent, time.now().add(timeout))
}

pub fn (ctx &TimerContext) deadline() ?time.Time {
Expand All @@ -88,7 +88,8 @@ pub fn (mut ctx TimerContext) cancel(remove_from_parent bool, err IError) {
ctx.cancel_ctx.cancel(false, err)
if remove_from_parent {
// Remove this TimerContext from its parent CancelContext's children.
remove_child(ctx.cancel_ctx.context, ctx)
mut cc := &ctx.cancel_ctx.context
remove_child(mut cc, ctx)
}
}

Expand Down
8 changes: 6 additions & 2 deletions vlib/context/deadline_test.v
Expand Up @@ -10,7 +10,9 @@ const (
// function that it should abandon its work as soon as it gets to it.
fn test_with_deadline() {
dur := time.now().add(short_duration)
ctx, cancel := context.with_deadline(context.background(), dur)
mut background := context.background()
mut b := &background
mut ctx, cancel := context.with_deadline(mut b, dur)

defer {
// Even though ctx will be expired, it is good practice to call its
Expand All @@ -33,7 +35,9 @@ fn test_with_deadline() {
fn test_with_timeout() {
// Pass a context with a timeout to tell a blocking function that it
// should abandon its work after the timeout elapses.
ctx, cancel := context.with_timeout(context.background(), short_duration)
mut background := context.background()
mut b := &background
mut ctx, cancel := context.with_timeout(mut b, short_duration)
defer {
cancel()
}
Expand Down
4 changes: 2 additions & 2 deletions vlib/context/value.v
Expand Up @@ -37,11 +37,11 @@ pub fn (ctx &ValueContext) deadline() ?time.Time {
return ctx.context.deadline()
}

pub fn (ctx &ValueContext) done() chan int {
pub fn (mut ctx ValueContext) done() chan int {
return ctx.context.done()
}

pub fn (ctx &ValueContext) err() IError {
pub fn (mut ctx ValueContext) err() IError {
return ctx.context.err()
}

Expand Down
2 changes: 1 addition & 1 deletion vlib/io/io.v
Expand Up @@ -4,7 +4,7 @@ const (
buf_max_len = 1024
)

pub fn cp(src Reader, mut dst Writer) ? {
pub fn cp(mut src Reader, mut dst Writer) ? {
mut buf := []byte{len: io.buf_max_len}
for {
len := src.read(mut buf) or { break }
Expand Down
2 changes: 1 addition & 1 deletion vlib/io/io_cp_test.v
Expand Up @@ -8,6 +8,6 @@ fn test_cp() ? {
}
mut r := io.new_buffered_reader(reader: f)
mut stdout := os.stdout()
io.cp(r, mut stdout) ?
io.cp(mut r, mut stdout) ?
assert true
}
4 changes: 2 additions & 2 deletions vlib/io/io_test.v
Expand Up @@ -30,12 +30,12 @@ fn (mut w Writ) write(buf []byte) ?int {
}

fn test_copy() {
src := Buf{
mut src := Buf{
bytes: 'abcdefghij'.repeat(10).bytes()
}
mut dst := Writ{
bytes: []byte{}
}
io.cp(src, mut dst) or { assert false }
io.cp(mut src, mut dst) or { assert false }
assert dst.bytes == src.bytes
}

0 comments on commit 0386f2b

Please sign in to comment.