modfile: fix crash on AddGoStmt in empty File

AddGoStmt uses File.Syntax without checking whether
it is nil or not. This causes crashes when using it on empty files that
have not had their Syntax member initialized to a valid pointer.

This change fixes it by ensuring File.Syntax is a valid pointer before
proceeding.

Fixes golang/go#62457.

Change-Id: Iab02039f79e73d939ca5d3e48b29faa5e0a9a5ec
Reviewed-on: https://go-review.googlesource.com/c/mod/+/570115
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Bryan Mills <bcmills@google.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/modfile/rule.go b/modfile/rule.go
index 26acaa5..0e7b7e2 100644
--- a/modfile/rule.go
+++ b/modfile/rule.go
@@ -975,6 +975,8 @@
 		var hint Expr
 		if f.Module != nil && f.Module.Syntax != nil {
 			hint = f.Module.Syntax
+		} else if f.Syntax == nil {
+			f.Syntax = new(FileSyntax)
 		}
 		f.Go = &Go{
 			Version: version,
diff --git a/modfile/rule_test.go b/modfile/rule_test.go
index 57c8be6..96e0bfe 100644
--- a/modfile/rule_test.go
+++ b/modfile/rule_test.go
@@ -1549,6 +1549,20 @@
 	},
 }
 
+var modifyEmptyFilesTests = []struct {
+	desc       string
+	operations func(f *File)
+	want       string
+}{
+	{
+		desc: `addGoStmt`,
+		operations: func(f *File) {
+			f.AddGoStmt("1.20")
+		},
+		want: `go 1.20`,
+	},
+}
+
 func fixV(path, version string) (string, error) {
 	if path != "example.com/m" {
 		return "", fmt.Errorf("module path must be example.com/m")
@@ -1846,3 +1860,29 @@
 		})
 	}
 }
+
+func TestAddOnEmptyFile(t *testing.T) {
+	for _, tt := range modifyEmptyFilesTests {
+		t.Run(tt.desc, func(t *testing.T) {
+			f := &File{}
+			tt.operations(f)
+
+			expect, err := Parse("out", []byte(tt.want), nil)
+			if err != nil {
+				t.Fatal(err)
+			}
+			golden, err := expect.Format()
+			if err != nil {
+				t.Fatal(err)
+			}
+			got, err := f.Format()
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			if !bytes.Equal(got, golden) {
+				t.Fatalf("got:\n%s\nwant:\n%s", got, golden)
+			}
+		})
+	}
+}