diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go index c78197896c39a44abf7f3ca1b75d03ea0d723853..997a830994f09bdd6118299e31e20125cadff3ff 100644 --- a/src/cmd/cgo/gcc.go +++ b/src/cmd/cgo/gcc.go @@ -29,7 +29,7 @@ import ( "unicode" "unicode/utf8" - "cmd/internal/str" + "cmd/internal/quoted" ) var debugDefine = flag.Bool("debug-define", false, "print relevant #defines") @@ -1568,7 +1568,7 @@ func checkGCCBaseCmd() ([]string, error) { if value == "" { value = defaultCC(goos, goarch) } - args, err := str.SplitQuotedFields(value) + args, err := quoted.Split(value) if err != nil { return nil, err } diff --git a/src/cmd/compile/internal/ssa/stmtlines_test.go b/src/cmd/compile/internal/ssa/stmtlines_test.go index 843db8c07ef409a3abdc485dfe9b4221f64fd4cc..90dd261c557fec7d5603cd7b3d3c5520f9382a19 100644 --- a/src/cmd/compile/internal/ssa/stmtlines_test.go +++ b/src/cmd/compile/internal/ssa/stmtlines_test.go @@ -2,7 +2,7 @@ package ssa_test import ( cmddwarf "cmd/internal/dwarf" - "cmd/internal/str" + "cmd/internal/quoted" "debug/dwarf" "debug/elf" "debug/macho" @@ -58,7 +58,7 @@ func TestStmtLines(t *testing.T) { if extld == "" { extld = "gcc" } - extldArgs, err := str.SplitQuotedFields(extld) + extldArgs, err := quoted.Split(extld) if err != nil { t.Fatal(err) } diff --git a/src/cmd/dist/buildtool.go b/src/cmd/dist/buildtool.go index 320c62f8505aa8bfaa68bb1d7bf81ac5c3d89207..75f04a975c92d492ef46591d10bef65ba2520b53 100644 --- a/src/cmd/dist/buildtool.go +++ b/src/cmd/dist/buildtool.go @@ -46,8 +46,8 @@ var bootstrapDirs = []string{ "cmd/internal/obj/...", "cmd/internal/objabi", "cmd/internal/pkgpath", + "cmd/internal/quoted", "cmd/internal/src", - "cmd/internal/str", "cmd/internal/sys", "cmd/link", "cmd/link/internal/...", diff --git a/src/cmd/go/internal/base/base.go b/src/cmd/go/internal/base/base.go index 0144525e307b0565b002c25b103dd9bc8108e8c0..954ce47a9899325014b4e99b5b6a1875a4120f30 100644 --- a/src/cmd/go/internal/base/base.go +++ b/src/cmd/go/internal/base/base.go @@ -17,7 +17,7 @@ import ( "sync" "cmd/go/internal/cfg" - "cmd/internal/str" + "cmd/go/internal/str" ) // A Command is an implementation of a go command diff --git a/src/cmd/go/internal/base/flag.go b/src/cmd/go/internal/base/flag.go index 7e5121bffbe8e37c34ae472edb44ea153e4e149f..2c72c7e562b7333be43813553321469a555b44a0 100644 --- a/src/cmd/go/internal/base/flag.go +++ b/src/cmd/go/internal/base/flag.go @@ -9,7 +9,7 @@ import ( "cmd/go/internal/cfg" "cmd/go/internal/fsys" - "cmd/internal/str" + "cmd/internal/quoted" ) // A StringsFlag is a command-line flag that interprets its argument @@ -18,7 +18,7 @@ type StringsFlag []string func (v *StringsFlag) Set(s string) error { var err error - *v, err = str.SplitQuotedFields(s) + *v, err = quoted.Split(s) if *v == nil { *v = []string{} } diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go index 181d2a2ca1f49734a8c94f88983d6ade8da9f367..e56dd8223f0519e90e9c89cd8e576d5f227becc5 100644 --- a/src/cmd/go/internal/envcmd/env.go +++ b/src/cmd/go/internal/envcmd/env.go @@ -26,7 +26,7 @@ import ( "cmd/go/internal/load" "cmd/go/internal/modload" "cmd/go/internal/work" - "cmd/internal/str" + "cmd/internal/quoted" ) var CmdEnv = &base.Command{ @@ -470,7 +470,7 @@ func checkEnvWrite(key, val string) error { if val == "" { break } - args, err := str.SplitQuotedFields(val) + args, err := quoted.Split(val) if err != nil { return fmt.Errorf("invalid %s: %v", key, err) } diff --git a/src/cmd/go/internal/fix/fix.go b/src/cmd/go/internal/fix/fix.go index cc5940fccd8f27672a3c202b2b06a322a40b820d..988d45e71ccfe2ccf571e409962bdd3ade4e3430 100644 --- a/src/cmd/go/internal/fix/fix.go +++ b/src/cmd/go/internal/fix/fix.go @@ -10,7 +10,7 @@ import ( "cmd/go/internal/cfg" "cmd/go/internal/load" "cmd/go/internal/modload" - "cmd/internal/str" + "cmd/go/internal/str" "context" "fmt" "os" diff --git a/src/cmd/go/internal/generate/generate.go b/src/cmd/go/internal/generate/generate.go index 5981e5ecdbeb6c391dee10cded5ffaf01a3714d4..a3873d11387c8195f0de88a887f1c4ead93ce3ed 100644 --- a/src/cmd/go/internal/generate/generate.go +++ b/src/cmd/go/internal/generate/generate.go @@ -26,7 +26,7 @@ import ( "cmd/go/internal/load" "cmd/go/internal/modload" "cmd/go/internal/work" - "cmd/internal/str" + "cmd/go/internal/str" ) var CmdGenerate = &base.Command{ diff --git a/src/cmd/go/internal/get/get.go b/src/cmd/go/internal/get/get.go index 0412506b9e87531fcac36adb8f84b60dc13abfbf..f46313dcff605434dfc4902e8fe91a59f1acf587 100644 --- a/src/cmd/go/internal/get/get.go +++ b/src/cmd/go/internal/get/get.go @@ -20,7 +20,7 @@ import ( "cmd/go/internal/vcs" "cmd/go/internal/web" "cmd/go/internal/work" - "cmd/internal/str" + "cmd/go/internal/str" "golang.org/x/mod/module" ) diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go index 821e622abbed0eea16775cbbee0381a08c8b70e1..8c85ddcf21163e94b0dad71b61801f492195080c 100644 --- a/src/cmd/go/internal/list/list.go +++ b/src/cmd/go/internal/list/list.go @@ -24,7 +24,7 @@ import ( "cmd/go/internal/modinfo" "cmd/go/internal/modload" "cmd/go/internal/work" - "cmd/internal/str" + "cmd/go/internal/str" ) var CmdList = &base.Command{ diff --git a/src/cmd/go/internal/load/flag.go b/src/cmd/go/internal/load/flag.go index d0d5716c3fc2dcd4a77a5dbc323e7710dfc38125..de079decdf2541c6f7ac312e002aec761427bfbc 100644 --- a/src/cmd/go/internal/load/flag.go +++ b/src/cmd/go/internal/load/flag.go @@ -6,7 +6,7 @@ package load import ( "cmd/go/internal/base" - "cmd/internal/str" + "cmd/internal/quoted" "fmt" "strings" ) @@ -63,7 +63,7 @@ func (f *PerPackageFlag) set(v, cwd string) error { match = MatchPackage(pattern, cwd) v = v[i+1:] } - flags, err := str.SplitQuotedFields(v) + flags, err := quoted.Split(v) if err != nil { return err } diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index dfe7849516a48b26f12e205476285bfef9ba7eb3..c6c5fb00a8db54c288b7c80deb6f8d27e4b86ba1 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -38,9 +38,9 @@ import ( "cmd/go/internal/modload" "cmd/go/internal/par" "cmd/go/internal/search" + "cmd/go/internal/str" "cmd/go/internal/trace" "cmd/go/internal/vcs" - "cmd/internal/str" "cmd/internal/sys" "golang.org/x/mod/modfile" diff --git a/src/cmd/go/internal/load/test.go b/src/cmd/go/internal/load/test.go index 4cefb62d51ac2f0ac6cf1fe02859d7064080b4b5..8a18dfbe931c9b2416ee142fbc8793859d9d9d1b 100644 --- a/src/cmd/go/internal/load/test.go +++ b/src/cmd/go/internal/load/test.go @@ -23,7 +23,7 @@ import ( "cmd/go/internal/fsys" "cmd/go/internal/trace" - "cmd/internal/str" + "cmd/go/internal/str" ) var TestMainDeps = []string{ diff --git a/src/cmd/go/internal/modcmd/vendor.go b/src/cmd/go/internal/modcmd/vendor.go index 57189b4607fa69e183fee5ad6a668358044fec8b..484e095cc764c2d4955b02b625280733882e74d2 100644 --- a/src/cmd/go/internal/modcmd/vendor.go +++ b/src/cmd/go/internal/modcmd/vendor.go @@ -24,7 +24,7 @@ import ( "cmd/go/internal/imports" "cmd/go/internal/load" "cmd/go/internal/modload" - "cmd/internal/str" + "cmd/go/internal/str" "golang.org/x/mod/module" "golang.org/x/mod/semver" diff --git a/src/cmd/go/internal/modfetch/codehost/codehost.go b/src/cmd/go/internal/modfetch/codehost/codehost.go index efb4b1516a2ef48874a19c4fc240119bc1a84ec1..378fbae34f9530378ced67a8298f797891af9e72 100644 --- a/src/cmd/go/internal/modfetch/codehost/codehost.go +++ b/src/cmd/go/internal/modfetch/codehost/codehost.go @@ -21,7 +21,7 @@ import ( "cmd/go/internal/cfg" "cmd/go/internal/lockedfile" - "cmd/internal/str" + "cmd/go/internal/str" ) // Downloaded size limits. diff --git a/src/cmd/go/internal/modfetch/codehost/vcs.go b/src/cmd/go/internal/modfetch/codehost/vcs.go index 5d810d2621c907614df4d02da47a015b0f937e6c..c2cca084e3077a8b3f9627e8b212491c1baf06dd 100644 --- a/src/cmd/go/internal/modfetch/codehost/vcs.go +++ b/src/cmd/go/internal/modfetch/codehost/vcs.go @@ -20,7 +20,7 @@ import ( "cmd/go/internal/lockedfile" "cmd/go/internal/par" - "cmd/internal/str" + "cmd/go/internal/str" ) // A VCSError indicates an error using a version control system. diff --git a/src/cmd/go/internal/modget/query.go b/src/cmd/go/internal/modget/query.go index d7341e7813dd6ea12aa7f96bfebaade626c7c8d5..887cb51b317f7f3c46db33192452197571467a09 100644 --- a/src/cmd/go/internal/modget/query.go +++ b/src/cmd/go/internal/modget/query.go @@ -14,7 +14,7 @@ import ( "cmd/go/internal/base" "cmd/go/internal/modload" "cmd/go/internal/search" - "cmd/internal/str" + "cmd/go/internal/str" "golang.org/x/mod/module" ) diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go index 0f5b0150003b7027495c9f2dd4daad5402d220a3..845bf2f8a2323ac1beff39fc2adcb4acfb52e7fd 100644 --- a/src/cmd/go/internal/modload/load.go +++ b/src/cmd/go/internal/modload/load.go @@ -119,7 +119,7 @@ import ( "cmd/go/internal/mvs" "cmd/go/internal/par" "cmd/go/internal/search" - "cmd/internal/str" + "cmd/go/internal/str" "golang.org/x/mod/module" "golang.org/x/mod/semver" diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go index c9ed129dbfaa6cb2472fc0f35854cb842cdc813a..1eb484de9d0b8796e4bbe9a6ecf37340399b1dfd 100644 --- a/src/cmd/go/internal/modload/query.go +++ b/src/cmd/go/internal/modload/query.go @@ -22,7 +22,7 @@ import ( "cmd/go/internal/modfetch" "cmd/go/internal/search" "cmd/go/internal/trace" - "cmd/internal/str" + "cmd/go/internal/str" "golang.org/x/mod/module" "golang.org/x/mod/semver" diff --git a/src/cmd/go/internal/run/run.go b/src/cmd/go/internal/run/run.go index 11e2c81b9aa53fec1c94c8b4981efe434630a20c..03895d27ebf2d68419ca0200c3ae79c139dcfa01 100644 --- a/src/cmd/go/internal/run/run.go +++ b/src/cmd/go/internal/run/run.go @@ -19,7 +19,7 @@ import ( "cmd/go/internal/load" "cmd/go/internal/modload" "cmd/go/internal/work" - "cmd/internal/str" + "cmd/go/internal/str" ) var CmdRun = &base.Command{ diff --git a/src/cmd/internal/str/path.go b/src/cmd/go/internal/str/path.go similarity index 100% rename from src/cmd/internal/str/path.go rename to src/cmd/go/internal/str/path.go diff --git a/src/cmd/go/internal/str/str.go b/src/cmd/go/internal/str/str.go new file mode 100644 index 0000000000000000000000000000000000000000..5bc521b9df46874b6f7a93d8d80ec1a4279f53bf --- /dev/null +++ b/src/cmd/go/internal/str/str.go @@ -0,0 +1,111 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package str provides string manipulation utilities. +package str + +import ( + "bytes" + "fmt" + "unicode" + "unicode/utf8" +) + +// StringList flattens its arguments into a single []string. +// Each argument in args must have type string or []string. +func StringList(args ...interface{}) []string { + var x []string + for _, arg := range args { + switch arg := arg.(type) { + case []string: + x = append(x, arg...) + case string: + x = append(x, arg) + default: + panic("stringList: invalid argument of type " + fmt.Sprintf("%T", arg)) + } + } + 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 +// do not 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 "", "" +} + +// Contains reports whether x contains s. +func Contains(x []string, s string) bool { + for _, t := range x { + if t == s { + return true + } + } + return false +} + +// Uniq removes consecutive duplicate strings from ss. +func Uniq(ss *[]string) { + if len(*ss) <= 1 { + return + } + uniq := (*ss)[:1] + for _, s := range *ss { + if s != uniq[len(uniq)-1] { + uniq = append(uniq, s) + } + } + *ss = uniq +} diff --git a/src/cmd/go/internal/str/str_test.go b/src/cmd/go/internal/str/str_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8ea758e0a8b64bf0c8e4314660f9b79050fb4383 --- /dev/null +++ b/src/cmd/go/internal/str/str_test.go @@ -0,0 +1,29 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package str + +import ( + "testing" +) + +var foldDupTests = []struct { + list []string + f1, f2 string +}{ + {StringList("math/rand", "math/big"), "", ""}, + {StringList("math", "strings"), "", ""}, + {StringList("strings"), "", ""}, + {StringList("strings", "strings"), "strings", "strings"}, + {StringList("Rand", "rand", "math", "math/rand", "math/Rand"), "Rand", "rand"}, +} + +func TestFoldDup(t *testing.T) { + for _, tt := range foldDupTests { + f1, f2 := FoldDup(tt.list) + if f1 != tt.f1 || f2 != tt.f2 { + t.Errorf("foldDup(%q) = %q, %q, want %q, %q", tt.list, f1, f2, tt.f1, tt.f2) + } + } +} diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go index dc1bea505bda24ebd09183d9ae0fa879aecaf4e9..ea1d4ff20e9ff15d7edb5ed8c5c5a32e91cf463a 100644 --- a/src/cmd/go/internal/test/test.go +++ b/src/cmd/go/internal/test/test.go @@ -33,7 +33,7 @@ import ( "cmd/go/internal/search" "cmd/go/internal/trace" "cmd/go/internal/work" - "cmd/internal/str" + "cmd/go/internal/str" "cmd/internal/test2json" ) diff --git a/src/cmd/go/internal/vcs/vcs.go b/src/cmd/go/internal/vcs/vcs.go index 941bd57147e244ecd3e84c31f28f057cc1a1b528..c4853d7ae31d1b40a8b0d1287b021b60045393b9 100644 --- a/src/cmd/go/internal/vcs/vcs.go +++ b/src/cmd/go/internal/vcs/vcs.go @@ -27,7 +27,7 @@ import ( "cmd/go/internal/cfg" "cmd/go/internal/search" "cmd/go/internal/web" - "cmd/internal/str" + "cmd/go/internal/str" "golang.org/x/mod/module" ) diff --git a/src/cmd/go/internal/work/buildid.go b/src/cmd/go/internal/work/buildid.go index 15f944d2af219e398a59b68ae1ab3f0a5edb6690..d4f2a716d7b98c76dabbc5ff0e592a2adf7f04ee 100644 --- a/src/cmd/go/internal/work/buildid.go +++ b/src/cmd/go/internal/work/buildid.go @@ -16,7 +16,7 @@ import ( "cmd/go/internal/cfg" "cmd/go/internal/fsys" "cmd/internal/buildid" - "cmd/internal/str" + "cmd/go/internal/str" ) // Build IDs diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index 62d814382881afbca28960b2818faae3fbdbd896..03f8866cf2cc210e9840d420fee0adeea926f1cc 100644 --- a/src/cmd/go/internal/work/exec.go +++ b/src/cmd/go/internal/work/exec.go @@ -34,8 +34,9 @@ import ( "cmd/go/internal/fsys" "cmd/go/internal/load" "cmd/go/internal/modload" + "cmd/go/internal/str" "cmd/go/internal/trace" - "cmd/internal/str" + "cmd/internal/quoted" "cmd/internal/sys" ) @@ -2666,7 +2667,7 @@ func envList(key, def string) []string { if v == "" { v = def } - args, err := str.SplitQuotedFields(v) + args, err := quoted.Split(v) if err != nil { panic(fmt.Sprintf("could not parse environment variable %s with value %q: %v", key, v, err)) } diff --git a/src/cmd/go/internal/work/gc.go b/src/cmd/go/internal/work/gc.go index 3eb9b35f40828f9010cfb63ca0f6aa3ab5079531..e3b4a817e782c0c7e30e7d0dd8713d920c1a5a0a 100644 --- a/src/cmd/go/internal/work/gc.go +++ b/src/cmd/go/internal/work/gc.go @@ -20,8 +20,9 @@ import ( "cmd/go/internal/cfg" "cmd/go/internal/fsys" "cmd/go/internal/load" + "cmd/go/internal/str" "cmd/internal/objabi" - "cmd/internal/str" + "cmd/internal/quoted" "cmd/internal/sys" "crypto/sha1" ) @@ -565,7 +566,7 @@ func setextld(ldflags []string, compiler []string) ([]string, error) { return ldflags, nil } } - joined, err := str.JoinAndQuoteFields(compiler) + joined, err := quoted.Join(compiler) if err != nil { return nil, err } diff --git a/src/cmd/go/internal/work/gccgo.go b/src/cmd/go/internal/work/gccgo.go index 3cb7b641833a48db0acf35f4a31697d336a9df16..60181b99e471d409b91408ccf26a7ff891cd89b2 100644 --- a/src/cmd/go/internal/work/gccgo.go +++ b/src/cmd/go/internal/work/gccgo.go @@ -17,7 +17,7 @@ import ( "cmd/go/internal/fsys" "cmd/go/internal/load" "cmd/internal/pkgpath" - "cmd/internal/str" + "cmd/go/internal/str" ) // The Gccgo toolchain. diff --git a/src/cmd/go/internal/work/init.go b/src/cmd/go/internal/work/init.go index 56e39f8c52a071510e3cb057ae1cbbb5d1f3f9c5..4dbbd2a13f2b4659e27ca880d55ff7c708fe3873 100644 --- a/src/cmd/go/internal/work/init.go +++ b/src/cmd/go/internal/work/init.go @@ -11,7 +11,7 @@ import ( "cmd/go/internal/cfg" "cmd/go/internal/fsys" "cmd/go/internal/modload" - "cmd/internal/str" + "cmd/internal/quoted" "cmd/internal/sys" "fmt" "os" @@ -46,7 +46,7 @@ func BuildInit() { // Make sure CC, CXX, and FC are absolute paths. for _, key := range []string{"CC", "CXX", "FC"} { value := cfg.Getenv(key) - args, err := str.SplitQuotedFields(value) + args, err := quoted.Split(value) if err != nil { base.Fatalf("go: %s environment variable could not be parsed: %v", key, err) } diff --git a/src/cmd/internal/quoted/quoted.go b/src/cmd/internal/quoted/quoted.go new file mode 100644 index 0000000000000000000000000000000000000000..e7575dfc669c5e0601b08c9837449423942b45e6 --- /dev/null +++ b/src/cmd/internal/quoted/quoted.go @@ -0,0 +1,127 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package quoted provides string manipulation utilities. +package quoted + +import ( + "flag" + "fmt" + "strings" + "unicode" +) + +func isSpaceByte(c byte) bool { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' +} + +// Split splits s into a list of fields, +// allowing single or double quotes around elements. +// There is no unescaping or other processing within +// quoted fields. +func Split(s string) ([]string, error) { + // Split fields allowing '' or "" around elements. + // Quotes further inside the string do not count. + var f []string + for len(s) > 0 { + for len(s) > 0 && isSpaceByte(s[0]) { + s = s[1:] + } + if len(s) == 0 { + break + } + // Accepted quoted string. No unescaping inside. + if s[0] == '"' || s[0] == '\'' { + quote := s[0] + s = s[1:] + i := 0 + for i < len(s) && s[i] != quote { + i++ + } + if i >= len(s) { + return nil, fmt.Errorf("unterminated %c string", quote) + } + f = append(f, s[:i]) + s = s[i+1:] + continue + } + i := 0 + for i < len(s) && !isSpaceByte(s[i]) { + i++ + } + f = append(f, s[:i]) + s = s[i:] + } + return f, nil +} + +// Join joins a list of arguments into a string that can be parsed +// with Split. Arguments are quoted only if necessary; arguments +// without spaces or quotes are kept as-is. No argument may contain both +// single and double quotes. +func Join(args []string) (string, error) { + var buf []byte + for i, arg := range args { + if i > 0 { + buf = append(buf, ' ') + } + var sawSpace, sawSingleQuote, sawDoubleQuote bool + for _, c := range arg { + switch { + case c > unicode.MaxASCII: + continue + case isSpaceByte(byte(c)): + sawSpace = true + case c == '\'': + sawSingleQuote = true + case c == '"': + sawDoubleQuote = true + } + } + switch { + case !sawSpace && !sawSingleQuote && !sawDoubleQuote: + buf = append(buf, []byte(arg)...) + + case !sawSingleQuote: + buf = append(buf, '\'') + buf = append(buf, []byte(arg)...) + buf = append(buf, '\'') + + case !sawDoubleQuote: + buf = append(buf, '"') + buf = append(buf, []byte(arg)...) + buf = append(buf, '"') + + default: + return "", fmt.Errorf("argument %q contains both single and double quotes and cannot be quoted", arg) + } + } + return string(buf), nil +} + +// A Flag parses a list of string arguments encoded with Join. +// It is useful for flags like cmd/link's -extldflags. +type Flag []string + +var _ flag.Value = (*Flag)(nil) + +func (f *Flag) Set(v string) error { + fs, err := Split(v) + if err != nil { + return err + } + *f = fs[:len(fs):len(fs)] + return nil +} + +func (f *Flag) String() string { + if f == nil { + return "" + } + s, err := Join(*f) + if err != nil { + return strings.Join(*f, " ") + } + return s +} diff --git a/src/cmd/internal/str/str_test.go b/src/cmd/internal/quoted/quoted_test.go similarity index 79% rename from src/cmd/internal/str/str_test.go rename to src/cmd/internal/quoted/quoted_test.go index 3609af6a06df48874ba647df51b62e4d85fda873..d76270c87b493e31907b155d3ff9a1695205e81e 100644 --- a/src/cmd/internal/str/str_test.go +++ b/src/cmd/internal/quoted/quoted_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package str +package quoted import ( "reflect" @@ -10,27 +10,7 @@ import ( "testing" ) -var foldDupTests = []struct { - list []string - f1, f2 string -}{ - {StringList("math/rand", "math/big"), "", ""}, - {StringList("math", "strings"), "", ""}, - {StringList("strings"), "", ""}, - {StringList("strings", "strings"), "strings", "strings"}, - {StringList("Rand", "rand", "math", "math/rand", "math/Rand"), "Rand", "rand"}, -} - -func TestFoldDup(t *testing.T) { - for _, tt := range foldDupTests { - f1, f2 := FoldDup(tt.list) - if f1 != tt.f1 || f2 != tt.f2 { - t.Errorf("foldDup(%q) = %q, %q, want %q, %q", tt.list, f1, f2, tt.f1, tt.f2) - } - } -} - -func TestSplitQuotedFields(t *testing.T) { +func TestSplit(t *testing.T) { for _, test := range []struct { name string value string @@ -54,7 +34,7 @@ func TestSplitQuotedFields(t *testing.T) { {name: "quote_unclosed", value: `'a`, wantErr: "unterminated ' string"}, } { t.Run(test.name, func(t *testing.T) { - got, err := SplitQuotedFields(test.value) + got, err := Split(test.value) if err != nil { if test.wantErr == "" { t.Fatalf("unexpected error: %v", err) @@ -73,7 +53,7 @@ func TestSplitQuotedFields(t *testing.T) { } } -func TestJoinAndQuoteFields(t *testing.T) { +func TestJoin(t *testing.T) { for _, test := range []struct { name string args []string @@ -88,7 +68,7 @@ func TestJoinAndQuoteFields(t *testing.T) { {name: "unquoteable", args: []string{`'"`}, wantErr: "contains both single and double quotes and cannot be quoted"}, } { t.Run(test.name, func(t *testing.T) { - got, err := JoinAndQuoteFields(test.args) + got, err := Join(test.args) if err != nil { if test.wantErr == "" { t.Fatalf("unexpected error: %v", err) diff --git a/src/cmd/internal/str/str.go b/src/cmd/internal/str/str.go deleted file mode 100644 index 409cf8f7b4b3a5ed1add20ba40af06ffbcee4867..0000000000000000000000000000000000000000 --- a/src/cmd/internal/str/str.go +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package str provides string manipulation utilities. -package str - -import ( - "bytes" - "flag" - "fmt" - "strings" - "unicode" - "unicode/utf8" -) - -// StringList flattens its arguments into a single []string. -// Each argument in args must have type string or []string. -func StringList(args ...interface{}) []string { - var x []string - for _, arg := range args { - switch arg := arg.(type) { - case []string: - x = append(x, arg...) - case string: - x = append(x, arg) - default: - panic("stringList: invalid argument of type " + fmt.Sprintf("%T", arg)) - } - } - 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 -// do not 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 "", "" -} - -// Contains reports whether x contains s. -func Contains(x []string, s string) bool { - for _, t := range x { - if t == s { - return true - } - } - return false -} - -// Uniq removes consecutive duplicate strings from ss. -func Uniq(ss *[]string) { - if len(*ss) <= 1 { - return - } - uniq := (*ss)[:1] - for _, s := range *ss { - if s != uniq[len(uniq)-1] { - uniq = append(uniq, s) - } - } - *ss = uniq -} - -func isSpaceByte(c byte) bool { - return c == ' ' || c == '\t' || c == '\n' || c == '\r' -} - -// SplitQuotedFields splits s into a list of fields, -// allowing single or double quotes around elements. -// There is no unescaping or other processing within -// quoted fields. -func SplitQuotedFields(s string) ([]string, error) { - // Split fields allowing '' or "" around elements. - // Quotes further inside the string do not count. - var f []string - for len(s) > 0 { - for len(s) > 0 && isSpaceByte(s[0]) { - s = s[1:] - } - if len(s) == 0 { - break - } - // Accepted quoted string. No unescaping inside. - if s[0] == '"' || s[0] == '\'' { - quote := s[0] - s = s[1:] - i := 0 - for i < len(s) && s[i] != quote { - i++ - } - if i >= len(s) { - return nil, fmt.Errorf("unterminated %c string", quote) - } - f = append(f, s[:i]) - s = s[i+1:] - continue - } - i := 0 - for i < len(s) && !isSpaceByte(s[i]) { - i++ - } - f = append(f, s[:i]) - s = s[i:] - } - return f, nil -} - -// JoinAndQuoteFields joins a list of arguments into a string that can be parsed -// with SplitQuotedFields. Arguments are quoted only if necessary; arguments -// without spaces or quotes are kept as-is. No argument may contain both -// single and double quotes. -func JoinAndQuoteFields(args []string) (string, error) { - var buf []byte - for i, arg := range args { - if i > 0 { - buf = append(buf, ' ') - } - var sawSpace, sawSingleQuote, sawDoubleQuote bool - for _, c := range arg { - switch { - case c > unicode.MaxASCII: - continue - case isSpaceByte(byte(c)): - sawSpace = true - case c == '\'': - sawSingleQuote = true - case c == '"': - sawDoubleQuote = true - } - } - switch { - case !sawSpace && !sawSingleQuote && !sawDoubleQuote: - buf = append(buf, []byte(arg)...) - - case !sawSingleQuote: - buf = append(buf, '\'') - buf = append(buf, []byte(arg)...) - buf = append(buf, '\'') - - case !sawDoubleQuote: - buf = append(buf, '"') - buf = append(buf, []byte(arg)...) - buf = append(buf, '"') - - default: - return "", fmt.Errorf("argument %q contains both single and double quotes and cannot be quoted", arg) - } - } - return string(buf), nil -} - -// A QuotedStringListFlag parses a list of string arguments encoded with -// JoinAndQuoteFields. It is useful for flags like cmd/link's -extldflags. -type QuotedStringListFlag []string - -var _ flag.Value = (*QuotedStringListFlag)(nil) - -func (f *QuotedStringListFlag) Set(v string) error { - fs, err := SplitQuotedFields(v) - if err != nil { - return err - } - *f = fs[:len(fs):len(fs)] - return nil -} - -func (f *QuotedStringListFlag) String() string { - if f == nil { - return "" - } - s, err := JoinAndQuoteFields(*f) - if err != nil { - return strings.Join(*f, " ") - } - return s -} diff --git a/src/cmd/link/dwarf_test.go b/src/cmd/link/dwarf_test.go index f7bbb014d9e48e446c8c611516eddbc82472cb3e..78ef3cfe9713691cbb053a8e180cff02dcd66820 100644 --- a/src/cmd/link/dwarf_test.go +++ b/src/cmd/link/dwarf_test.go @@ -8,7 +8,7 @@ import ( "bytes" cmddwarf "cmd/internal/dwarf" "cmd/internal/objfile" - "cmd/internal/str" + "cmd/internal/quoted" "debug/dwarf" "internal/testenv" "os" @@ -68,7 +68,7 @@ func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) if extld == "" { extld = "gcc" } - extldArgs, err := str.SplitQuotedFields(extld) + extldArgs, err := quoted.Split(extld) if err != nil { t.Fatal(err) } diff --git a/src/cmd/link/internal/ld/main.go b/src/cmd/link/internal/ld/main.go index a5a5a71250fe92b592b8d4beca01c5b932c85512..a1d86965e434bb99f6eb1e68522debd3fd07f0c1 100644 --- a/src/cmd/link/internal/ld/main.go +++ b/src/cmd/link/internal/ld/main.go @@ -34,7 +34,7 @@ import ( "bufio" "cmd/internal/goobj" "cmd/internal/objabi" - "cmd/internal/str" + "cmd/internal/quoted" "cmd/internal/sys" "cmd/link/internal/benchmark" "flag" @@ -76,8 +76,8 @@ var ( flagLibGCC = flag.String("libgcc", "", "compiler support lib for internal linking; use \"none\" to disable") flagTmpdir = flag.String("tmpdir", "", "use `directory` for temporary files") - flagExtld str.QuotedStringListFlag - flagExtldflags str.QuotedStringListFlag + flagExtld quoted.Flag + flagExtldflags quoted.Flag flagExtar = flag.String("extar", "", "archive program for buildmode=c-archive") flagA = flag.Bool("a", false, "no-op (deprecated)")