mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-04 10:57:50 +08:00
f001c783d5
Although there are no explicit writes to $paths in the test cases, there is one hidden write: when the first search on external commands is done. Since the internal cache of EnvPathList starts empty, EnvPathList.get() notices that it needs to fetch the environment to update itself. However, the update first modifies .cachedValue and then .cachedPaths. If another goroutine uses .get() in the middle, it would think that the cache is actually up to date and uses the outdated .cachedPaths, which is empty. EnvPathList.get() is now correctly guarded by a write lock. The offending test case has been uncommented as well.
136 lines
2.7 KiB
Go
136 lines
2.7 KiB
Go
package eval
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// Errors
|
|
var (
|
|
ErrCanOnlyAssignList = errors.New("can only assign compatible values")
|
|
ErrPathMustBeString = errors.New("path must be string")
|
|
ErrPathCannotContainColonZero = errors.New(`path cannot contain colon or \0`)
|
|
)
|
|
|
|
// EnvPathList is a variable whose value is constructed from an environment
|
|
// variable by splitting at colons. Changes to it are also propagated to the
|
|
// corresponding environment variable. Its elements cannot contain colons or
|
|
// \0; attempting to put colon or \0 in its elements will result in an error.
|
|
//
|
|
// EnvPathList implements both Value and Variable interfaces. It also satisfied
|
|
// ListLike.
|
|
type EnvPathList struct {
|
|
sync.RWMutex
|
|
envName string
|
|
cachedValue string
|
|
cachedPaths []string
|
|
}
|
|
|
|
var (
|
|
_ Variable = (*EnvPathList)(nil)
|
|
_ Value = (*EnvPathList)(nil)
|
|
_ ListLike = (*EnvPathList)(nil)
|
|
)
|
|
|
|
func (epl *EnvPathList) Get() Value {
|
|
return epl
|
|
}
|
|
|
|
func (epl *EnvPathList) Set(v Value) {
|
|
elemser, ok := v.(Elemser)
|
|
if !ok {
|
|
throw(ErrCanOnlyAssignList)
|
|
}
|
|
var paths []string
|
|
for v := range elemser.Elems() {
|
|
s, ok := v.(String)
|
|
if !ok {
|
|
throw(ErrPathMustBeString)
|
|
}
|
|
path := string(s)
|
|
if strings.ContainsAny(path, ":\x00") {
|
|
throw(ErrPathCannotContainColonZero)
|
|
}
|
|
paths = append(paths, string(s))
|
|
}
|
|
epl.set(paths)
|
|
}
|
|
|
|
func (epl *EnvPathList) Kind() string {
|
|
return "list"
|
|
}
|
|
|
|
func (epl *EnvPathList) Repr(indent int) string {
|
|
var b ListReprBuilder
|
|
b.Indent = indent
|
|
for _, path := range epl.get() {
|
|
b.WriteElem(quote(path))
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func (epl *EnvPathList) Len() int {
|
|
return len(epl.get())
|
|
}
|
|
|
|
func (epl *EnvPathList) Elems() <-chan Value {
|
|
ch := make(chan Value)
|
|
go func() {
|
|
for _, p := range epl.get() {
|
|
ch <- String(p)
|
|
}
|
|
close(ch)
|
|
}()
|
|
return ch
|
|
}
|
|
|
|
func (epl *EnvPathList) IndexOne(idx Value) Value {
|
|
paths := epl.get()
|
|
i := intIndexWithin(idx, len(paths))
|
|
return String(paths[i])
|
|
}
|
|
|
|
func (epl *EnvPathList) IndexSet(idx, v Value) {
|
|
s, ok := v.(String)
|
|
if !ok {
|
|
throw(ErrPathMustBeString)
|
|
}
|
|
|
|
paths := epl.get()
|
|
i := intIndexWithin(idx, len(paths))
|
|
|
|
epl.Lock()
|
|
defer epl.Unlock()
|
|
paths[i] = string(s)
|
|
epl.syncFromPaths()
|
|
}
|
|
|
|
func (epl *EnvPathList) get() []string {
|
|
epl.Lock()
|
|
defer epl.Unlock()
|
|
|
|
value := os.Getenv(epl.envName)
|
|
if value == epl.cachedValue {
|
|
return epl.cachedPaths
|
|
}
|
|
epl.cachedValue = value
|
|
epl.cachedPaths = strings.Split(value, ":")
|
|
return epl.cachedPaths
|
|
}
|
|
|
|
func (epl *EnvPathList) set(paths []string) {
|
|
epl.Lock()
|
|
defer epl.Unlock()
|
|
|
|
epl.cachedPaths = paths
|
|
epl.syncFromPaths()
|
|
}
|
|
|
|
func (epl *EnvPathList) syncFromPaths() {
|
|
epl.cachedValue = strings.Join(epl.cachedPaths, ":")
|
|
err := os.Setenv(epl.envName, epl.cachedValue)
|
|
maybeThrow(err)
|
|
}
|