Repository: gohugoio/hugo (language: go)

Issue:
Upgrade to Go 1.22.1
See https://go.dev/doc/devel/release#go1.22.minor

Note that the template packages also needs to be resynced as there is a security (not relevant for Hugo, but people want a clean security report) patch in that release.


Discussion / hints:

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index a584781b00d..bec0337ae39 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -4,7 +4,7 @@ parameters:
 defaults: &defaults
   resource_class: large
   docker:
-      - image: bepsays/ci-hugoreleaser:1.22200.20000
+      - image: bepsays/ci-hugoreleaser:1.22200.20100
 environment: &buildenv
       GOMODCACHE: /root/project/gomodcache
 version: 2
@@ -60,7 +60,7 @@ jobs:
     environment:
         <<: [*buildenv]
     docker:
-    - image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20000
+    - image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20100
     steps:
       - *restore-cache
       - &attach-workspace
diff --git a/scripts/fork_go_templates/main.go b/scripts/fork_go_templates/main.go
index 5b9262c0afc..38e81ac9ddd 100644
--- a/scripts/fork_go_templates/main.go
+++ b/scripts/fork_go_templates/main.go
@@ -16,7 +16,8 @@ import (
 )
 
 func main() {
-	// The current is built with 446a5dcf5a3230ce9832682d8f521071d8a34a2b (go 1.22 dev. Thu Oct 5 12:20:11 2023 -0700)
+	// The current is built with db6097f8cb [release-branch.go1.22] go1.22.1
+	// TODO(bep) preserve the staticcheck.conf file.
 	fmt.Println("Forking ...")
 	defer fmt.Println("Done ...")
 
diff --git a/tpl/internal/go_templates/htmltemplate/js.go b/tpl/internal/go_templates/htmltemplate/js.go
index 79d9102e565..cc80d2b64e1 100644
--- a/tpl/internal/go_templates/htmltemplate/js.go
+++ b/tpl/internal/go_templates/htmltemplate/js.go
@@ -172,13 +172,31 @@ func jsValEscaper(args ...any) string {
 	// cyclic data. This may be an unacceptable DoS risk.
 	b, err := json.Marshal(a)
 	if err != nil {
-		// Put a space before comment so that if it is flush against
+		// While the standard JSON marshaller does not include user controlled
+		// information in the error message, if a type has a MarshalJSON method,
+		// the content of the error message is not guaranteed. Since we insert
+		// the error into the template, as part of a comment, we attempt to
+		// prevent the error from either terminating the comment, or the script
+		// block itself.
+		//
+		// In particular we:
+		//   * replace "*/" comment end tokens with "* /", which does not
+		//     terminate the comment
+		//   * replace "</script" with "\x3C/script", and "<!--" with
+		//     "\x3C!--", which prevents confusing script block termination
+		//     semantics
+		//
+		// We also put a space before the comment so that if it is flush against
 		// a division operator it is not turned into a line comment:
 		//     x/{{y}}
 		// turning into
 		//     x//* error marshaling y:
 		//          second line of error message */null
-		return fmt.Sprintf(" /* %s */null ", strings.ReplaceAll(err.Error(), "*/", "* /"))
+		errStr := err.Error()
+		errStr = strings.ReplaceAll(errStr, "*/", "* /")
+		errStr = strings.ReplaceAll(errStr, "</script", `\x3C/script`)
+		errStr = strings.ReplaceAll(errStr, "<!--", `\x3C!--`)
+		return fmt.Sprintf(" /* %s */null ", errStr)
 	}
 
 	// TODO: maybe post-process output to prevent it from containing
diff --git a/tpl/internal/go_templates/texttemplate/parse/node.go b/tpl/internal/go_templates/texttemplate/parse/node.go
index 47268225c8c..c36688825c2 100644
--- a/tpl/internal/go_templates/texttemplate/parse/node.go
+++ b/tpl/internal/go_templates/texttemplate/parse/node.go
@@ -284,7 +284,6 @@ func (a *ActionNode) tree() *Tree {
 
 func (a *ActionNode) Copy() Node {
 	return a.tr.newAction(a.Pos, a.Line, a.Pipe.CopyPipe())
-
 }
 
 // CommandNode holds a command (a pipeline inside an evaluating action).


Test patch:
diff --git a/tpl/internal/go_templates/fmtsort/sort_test.go b/tpl/internal/go_templates/fmtsort/sort_test.go
index fef952fa609..e86b4c673d4 100644
--- a/tpl/internal/go_templates/fmtsort/sort_test.go
+++ b/tpl/internal/go_templates/fmtsort/sort_test.go
@@ -6,14 +6,14 @@ package fmtsort_test
 
 import (
 	"fmt"
+	"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
 	"math"
 	"reflect"
+	"runtime"
 	"sort"
 	"strings"
 	"testing"
 	"unsafe"
-
-	"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
 )
 
 var compareTests = [][]reflect.Value{
@@ -191,14 +191,14 @@ func sprintKey(key reflect.Value) string {
 var (
 	ints  [3]int
 	chans = makeChans()
-	// pin   runtime.Pinner
+	pin   runtime.Pinner
 )
 
 func makeChans() []chan int {
 	cs := []chan int{make(chan int), make(chan int), make(chan int)}
 	// Order channels by address. See issue #49431.
 	for i := range cs {
-		reflect.ValueOf(cs[i]).UnsafePointer()
+		pin.Pin(reflect.ValueOf(cs[i]).UnsafePointer())
 	}
 	sort.Slice(cs, func(i, j int) bool {
 		return uintptr(reflect.ValueOf(cs[i]).UnsafePointer()) < uintptr(reflect.ValueOf(cs[j]).UnsafePointer())
diff --git a/tpl/internal/go_templates/htmltemplate/js_test.go b/tpl/internal/go_templates/htmltemplate/js_test.go
index 67a9213370b..c78f43b5f7d 100644
--- a/tpl/internal/go_templates/htmltemplate/js_test.go
+++ b/tpl/internal/go_templates/htmltemplate/js_test.go
@@ -8,6 +8,7 @@
 package template
 
 import (
+	"errors"
 	"math"
 	"strings"
 	"testing"
@@ -106,61 +107,72 @@ func TestNextJsCtx(t *testing.T) {
 	}
 }
 
+type jsonErrType struct{}
+
+func (e *jsonErrType) MarshalJSON() ([]byte, error) {
+	return nil, errors.New("beep */ boop </script blip <!--")
+}
+
 func TestJSValEscaper(t *testing.T) {
 	tests := []struct {
-		x  any
-		js string
+		x        any
+		js       string
+		skipNest bool
 	}{
-		{int(42), " 42 "},
-		{uint(42), " 42 "},
-		{int16(42), " 42 "},
-		{uint16(42), " 42 "},
-		{int32(-42), " -42 "},
-		{uint32(42), " 42 "},
-		{int16(-42), " -42 "},
-		{uint16(42), " 42 "},
-		{int64(-42), " -42 "},
-		{uint64(42), " 42 "},
-		{uint64(1) << 53, " 9007199254740992 "},
+		{int(42), " 42 ", false},
+		{uint(42), " 42 ", false},
+		{int16(42), " 42 ", false},
+		{uint16(42), " 42 ", false},
+		{int32(-42), " -42 ", false},
+		{uint32(42), " 42 ", false},
+		{int16(-42), " -42 ", false},
+		{uint16(42), " 42 ", false},
+		{int64(-42), " -42 ", false},
+		{uint64(42), " 42 ", false},
+		{uint64(1) << 53, " 9007199254740992 ", false},
 		// ulp(1 << 53) > 1 so this loses precision in JS
 		// but it is still a representable integer literal.
-		{uint64(1)<<53 + 1, " 9007199254740993 "},
-		{float32(1.0), " 1 "},
-		{float32(-1.0), " -1 "},
-		{float32(0.5), " 0.5 "},
-		{float32(-0.5), " -0.5 "},
-		{float32(1.0) / float32(256), " 0.00390625 "},
-		{float32(0), " 0 "},
-		{math.Copysign(0, -1), " -0 "},
-		{float64(1.0), " 1 "},
-		{float64(-1.0), " -1 "},
-		{float64(0.5), " 0.5 "},
-		{float64(-0.5), " -0.5 "},
-		{float64(0), " 0 "},
-		{math.Copysign(0, -1), " -0 "},
-		{"", `""`},
-		{"foo", `"foo"`},
+		{uint64(1)<<53 + 1, " 9007199254740993 ", false},
+		{float32(1.0), " 1 ", false},
+		{float32(-1.0), " -1 ", false},
+		{float32(0.5), " 0.5 ", false},
+		{float32(-0.5), " -0.5 ", false},
+		{float32(1.0) / float32(256), " 0.00390625 ", false},
+		{float32(0), " 0 ", false},
+		{math.Copysign(0, -1), " -0 ", false},
+		{float64(1.0), " 1 ", false},
+		{float64(-1.0), " -1 ", false},
+		{float64(0.5), " 0.5 ", false},
+		{float64(-0.5), " -0.5 ", false},
+		{float64(0), " 0 ", false},
+		{math.Copysign(0, -1), " -0 ", false},
+		{"", `""`, false},
+		{"foo", `"foo"`, false},
 		// Newlines.
-		{"\r\n\u2028\u2029", `"\r\n\u2028\u2029"`},
+		{"\r\n\u2028\u2029", `"\r\n\u2028\u2029"`, false},
 		// "\v" == "v" on IE 6 so use "\u000b" instead.
-		{"\t\x0b", `"\t\u000b"`},
-		{struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`},
-		{[]any{}, "[]"},
-		{[]any{42, "foo", nil}, `[42,"foo",null]`},
-		{[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`},
-		{"<!--", `"\u003c!--"`},
-		{"-->", `"--\u003e"`},
-		{"<![CDATA[", `"\u003c![CDATA["`},
-		{"]]>", `"]]\u003e"`},
-		{"</script", `"\u003c/script"`},
-		{"\U0001D11E", "\"\U0001D11E\""}, // or "\uD834\uDD1E"
-		{nil, " null "},
+		{"\t\x0b", `"\t\u000b"`, false},
+		{struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`, false},
+		{[]any{}, "[]", false},
+		{[]any{42, "foo", nil}, `[42,"foo",null]`, false},
+		{[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`, false},
+		{"<!--", `"\u003c!--"`, false},
+		{"-->", `"--\u003e"`, false},
+		{"<![CDATA[", `"\u003c![CDATA["`, false},
+		{"]]>", `"]]\u003e"`, false},
+		{"</script", `"\u003c/script"`, false},
+		{"\U0001D11E", "\"\U0001D11E\"", false}, // or "\uD834\uDD1E"
+		{nil, " null ", false},
+		{&jsonErrType{}, " /* json: error calling MarshalJSON for type *template.jsonErrType: beep * / boop \\x3C/script blip \\x3C!-- */null ", true},
 	}
 
 	for _, test := range tests {
 		if js := jsValEscaper(test.x); js != test.js {
 			t.Errorf("%+v: want\n\t%q\ngot\n\t%q", test.x, test.js, js)
 		}
+		if test.skipNest {
+			continue
+		}
 		// Make sure that escaping corner cases are not broken
 		// by nesting.
 		a := []any{test.x}
diff --git a/tpl/internal/go_templates/testenv/exec.go b/tpl/internal/go_templates/testenv/exec.go
index 14460224800..7f6ad5cac4e 100644
--- a/tpl/internal/go_templates/testenv/exec.go
+++ b/tpl/internal/go_templates/testenv/exec.go
@@ -6,6 +6,7 @@ package testenv
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"os"
 	"os/exec"
@@ -60,6 +61,13 @@ func tryExec() error {
 	// 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()
diff --git a/tpl/internal/go_templates/testenv/testenv_notunix.go b/tpl/internal/go_templates/testenv/testenv_notunix.go
index 916f1815379..a7df5f5ddce 100644
--- a/tpl/internal/go_templates/testenv/testenv_notunix.go
+++ b/tpl/internal/go_templates/testenv/testenv_notunix.go
@@ -17,5 +17,5 @@ import (
 var Sigquit = os.Kill
 
 func syscallIsNotSupported(err error) bool {
-	return errors.Is(err, fs.ErrPermission)
+	return errors.Is(err, fs.ErrPermission) || errors.Is(err, errors.ErrUnsupported)
 }
diff --git a/tpl/internal/go_templates/testenv/testenv_test.go b/tpl/internal/go_templates/testenv/testenv_test.go
index 13e11abfb53..d4b2b368f37 100644
--- a/tpl/internal/go_templates/testenv/testenv_test.go
+++ b/tpl/internal/go_templates/testenv/testenv_test.go
@@ -54,8 +54,8 @@ func TestGoToolLocation(t *testing.T) {
 	}
 }
 
+// Modified by Hugo.
 func TestHasGoBuild(t *testing.T) {
-	// Removed by Hugo.
 }
 
 func TestMustHaveExec(t *testing.T) {
@@ -73,7 +73,7 @@ func TestMustHaveExec(t *testing.T) {
 			t.Errorf("expected MustHaveExec to skip on %v", runtime.GOOS)
 		}
 	case "ios":
-		if b := testenv.Builder(); strings.HasSuffix(b, "-corellium") && !hasExec {
+		if b := testenv.Builder(); isCorelliumBuilder(b) && !hasExec {
 			// Most ios environments can't exec, but the corellium builder can.
 			t.Errorf("expected MustHaveExec not to skip on %v", b)
 		}
@@ -106,3 +106,23 @@ func TestCleanCmdEnvPWD(t *testing.T) {
 	}
 	t.Error("PWD not set in cmd.Env")
 }
+
+func isCorelliumBuilder(builderName string) bool {
+	// Support both the old infra's builder names and the LUCI builder names.
+	// The former's names are ad-hoc so we could maintain this invariant on
+	// the builder side. The latter's names are structured, and "corellium" will
+	// appear as a "host" suffix after the GOOS and GOARCH, which always begin
+	// with an underscore.
+	return strings.HasSuffix(builderName, "-corellium") || strings.Contains(builderName, "_corellium")
+}
+
+func isEmulatedBuilder(builderName string) bool {
+	// Support both the old infra's builder names and the LUCI builder names.
+	// The former's names are ad-hoc so we could maintain this invariant on
+	// the builder side. The latter's names are structured, and the signifier
+	// of emulation "emu" will appear as a "host" suffix after the GOOS and
+	// GOARCH because it modifies the run environment in such a way that it
+	// the target GOOS and GOARCH may not match the host. This suffix always
+	// begins with an underscore.
+	return strings.HasSuffix(builderName, "-emu") || strings.Contains(builderName, "_emu")
+}
diff --git a/tpl/internal/go_templates/testenv/testenv_unix.go b/tpl/internal/go_templates/testenv/testenv_unix.go
index 296eefc7a71..a629078842e 100644
--- a/tpl/internal/go_templates/testenv/testenv_unix.go
+++ b/tpl/internal/go_templates/testenv/testenv_unix.go
@@ -35,7 +35,7 @@ func syscallIsNotSupported(err error) bool {
 		}
 	}
 
-	if errors.Is(err, fs.ErrPermission) {
+	if errors.Is(err, fs.ErrPermission) || errors.Is(err, errors.ErrUnsupported) {
 		return true
 	}