]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: reject case-insensitive file name, import collisions
authorRuss Cox <rsc@golang.org>
Fri, 15 Feb 2013 19:39:39 +0000 (14:39 -0500)
committerRuss Cox <rsc@golang.org>
Fri, 15 Feb 2013 19:39:39 +0000 (14:39 -0500)
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

src/cmd/go/main.go
src/cmd/go/pkg.go
src/cmd/go/test.bash

index 10513d723543903bf5b4b69c0b3a1cc42eb672d3..9abe5913b09baac9857b1541f756e43cc38fb0e8 100644 (file)
@@ -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 "", ""
+}
index 793a43da8fb593324bb646b92735b41976bce0f0..7fc61fd862827e1d0dc2d0b92b6e009e0805829b 100644 (file)
@@ -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
 }
 
index 22bada529ce8fa1be03fff9d4d0c744f86cd8b00..460061a11a3c140c1a00ea84950dfde9482e40a0 100755 (executable)
@@ -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