go/loader: fix a data race

The loader was calling (*types.Checker).Files on the "unsafe" package,
a global variable.  Even with zero files, this operation is not a no-op
because it sets the package's "complete" flag, leading to a data race.
(Because Unsafe.complete is already set at construction, the
race is benign, but is reported by -race nonetheless.)

Fixes golang/go#20718

Change-Id: I5a4f95be5ab4c60ea3b6c2a7fb6f1b67acbf42bc
Reviewed-on: https://go-review.googlesource.com/46071
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/go/loader/loader.go b/go/loader/loader.go
index f69a668..1cd4d6e 100644
--- a/go/loader/loader.go
+++ b/go/loader/loader.go
@@ -1009,15 +1009,19 @@
 			time.Since(imp.start), info.Pkg.Path(), len(files))
 	}
 
-	if info.Pkg == types.Unsafe && len(files) > 0 {
-		panic(`addFiles("unsafe") not permitted`)
+	// Don't call checker.Files on Unsafe, even with zero files,
+	// because it would mutate the package, which is a global.
+	if info.Pkg == types.Unsafe {
+		if len(files) > 0 {
+			panic(`"unsafe" package contains unexpected files`)
+		}
+	} else {
+		// Ignore the returned (first) error since we
+		// already collect them all in the PackageInfo.
+		info.checker.Files(files)
+		info.Files = append(info.Files, files...)
 	}
 
-	// Ignore the returned (first) error since we
-	// already collect them all in the PackageInfo.
-	info.checker.Files(files)
-	info.Files = append(info.Files, files...)
-
 	if imp.conf.AfterTypeCheck != nil {
 		imp.conf.AfterTypeCheck(info, files)
 	}
diff --git a/go/loader/loader_test.go b/go/loader/loader_test.go
index 807f798..02630eb 100644
--- a/go/loader/loader_test.go
+++ b/go/loader/loader_test.go
@@ -800,3 +800,17 @@
 	}
 	return strings.Join(pkgs, " ")
 }
+
+// Load package "io" twice in parallel.
+// When run with -race, this is a regression test for Go issue 20718, in
+// which the global "unsafe" package was modified concurrently.
+func TestLoad1(t *testing.T) { loadIO(t) }
+func TestLoad2(t *testing.T) { loadIO(t) }
+
+func loadIO(t *testing.T) {
+	t.Parallel()
+	conf := &loader.Config{ImportPkgs: map[string]bool{"io": false}}
+	if _, err := conf.Load(); err != nil {
+		t.Fatal(err)
+	}
+}