mirror of https://github.com/golang/go.git
cmd/go: reject case-insensitive file name, import collisions
To make sure that Go code will work when moved to a system with a case-insensitive file system, like OS X or Windows, reject any package built from files with names differing only in case, and also any package built from imported dependencies with names differing only in case. Fixes #4773. R=golang-dev, iant CC=golang-dev https://golang.org/cl/7314104
This commit is contained in:
parent
61e02ee901
commit
2d4164596f
|
|
@ -589,3 +589,60 @@ func stringList(args ...interface{}) []string {
|
|||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// toFold returns a string with the property that
|
||||
// strings.EqualFold(s, t) iff toFold(s) == toFold(t)
|
||||
// This lets us test a large set of strings for fold-equivalent
|
||||
// duplicates without making a quadratic number of calls
|
||||
// to EqualFold. Note that strings.ToUpper and strings.ToLower
|
||||
// have the desired property in some corner cases.
|
||||
func toFold(s string) string {
|
||||
// Fast path: all ASCII, no upper case.
|
||||
// Most paths look like this already.
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if c >= utf8.RuneSelf || 'A' <= c && c <= 'Z' {
|
||||
goto Slow
|
||||
}
|
||||
}
|
||||
return s
|
||||
|
||||
Slow:
|
||||
var buf bytes.Buffer
|
||||
for _, r := range s {
|
||||
// SimpleFold(x) cycles to the next equivalent rune > x
|
||||
// or wraps around to smaller values. Iterate until it wraps,
|
||||
// and we've found the minimum value.
|
||||
for {
|
||||
r0 := r
|
||||
r = unicode.SimpleFold(r0)
|
||||
if r <= r0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Exception to allow fast path above: A-Z => a-z
|
||||
if 'A' <= r && r <= 'Z' {
|
||||
r += 'a' - 'A'
|
||||
}
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// foldDup reports a pair of strings from the list that are
|
||||
// equal according to strings.EqualFold.
|
||||
// It returns "", "" if there are no such strings.
|
||||
func foldDup(list []string) (string, string) {
|
||||
clash := map[string]string{}
|
||||
for _, s := range list {
|
||||
fold := toFold(s)
|
||||
if t := clash[fold]; t != "" {
|
||||
if s > t {
|
||||
s, t = t, s
|
||||
}
|
||||
return s, t
|
||||
}
|
||||
clash[fold] = s
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,6 +125,9 @@ func (p *PackageError) Error() string {
|
|||
// is the most important thing.
|
||||
return p.Pos + ": " + p.Err
|
||||
}
|
||||
if len(p.ImportStack) == 0 {
|
||||
return p.Err
|
||||
}
|
||||
return "package " + strings.Join(p.ImportStack, "\n\timports ") + ": " + p.Err
|
||||
}
|
||||
|
||||
|
|
@ -370,6 +373,31 @@ func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package
|
|||
p.allgofiles = append(p.allgofiles, p.gofiles...)
|
||||
sort.Strings(p.allgofiles)
|
||||
|
||||
// Check for case-insensitive collision of input files.
|
||||
// To avoid problems on case-insensitive files, we reject any package
|
||||
// where two different input files have equal names under a case-insensitive
|
||||
// comparison.
|
||||
f1, f2 := foldDup(stringList(
|
||||
p.GoFiles,
|
||||
p.CgoFiles,
|
||||
p.IgnoredGoFiles,
|
||||
p.CFiles,
|
||||
p.HFiles,
|
||||
p.SFiles,
|
||||
p.SysoFiles,
|
||||
p.SwigFiles,
|
||||
p.SwigCXXFiles,
|
||||
p.TestGoFiles,
|
||||
p.XTestGoFiles,
|
||||
))
|
||||
if f1 != "" {
|
||||
p.Error = &PackageError{
|
||||
ImportStack: stk.copy(),
|
||||
Err: fmt.Sprintf("case-insensitive file name collision: %q and %q", f1, f2),
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Build list of imported packages and full dependency list.
|
||||
imports := make([]*Package, 0, len(p.Imports))
|
||||
deps := make(map[string]bool)
|
||||
|
|
@ -423,8 +451,21 @@ func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package
|
|||
if p.Standard && (p.ImportPath == "unsafe" || buildContext.Compiler == "gccgo") {
|
||||
p.target = ""
|
||||
}
|
||||
|
||||
p.Target = p.target
|
||||
|
||||
// In the absence of errors lower in the dependency tree,
|
||||
// check for case-insensitive collisions of import paths.
|
||||
if len(p.DepsErrors) == 0 {
|
||||
dep1, dep2 := foldDup(p.Deps)
|
||||
if dep1 != "" {
|
||||
p.Error = &PackageError{
|
||||
ImportStack: stk.copy(),
|
||||
Err: fmt.Sprintf("case-insensitive import collision: %q and %q", dep1, dep2),
|
||||
}
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -279,6 +279,47 @@ fi
|
|||
unset GOPATH
|
||||
rm -rf $d
|
||||
|
||||
# issue 4773. case-insensitive collisions
|
||||
d=$(mktemp -d -t testgo)
|
||||
export GOPATH=$d
|
||||
mkdir -p $d/src/example/a $d/src/example/b
|
||||
cat >$d/src/example/a/a.go <<EOF
|
||||
package p
|
||||
import (
|
||||
_ "math/rand"
|
||||
_ "math/Rand"
|
||||
)
|
||||
EOF
|
||||
if ./testgo list example/a 2>$d/out; then
|
||||
echo go list example/a should have failed, did not.
|
||||
ok=false
|
||||
elif ! grep "case-insensitive import collision" $d/out >/dev/null; then
|
||||
echo go list example/a did not report import collision.
|
||||
ok=false
|
||||
fi
|
||||
cat >$d/src/example/b/file.go <<EOF
|
||||
package b
|
||||
EOF
|
||||
cat >$d/src/example/b/FILE.go <<EOF
|
||||
package b
|
||||
EOF
|
||||
if [ $(ls $d/src/example/b | wc -l) = 2 ]; then
|
||||
# case-sensitive file system, let directory read find both files
|
||||
args="example/b"
|
||||
else
|
||||
# case-insensitive file system, list files explicitly on command line.
|
||||
args="$d/src/example/b/file.go $d/src/example/b/FILE.go"
|
||||
fi
|
||||
if ./testgo list $args 2>$d/out; then
|
||||
echo go list example/b should have failed, did not.
|
||||
ok=false
|
||||
elif ! grep "case-insensitive file name collision" $d/out >/dev/null; then
|
||||
echo go list example/b did not report file name collision.
|
||||
ok=false
|
||||
fi
|
||||
unset GOPATH
|
||||
rm -rf $d
|
||||
|
||||
# Only succeeds if source order is preserved.
|
||||
./testgo test testdata/example[12]_test.go
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue