From: Bryan C. Mills Date: Fri, 1 Mar 2019 15:09:02 +0000 (-0500) Subject: cmd/go: quote expanded shell variables used within regular expressions X-Git-Tag: go1.13beta1~1274 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=b45f5b5e16c8176c909b441ebfa731cd6ff0cd63;p=gostls13.git cmd/go: quote expanded shell variables used within regular expressions We mostly use shell variables for paths, and we don't want file paths like "C:\work\go1.4" to turn into regular expressions. Updates #30228 Updates #30241 Change-Id: If18b775b2f8b2821eaf197c4be4a322066af839f Reviewed-on: https://go-review.googlesource.com/c/164626 Run-TryBot: Bryan C. Mills TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor Reviewed-by: Jay Conrod --- diff --git a/src/cmd/go/script_test.go b/src/cmd/go/script_test.go index e204471beb..48420daa1f 100644 --- a/src/cmd/go/script_test.go +++ b/src/cmd/go/script_test.go @@ -192,7 +192,7 @@ func (ts *testScript) run() { a, err := txtar.ParseFile(ts.file) ts.check(err) for _, f := range a.Files { - name := ts.mkabs(ts.expand(f.Name)) + name := ts.mkabs(ts.expand(f.Name, false)) ts.check(os.MkdirAll(filepath.Dir(name), 0777)) ts.check(ioutil.WriteFile(name, f.Data, 0666)) } @@ -238,34 +238,24 @@ Script: } // Parse input line. Ignore blanks entirely. - args := ts.parse(line) - if len(args) == 0 { + parsed := ts.parse(line) + if parsed.name == "" { + if parsed.neg || len(parsed.conds) > 0 { + ts.fatalf("missing command") + } continue } // Echo command to log. fmt.Fprintf(&ts.log, "> %s\n", line) - // Command prefix [cond] means only run this command if cond is satisfied. - for strings.HasPrefix(args[0], "[") && strings.HasSuffix(args[0], "]") { - cond := args[0] - cond = cond[1 : len(cond)-1] - cond = strings.TrimSpace(cond) - args = args[1:] - if len(args) == 0 { - ts.fatalf("missing command after condition") - } - want := true - if strings.HasPrefix(cond, "!") { - want = false - cond = strings.TrimSpace(cond[1:]) - } + for _, cond := range parsed.conds { // Known conds are: $GOOS, $GOARCH, runtime.Compiler, and 'short' (for testing.Short). // // NOTE: If you make changes here, update testdata/script/README too! // ok := false - switch cond { + switch cond.tag { case runtime.GOOS, runtime.GOARCH, runtime.Compiler: ok = true case "short": @@ -285,8 +275,8 @@ Script: case "symlink": ok = testenv.HasSymlink() default: - if strings.HasPrefix(cond, "exec:") { - prog := cond[len("exec:"):] + if strings.HasPrefix(cond.tag, "exec:") { + prog := cond.tag[len("exec:"):] ok = execCache.Do(prog, func() interface{} { if runtime.GOOS == "plan9" && prog == "git" { // The Git command is usually not the real Git on Plan 9. @@ -298,33 +288,22 @@ Script: }).(bool) break } - if !imports.KnownArch[cond] && !imports.KnownOS[cond] && cond != "gc" && cond != "gccgo" { - ts.fatalf("unknown condition %q", cond) + if !imports.KnownArch[cond.tag] && !imports.KnownOS[cond.tag] && cond.tag != "gc" && cond.tag != "gccgo" { + ts.fatalf("unknown condition %q", cond.tag) } } - if ok != want { + if ok != cond.want { // Don't run rest of line. continue Script } } - // Command prefix ! means negate the expectations about this command: - // go command should fail, match should not be found, etc. - neg := false - if args[0] == "!" { - neg = true - args = args[1:] - if len(args) == 0 { - ts.fatalf("! on line by itself") - } - } - // Run command. - cmd := scriptCmds[args[0]] + cmd := scriptCmds[parsed.name] if cmd == nil { - ts.fatalf("unknown command %q", args[0]) + ts.fatalf("unknown command %q", parsed.name) } - cmd(ts, neg, args[1:]) + cmd(ts, parsed.neg, parsed.args) // Command can ask script to stop early. if ts.stopped { @@ -376,6 +355,14 @@ var scriptCmds = map[string]func(*testScript, bool, []string){ "wait": (*testScript).cmdWait, } +// When expanding shell variables for these commands, we apply regexp quoting to +// expanded strings within the first argument. +var regexpCmd = map[string]bool{ + "grep": true, + "stderr": true, + "stdout": true, +} + // addcrlf adds CRLF line endings to the named files. func (ts *testScript) cmdAddcrlf(neg bool, args []string) { if len(args) == 0 { @@ -486,8 +473,8 @@ func (ts *testScript) doCmdCmp(args []string, env bool) { text2 = string(data) if env { - text1 = ts.expand(text1) - text2 = ts.expand(text2) + text1 = ts.expand(text1, false) + text2 = ts.expand(text2, false) } if text1 == text2 { @@ -765,9 +752,11 @@ func scriptMatch(ts *testScript, neg bool, args []string, text, name string) { ts.fatalf("usage: %s [-count=N] 'pattern'%s", name, extraUsage) } - pattern := args[0] - re, err := regexp.Compile(`(?m)` + pattern) - ts.check(err) + pattern := `(?m)` + args[0] + re, err := regexp.Compile(pattern) + if err != nil { + ts.fatalf("regexp.Compile(%q): %v", pattern, err) + } isGrep := name == "grep" if isGrep { @@ -956,8 +945,20 @@ func interruptProcess(p *os.Process) { } // expand applies environment variable expansion to the string s. -func (ts *testScript) expand(s string) string { - return os.Expand(s, func(key string) string { return ts.envMap[key] }) +func (ts *testScript) expand(s string, inRegexp bool) string { + return os.Expand(s, func(key string) string { + e := ts.envMap[key] + if inRegexp { + // Replace workdir with $WORK, since we have done the same substitution in + // the text we're about to compare against. + e = strings.ReplaceAll(e, ts.workdir, "$WORK") + + // Quote to literal strings: we want paths like C:\work\go1.4 to remain + // paths rather than regular expressions. + e = regexp.QuoteMeta(e) + } + return e + }) } // fatalf aborts the test with the given failure message. @@ -975,27 +976,82 @@ func (ts *testScript) mkabs(file string) string { return filepath.Join(ts.cd, file) } +// A condition guards execution of a command. +type condition struct { + want bool + tag string +} + +// A command is a complete command parsed from a script. +type command struct { + neg bool // if true, expect the command to fail + conds []condition // all must be satisfied + name string // the name of the command; must be non-empty + args []string // shell-expanded arguments following name +} + // parse parses a single line as a list of space-separated arguments // subject to environment variable expansion (but not resplitting). // Single quotes around text disable splitting and expansion. // To embed a single quote, double it: 'Don''t communicate by sharing memory.' -func (ts *testScript) parse(line string) []string { +func (ts *testScript) parse(line string) command { ts.line = line var ( - args []string - arg string // text of current arg so far (need to add line[start:i]) - start = -1 // if >= 0, position where current arg text chunk starts - quoted = false // currently processing quoted text + cmd command + arg string // text of current arg so far (need to add line[start:i]) + start = -1 // if >= 0, position where current arg text chunk starts + quoted = false // currently processing quoted text + isRegexp = false // currently processing unquoted regular expression ) + + flushArg := func() { + defer func() { + arg = "" + start = -1 + }() + + if cmd.name != "" { + cmd.args = append(cmd.args, arg) + isRegexp = false // Commands take only one regexp argument, so no subsequent args are regexps. + return + } + + // Command prefix ! means negate the expectations about this command: + // go command should fail, match should not be found, etc. + if arg == "!" { + if cmd.neg { + ts.fatalf("duplicated '!' token") + } + cmd.neg = true + return + } + + // Command prefix [cond] means only run this command if cond is satisfied. + if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") { + want := true + arg = strings.TrimSpace(arg[1 : len(arg)-1]) + if strings.HasPrefix(arg, "!") { + want = false + arg = strings.TrimSpace(arg[1:]) + } + if arg == "" { + ts.fatalf("empty condition") + } + cmd.conds = append(cmd.conds, condition{want: want, tag: arg}) + return + } + + cmd.name = arg + isRegexp = regexpCmd[cmd.name] + } + for i := 0; ; i++ { if !quoted && (i >= len(line) || line[i] == ' ' || line[i] == '\t' || line[i] == '\r' || line[i] == '#') { // Found arg-separating space. if start >= 0 { - arg += ts.expand(line[start:i]) - args = append(args, arg) - start = -1 - arg = "" + arg += ts.expand(line[start:i], isRegexp) + flushArg() } if i >= len(line) || line[i] == '#' { break @@ -1009,7 +1065,7 @@ func (ts *testScript) parse(line string) []string { if !quoted { // starting a quoted chunk if start >= 0 { - arg += ts.expand(line[start:i]) + arg += ts.expand(line[start:i], isRegexp) } start = i + 1 quoted = true @@ -1033,7 +1089,7 @@ func (ts *testScript) parse(line string) []string { start = i } } - return args + return cmd } // diff returns a formatted diff of the two texts,