diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2023-08-23 18:23:52 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2023-08-23 21:49:27 +0200 |
commit | 24b1be45c17d68c67bab61b2fbb568f53a3d8202 (patch) | |
tree | 4202ce89de8f523e051912e365bdf93b1a4422fd /tpl | |
parent | 111f02db2a2cb139a19e4643dd73fa637e40ad6e (diff) |
Go 1.21 Upgrade
Fixes #11351
Diffstat (limited to 'tpl')
20 files changed, 425 insertions, 100 deletions
diff --git a/tpl/internal/go_templates/cfg/cfg.go b/tpl/internal/go_templates/cfg/cfg.go index 78664d7a9..2af0ec707 100644 --- a/tpl/internal/go_templates/cfg/cfg.go +++ b/tpl/internal/go_templates/cfg/cfg.go @@ -38,6 +38,7 @@ const KnownEnv = ` GOARM GOBIN GOCACHE + GOCACHEPROG GOENV GOEXE GOEXPERIMENT @@ -59,6 +60,7 @@ const KnownEnv = ` GOROOT GOSUMDB GOTMPDIR + GOTOOLCHAIN GOTOOLDIR GOVCS GOWASM diff --git a/tpl/internal/go_templates/htmltemplate/attr_string.go b/tpl/internal/go_templates/htmltemplate/attr_string.go index babe70c08..51c3f2620 100644 --- a/tpl/internal/go_templates/htmltemplate/attr_string.go +++ b/tpl/internal/go_templates/htmltemplate/attr_string.go @@ -4,6 +4,18 @@ package template import "strconv" +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[attrNone-0] + _ = x[attrScript-1] + _ = x[attrScriptType-2] + _ = x[attrStyle-3] + _ = x[attrURL-4] + _ = x[attrSrcset-5] +} + const _attr_name = "attrNoneattrScriptattrScriptTypeattrStyleattrURLattrSrcset" var _attr_index = [...]uint8{0, 8, 18, 32, 41, 48, 58} diff --git a/tpl/internal/go_templates/htmltemplate/delim_string.go b/tpl/internal/go_templates/htmltemplate/delim_string.go index 6d80e09a4..8d8285022 100644 --- a/tpl/internal/go_templates/htmltemplate/delim_string.go +++ b/tpl/internal/go_templates/htmltemplate/delim_string.go @@ -4,6 +4,16 @@ package template import "strconv" +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[delimNone-0] + _ = x[delimDoubleQuote-1] + _ = x[delimSingleQuote-2] + _ = x[delimSpaceOrTagEnd-3] +} + const _delim_name = "delimNonedelimDoubleQuotedelimSingleQuotedelimSpaceOrTagEnd" var _delim_index = [...]uint8{0, 9, 25, 41, 59} diff --git a/tpl/internal/go_templates/htmltemplate/doc.go b/tpl/internal/go_templates/htmltemplate/doc.go index 98b5658f4..6be5e0f84 100644 --- a/tpl/internal/go_templates/htmltemplate/doc.go +++ b/tpl/internal/go_templates/htmltemplate/doc.go @@ -5,16 +5,16 @@ /* Package template (html/template) implements data-driven templates for generating HTML output safe against code injection. It provides the -same interface as package text/template and should be used instead of -text/template whenever the output is HTML. +same interface as [text/template] and should be used instead of +[text/template] whenever the output is HTML. The documentation here focuses on the security features of the package. For information about how to program the templates themselves, see the -documentation for text/template. +documentation for [text/template]. # Introduction -This package wraps package text/template so you can share its template API +This package wraps [text/template] so you can share its template API to parse and execute HTML templates safely. tmpl, err := template.New("name").Parse(...) diff --git a/tpl/internal/go_templates/htmltemplate/element_string.go b/tpl/internal/go_templates/htmltemplate/element_string.go index 4573e0873..db286655a 100644 --- a/tpl/internal/go_templates/htmltemplate/element_string.go +++ b/tpl/internal/go_templates/htmltemplate/element_string.go @@ -4,6 +4,17 @@ package template import "strconv" +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[elementNone-0] + _ = x[elementScript-1] + _ = x[elementStyle-2] + _ = x[elementTextarea-3] + _ = x[elementTitle-4] +} + const _element_name = "elementNoneelementScriptelementStyleelementTextareaelementTitle" var _element_index = [...]uint8{0, 11, 24, 36, 51, 63} diff --git a/tpl/internal/go_templates/htmltemplate/error.go b/tpl/internal/go_templates/htmltemplate/error.go index 0a62563cf..a4450a4a9 100644 --- a/tpl/internal/go_templates/htmltemplate/error.go +++ b/tpl/internal/go_templates/htmltemplate/error.go @@ -216,18 +216,13 @@ const ( // disallowed. Avoid using "html" and "urlquery" entirely in new templates. ErrPredefinedEscaper - // errJSTmplLit: "... appears in a JS template literal" + // ErrJSTemplate: "... appears in a JS template literal" // Example: - // <script>var tmpl = `{{.Interp}`</script> + // <script>var tmpl = `{{.Interp}}`</script> // Discussion: // Package html/template does not support actions inside of JS template // literals. - // - // TODO(rolandshoemaker): we cannot add this as an exported error in a minor - // release, since it is backwards incompatible with the other minor - // releases. As such we need to leave it unexported, and then we'll add it - // in the next major release. - errJSTmplLit + ErrJSTemplate ) func (e *Error) Error() string { diff --git a/tpl/internal/go_templates/htmltemplate/escape.go b/tpl/internal/go_templates/htmltemplate/escape.go index ba9b4bbb8..f6b2e4b34 100644 --- a/tpl/internal/go_templates/htmltemplate/escape.go +++ b/tpl/internal/go_templates/htmltemplate/escape.go @@ -8,6 +8,8 @@ import ( "bytes" "fmt" "html" + + //"internal/godebug" "io" template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate" @@ -161,6 +163,7 @@ func (e *escaper) escape(c context, n parse.Node) context { panic("escaping " + n.String() + " is unimplemented") } +// Modified by Hugo. // var debugAllowActionJSTmpl = godebug.New("jstmpllitinterp") // escapeAction escapes an action template node. @@ -227,12 +230,13 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context { case stateJSDqStr, stateJSSqStr: s = append(s, "_html_template_jsstrescaper") case stateJSBqStr: - if SecurityAllowActionJSTmpl.Load() { // .Value() == "1" { + if SecurityAllowActionJSTmpl.Load() { + //debugAllowActionJSTmpl.IncNonDefault() s = append(s, "_html_template_jsstrescaper") } else { return context{ state: stateError, - err: errorf(errJSTmplLit, n, n.Line, "%s appears in a JS template literal", n), + err: errorf(ErrJSTemplate, n, n.Line, "%s appears in a JS template literal", n), } } case stateJSRegexp: @@ -756,7 +760,7 @@ func (e *escaper) escapeText(c context, n *parse.TextNode) context { } else if isComment(c.state) && c.delim == delimNone { switch c.state { case stateJSBlockCmt: - // https://es5.github.com/#x7.4: + // https://es5.github.io/#x7.4: // "Comments behave like white space and are // discarded except that, if a MultiLineComment // contains a line terminator character, then diff --git a/tpl/internal/go_templates/htmltemplate/exec_test.go b/tpl/internal/go_templates/htmltemplate/exec_test.go index 0f29cb060..8f9b893b4 100644 --- a/tpl/internal/go_templates/htmltemplate/exec_test.go +++ b/tpl/internal/go_templates/htmltemplate/exec_test.go @@ -925,7 +925,7 @@ func TestJSEscaping(t *testing.T) { {`'foo`, `\'foo`}, {`Go "jump" \`, `Go \"jump\" \\`}, {`Yukihiro says "今日は世界"`, `Yukihiro says \"今日は世界\"`}, - {"unprintable \uFDFF", `unprintable \uFDFF`}, + {"unprintable \uFFFE", `unprintable \uFFFE`}, {`<html>`, `\u003Chtml\u003E`}, {`no = in attributes`, `no \u003D in attributes`}, {`' does not become HTML entity`, `\u0026#x27; does not become HTML entity`}, diff --git a/tpl/internal/go_templates/htmltemplate/transition.go b/tpl/internal/go_templates/htmltemplate/transition.go index 92eb35190..3b9fbfb68 100644 --- a/tpl/internal/go_templates/htmltemplate/transition.go +++ b/tpl/internal/go_templates/htmltemplate/transition.go @@ -397,7 +397,7 @@ func tLineCmt(c context, s []byte) (context, int) { return c, len(s) } c.state = endState - // Per section 7.4 of EcmaScript 5 : https://es5.github.com/#x7.4 + // Per section 7.4 of EcmaScript 5 : https://es5.github.io/#x7.4 // "However, the LineTerminator at the end of the line is not // considered to be part of the single-line comment; it is // recognized separately by the lexical grammar and becomes part diff --git a/tpl/internal/go_templates/htmltemplate/urlpart_string.go b/tpl/internal/go_templates/htmltemplate/urlpart_string.go index 813eea9e4..7bc957e81 100644 --- a/tpl/internal/go_templates/htmltemplate/urlpart_string.go +++ b/tpl/internal/go_templates/htmltemplate/urlpart_string.go @@ -4,6 +4,16 @@ package template import "strconv" +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[urlPartNone-0] + _ = x[urlPartPreQuery-1] + _ = x[urlPartQueryOrFrag-2] + _ = x[urlPartUnknown-3] +} + const _urlPart_name = "urlPartNoneurlPartPreQueryurlPartQueryOrFragurlPartUnknown" var _urlPart_index = [...]uint8{0, 11, 26, 44, 58} diff --git a/tpl/internal/go_templates/testenv/exec.go b/tpl/internal/go_templates/testenv/exec.go index 13b4c7102..88791b3b4 100644 --- a/tpl/internal/go_templates/testenv/exec.go +++ b/tpl/internal/go_templates/testenv/exec.go @@ -6,33 +6,78 @@ package testenv import ( "context" + "fmt" "os" "os/exec" "runtime" + "strconv" "strings" "sync" "testing" + "time" ) -// HasExec reports whether the current system can start new processes -// using os.StartProcess or (more commonly) exec.Command. -func HasExec() bool { - switch runtime.GOOS { - case "js", "ios": - return false - } - return true -} - // MustHaveExec checks that the current system can start new processes // using os.StartProcess or (more commonly) exec.Command. // If not, MustHaveExec calls t.Skip with an explanation. +// +// On some platforms MustHaveExec checks for exec support by re-executing the +// current executable, which must be a binary built by 'go test'. +// We intentionally do not provide a HasExec function because of the risk of +// inappropriate recursion in TestMain functions. +// +// To check for exec support outside of a test, just try to exec the command. +// If exec is not supported, testenv.SyscallIsNotSupported will return true +// for the resulting error. func MustHaveExec(t testing.TB) { - if !HasExec() { - t.Skipf("skipping test: cannot exec subprocess on %s/%s", runtime.GOOS, runtime.GOARCH) + tryExecOnce.Do(func() { + tryExecErr = tryExec() + }) + if tryExecErr != nil { + t.Skipf("skipping test: cannot exec subprocess on %s/%s: %v", runtime.GOOS, runtime.GOARCH, tryExecErr) } } +var ( + tryExecOnce sync.Once + tryExecErr error +) + +func tryExec() error { + switch runtime.GOOS { + case "wasip1", "js", "ios": + default: + // Assume that exec always works on non-mobile platforms and Android. + return nil + } + + // ios has an exec syscall but on real iOS devices it might return a + // permission error. In an emulated environment (such as a Corellium host) + // it might succeed, so if we need to exec we'll just have to try it and + // find out. + // + // As of 2023-04-19 wasip1 and js don't have exec syscalls at all, but we + // may as well use the same path so that this branch can be tested without + // an ios environment. + + /*if !testing.Testing() { + // This isn't a standard 'go test' binary, so we don't know how to + // self-exec in a way that should succeed without side effects. + // Just forget it. + return errors.New("can't probe for exec support with a non-test executable") + }*/ + + // We know that this is a test executable. We should be able to run it with a + // no-op flag to check for overall exec support. + exe, err := os.Executable() + if err != nil { + return fmt.Errorf("can't probe for exec support: %w", err) + } + cmd := exec.Command(exe, "-test.list=^$") + cmd.Env = origEnv + return cmd.Run() +} + var execPaths sync.Map // path -> error // MustHaveExecPath checks that the current system can start the named executable @@ -82,7 +127,87 @@ func CleanCmdEnv(cmd *exec.Cmd) *exec.Cmd { // - fails the test if the command does not complete before the test's deadline, and // - sets a Cleanup function that verifies that the test did not leak a subprocess. func CommandContext(t testing.TB, ctx context.Context, name string, args ...string) *exec.Cmd { - panic("Not implemented, Hugo is not using this") + t.Helper() + MustHaveExec(t) + + var ( + cancelCtx context.CancelFunc + gracePeriod time.Duration // unlimited unless the test has a deadline (to allow for interactive debugging) + ) + + if t, ok := t.(interface { + testing.TB + Deadline() (time.Time, bool) + }); ok { + if td, ok := t.Deadline(); ok { + // Start with a minimum grace period, just long enough to consume the + // output of a reasonable program after it terminates. + gracePeriod = 100 * time.Millisecond + if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" { + scale, err := strconv.Atoi(s) + if err != nil { + t.Fatalf("invalid GO_TEST_TIMEOUT_SCALE: %v", err) + } + gracePeriod *= time.Duration(scale) + } + + // If time allows, increase the termination grace period to 5% of the + // test's remaining time. + testTimeout := time.Until(td) + if gp := testTimeout / 20; gp > gracePeriod { + gracePeriod = gp + } + + // When we run commands that execute subprocesses, we want to reserve two + // grace periods to clean up: one for the delay between the first + // termination signal being sent (via the Cancel callback when the Context + // expires) and the process being forcibly terminated (via the WaitDelay + // field), and a second one for the delay between the process being + // terminated and the test logging its output for debugging. + // + // (We want to ensure that the test process itself has enough time to + // log the output before it is also terminated.) + cmdTimeout := testTimeout - 2*gracePeriod + + if cd, ok := ctx.Deadline(); !ok || time.Until(cd) > cmdTimeout { + // Either ctx doesn't have a deadline, or its deadline would expire + // after (or too close before) the test has already timed out. + // Add a shorter timeout so that the test will produce useful output. + ctx, cancelCtx = context.WithTimeout(ctx, cmdTimeout) + } + } + } + + cmd := exec.CommandContext(ctx, name, args...) + cmd.Cancel = func() error { + if cancelCtx != nil && ctx.Err() == context.DeadlineExceeded { + // The command timed out due to running too close to the test's deadline. + // There is no way the test did that intentionally — it's too close to the + // wire! — so mark it as a test failure. That way, if the test expects the + // command to fail for some other reason, it doesn't have to distinguish + // between that reason and a timeout. + t.Errorf("test timed out while running command: %v", cmd) + } else { + // The command is being terminated due to ctx being canceled, but + // apparently not due to an explicit test deadline that we added. + // Log that information in case it is useful for diagnosing a failure, + // but don't actually fail the test because of it. + t.Logf("%v: terminating command: %v", ctx.Err(), cmd) + } + return cmd.Process.Signal(Sigquit) + } + cmd.WaitDelay = gracePeriod + + t.Cleanup(func() { + if cancelCtx != nil { + cancelCtx() + } + if cmd.Process != nil && cmd.ProcessState == nil { + t.Errorf("command was started, but test did not wait for it to complete: %v", cmd) + } + }) + + return cmd } // Command is like exec.Command, but applies the same changes as diff --git a/tpl/internal/go_templates/testenv/testenv.go b/tpl/internal/go_templates/testenv/testenv.go index 91de6e76c..2430ae6cf 100644 --- a/tpl/internal/go_templates/testenv/testenv.go +++ b/tpl/internal/go_templates/testenv/testenv.go @@ -11,9 +11,14 @@ package testenv import ( + "bytes" "errors" "flag" "fmt" + + "github.com/gohugoio/hugo/tpl/internal/go_templates/cfg" + + //"internal/platform" "os" "os/exec" "path/filepath" @@ -22,10 +27,14 @@ import ( "strings" "sync" "testing" - - "github.com/gohugoio/hugo/tpl/internal/go_templates/cfg" ) +// Save the original environment during init for use in checks. A test +// binary may modify its environment before calling HasExec to change its +// behavior (such as mimicking a command-line tool), and that modified +// environment might cause environment checks to behave erratically. +var origEnv = os.Environ() + // Builder reports the name of the builder running this test // (for example, "linux-amd64" or "windows-386-gce"). // If the test is not running on the build infrastructure, @@ -37,29 +46,26 @@ func Builder() string { // HasGoBuild reports whether the current system can build programs with “go build” // and then run them with os.StartProcess or exec.Command. func HasGoBuild() bool { - if os.Getenv("GO_GCFLAGS") != "" { - // It's too much work to require every caller of the go command - // to pass along "-gcflags="+os.Getenv("GO_GCFLAGS"). - // For now, if $GO_GCFLAGS is set, report that we simply can't - // run go build. - return false - } - switch runtime.GOOS { - case "android", "js", "ios": - return false - } - return true + // Modified by Hugo (not needed) + return false } +var ( + goBuildOnce sync.Once + goBuildErr error +) + // MustHaveGoBuild checks that the current system can build programs with “go build” // and then run them with os.StartProcess or exec.Command. // If not, MustHaveGoBuild calls t.Skip with an explanation. func MustHaveGoBuild(t testing.TB) { if os.Getenv("GO_GCFLAGS") != "" { + t.Helper() t.Skipf("skipping test: 'go build' not compatible with setting $GO_GCFLAGS") } if !HasGoBuild() { - t.Skipf("skipping test: 'go build' not available on %s/%s", runtime.GOOS, runtime.GOARCH) + t.Helper() + t.Skipf("skipping test: 'go build' unavailable: %v", goBuildErr) } } @@ -77,6 +83,25 @@ func MustHaveGoRun(t testing.TB) { } } +// HasParallelism reports whether the current system can execute multiple +// threads in parallel. +// There is a copy of this function in cmd/dist/test.go. +func HasParallelism() bool { + switch runtime.GOOS { + case "js", "wasip1": + return false + } + return true +} + +// MustHaveParallelism checks that the current system can execute multiple +// threads in parallel. If not, MustHaveParallelism calls t.Skip with an explanation. +func MustHaveParallelism(t testing.TB) { + if !HasParallelism() { + t.Skipf("skipping test: no parallelism available on %s/%s", runtime.GOOS, runtime.GOARCH) + } +} + // GoToolPath reports the path to the Go tool. // It is a convenience wrapper around GoTool. // If the tool is unavailable GoToolPath calls t.Skip. @@ -124,6 +149,9 @@ func findGOROOT() (string, error) { // runs the test in the directory containing the packaged under test.) That // means that if we start walking up the tree, we should eventually find // GOROOT/src/go.mod, and we can report the parent directory of that. + // + // Notably, this works even if we can't run 'go env GOROOT' as a + // subprocess. cwd, err := os.Getwd() if err != nil { @@ -174,7 +202,8 @@ func findGOROOT() (string, error) { // GOROOT reports the path to the directory containing the root of the Go // project source tree. This is normally equivalent to runtime.GOROOT, but -// works even if the test binary was built with -trimpath. +// works even if the test binary was built with -trimpath and cannot exec +// 'go env GOROOT'. // // If GOROOT cannot be found, GOROOT skips t if t is non-nil, // or panics otherwise. @@ -195,25 +224,18 @@ func GoTool() (string, error) { if !HasGoBuild() { return "", errors.New("platform cannot run go tool") } - var exeSuffix string - if runtime.GOOS == "windows" { - exeSuffix = ".exe" - } - goroot, err := findGOROOT() - if err != nil { - return "", fmt.Errorf("cannot find go tool: %w", err) - } - path := filepath.Join(goroot, "bin", "go"+exeSuffix) - if _, err := os.Stat(path); err == nil { - return path, nil - } - goBin, err := exec.LookPath("go" + exeSuffix) - if err != nil { - return "", errors.New("cannot find go tool: " + err.Error()) - } - return goBin, nil + goToolOnce.Do(func() { + goToolPath, goToolErr = exec.LookPath("go") + }) + return goToolPath, goToolErr } +var ( + goToolOnce sync.Once + goToolPath string + goToolErr error +) + // HasSrc reports whether the entire source tree is available under GOROOT. func HasSrc() bool { switch runtime.GOOS { @@ -226,50 +248,82 @@ func HasSrc() bool { // HasExternalNetwork reports whether the current system can use // external (non-localhost) networks. func HasExternalNetwork() bool { - return !testing.Short() && runtime.GOOS != "js" + return !testing.Short() && runtime.GOOS != "js" && runtime.GOOS != "wasip1" } // MustHaveExternalNetwork checks that the current system can use // external (non-localhost) networks. // If not, MustHaveExternalNetwork calls t.Skip with an explanation. func MustHaveExternalNetwork(t testing.TB) { - if runtime.GOOS == "js" { + if runtime.GOOS == "js" || runtime.GOOS == "wasip1" { + t.Helper() t.Skipf("skipping test: no external network on %s", runtime.GOOS) } if testing.Short() { + t.Helper() t.Skipf("skipping test: no external network in -short mode") } } -var haveCGO bool - // HasCGO reports whether the current system can use cgo. func HasCGO() bool { - return haveCGO + hasCgoOnce.Do(func() { + goTool, err := GoTool() + if err != nil { + return + } + cmd := exec.Command(goTool, "env", "CGO_ENABLED") + cmd.Env = origEnv + out, err := cmd.Output() + if err != nil { + panic(fmt.Sprintf("%v: %v", cmd, out)) + } + hasCgo, err = strconv.ParseBool(string(bytes.TrimSpace(out))) + if err != nil { + panic(fmt.Sprintf("%v: non-boolean output %q", cmd, out)) + } + }) + return hasCgo } +var ( + hasCgoOnce sync.Once + hasCgo bool +) + // MustHaveCGO calls t.Skip if cgo is not available. func MustHaveCGO(t testing.TB) { - if !haveCGO { + if !HasCGO() { t.Skipf("skipping test: no cgo") } } // CanInternalLink reports whether the current system can link programs with // internal linking. -func CanInternalLink() bool { - panic("not implemented, not needed by Hugo") +func CanInternalLink(withCgo bool) bool { + // Modified by Hugo (not needed) + return false } // MustInternalLink checks that the current system can link programs with internal // linking. // If not, MustInternalLink calls t.Skip with an explanation. -func MustInternalLink(t testing.TB) { - if !CanInternalLink() { +func MustInternalLink(t testing.TB, withCgo bool) { + if !CanInternalLink(withCgo) { + if withCgo && CanInternalLink(false) { |