From: Russ Cox Date: Fri, 15 Feb 2013 19:39:39 +0000 (-0500) Subject: cmd/go: reject case-insensitive file name, import collisions X-Git-Tag: go1.1rc2~997 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=2d4164596f3bd798996732aaa01b95e70f91e8a8;p=gostls13.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 --- diff --git a/src/cmd/go/main.go b/src/cmd/go/main.go index 10513d7235..9abe5913b0 100644 --- a/src/cmd/go/main.go +++ b/src/cmd/go/main.go @@ -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 "", "" +} diff --git a/src/cmd/go/pkg.go b/src/cmd/go/pkg.go index 793a43da8f..7fc61fd862 100644 --- a/src/cmd/go/pkg.go +++ b/src/cmd/go/pkg.go @@ -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 } diff --git a/src/cmd/go/test.bash b/src/cmd/go/test.bash index 22bada529c..460061a11a 100755 --- a/src/cmd/go/test.bash +++ b/src/cmd/go/test.bash @@ -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 <$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 <$d/src/example/b/FILE.go <$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