go/analysis/passes: fix bugs discovered in std

asmdecl:
- MOVW $x+0(FP) is OK if x is big, because $x is an address
  (happens in internal/cpu, golang.org/x/sys/cpu, runtime)
- ignore TEXT lines in comments
  (happens in runtime/internal/atomic)
- wasm's CallImport instruction writes return results
  (happens in syscall)
- allow write out-of-bounds (SP) references in NOFRAME functions
  (happens in runtime)
- recognize "NOP SP" as an SP "write" to disable SP bounds checking
- 'go test' in passes/asmdecl was not testing all architectures; fix that

stdmethods:
- ignore WriteTo if obviously not io.WriterTo (as in go/types and runtime/pprof)

errorsas:
- don't complain about package errors testing invalid calls

structtag:
- don't complain about encoding/json and encoding/xml testing invalid tags

unmarshal:
- don't complain about encoding/gob, encoding/json, encoding/xml testing invalid calls

For golang/go#31916.
Fixes golang/go#25822.

Change-Id: I322c08b5991ffc4995112b8ea945161a4c5193ce
Reviewed-on: https://go-review.googlesource.com/c/tools/+/176097
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Michael Matloob <matloob@golang.org>
diff --git a/go/analysis/cmd/vet/vet.go b/go/analysis/cmd/vet/vet.go
index 4ad2ca6..ec6dcb9 100644
--- a/go/analysis/cmd/vet/vet.go
+++ b/go/analysis/cmd/vet/vet.go
@@ -31,7 +31,6 @@
 	"golang.org/x/tools/go/analysis/passes/loopclosure"
 	"golang.org/x/tools/go/analysis/passes/lostcancel"
 	"golang.org/x/tools/go/analysis/passes/nilfunc"
-	"golang.org/x/tools/go/analysis/passes/nilness"
 	"golang.org/x/tools/go/analysis/passes/printf"
 	"golang.org/x/tools/go/analysis/passes/shift"
 	"golang.org/x/tools/go/analysis/passes/stdmethods"
@@ -79,6 +78,6 @@
 		// pkgfact.Analyzer,
 
 		// uses SSA:
-		nilness.Analyzer,
+		// nilness.Analyzer,
 	)
 }
diff --git a/go/analysis/passes/asmdecl/asmdecl.go b/go/analysis/passes/asmdecl/asmdecl.go
index 6403d77..d41c4e9 100644
--- a/go/analysis/passes/asmdecl/asmdecl.go
+++ b/go/analysis/passes/asmdecl/asmdecl.go
@@ -130,7 +130,7 @@
 	asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`)
 	asmTEXT      = re(`\bTEXT\b(.*)·([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+()]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`)
 	asmDATA      = re(`\b(DATA|GLOBL)\b`)
-	asmNamedFP   = re(`([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`)
+	asmNamedFP   = re(`\$?([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`)
 	asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`)
 	asmSP        = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`)
 	asmOpcode    = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`)
@@ -184,6 +184,7 @@
 			fnName             string
 			localSize, argSize int
 			wroteSP            bool
+			noframe            bool
 			haveRetArg         bool
 			retLine            []int
 		)
@@ -231,6 +232,11 @@
 				}
 			}
 
+			// Ignore comments and commented-out code.
+			if i := strings.Index(line, "//"); i >= 0 {
+				line = line[:i]
+			}
+
 			if m := asmTEXT.FindStringSubmatch(line); m != nil {
 				flushRet()
 				if arch == "" {
@@ -254,7 +260,7 @@
 					// identifiers to represent the directory separator.
 					pkgPath = strings.Replace(pkgPath, "∕", "/", -1)
 					if pkgPath != pass.Pkg.Path() {
-						log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath)
+						// log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath)
 						fn = nil
 						fnName = ""
 						continue
@@ -275,7 +281,8 @@
 					localSize += archDef.intSize
 				}
 				argSize, _ = strconv.Atoi(m[5])
-				if fn == nil && !strings.Contains(fnName, "<>") {
+				noframe = strings.Contains(flag, "NOFRAME")
+				if fn == nil && !strings.Contains(fnName, "<>") && !noframe {
 					badf("function %s missing Go declaration", fnName)
 				}
 				wroteSP = false
@@ -305,13 +312,18 @@
 				continue
 			}
 
-			if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) {
+			if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) || strings.Contains(line, "NOP "+archDef.stack) || strings.Contains(line, "NOP\t"+archDef.stack) {
 				wroteSP = true
 				continue
 			}
 
+			if arch == "wasm" && strings.Contains(line, "CallImport") {
+				// CallImport is a call out to magic that can write the result.
+				haveRetArg = true
+			}
+
 			for _, m := range asmSP.FindAllStringSubmatch(line, -1) {
-				if m[3] != archDef.stack || wroteSP {
+				if m[3] != archDef.stack || wroteSP || noframe {
 					continue
 				}
 				off := 0
@@ -371,7 +383,7 @@
 					}
 					continue
 				}
-				asmCheckVar(badf, fn, line, m[0], off, v)
+				asmCheckVar(badf, fn, line, m[0], off, v, archDef)
 			}
 		}
 		flushRet()
@@ -589,7 +601,7 @@
 }
 
 // asmCheckVar checks a single variable reference.
-func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar) {
+func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar, archDef *asmArch) {
 	m := asmOpcode.FindStringSubmatch(line)
 	if m == nil {
 		if !strings.HasPrefix(strings.TrimSpace(line), "//") {
@@ -598,6 +610,8 @@
 		return
 	}
 
+	addr := strings.HasPrefix(expr, "$")
+
 	// Determine operand sizes from instruction.
 	// Typically the suffix suffices, but there are exceptions.
 	var src, dst, kind asmKind
@@ -617,10 +631,13 @@
 	// They just take the address of it.
 	case "386.LEAL":
 		dst = 4
+		addr = true
 	case "amd64.LEAQ":
 		dst = 8
+		addr = true
 	case "amd64p32.LEAL":
 		dst = 4
+		addr = true
 	default:
 		switch fn.arch.name {
 		case "386", "amd64":
@@ -725,6 +742,11 @@
 		vs = v.inner[0].size
 		vt = v.inner[0].typ
 	}
+	if addr {
+		vk = asmKind(archDef.ptrSize)
+		vs = archDef.ptrSize
+		vt = "address"
+	}
 
 	if off != v.off {
 		var inner bytes.Buffer
diff --git a/go/analysis/passes/asmdecl/asmdecl_test.go b/go/analysis/passes/asmdecl/asmdecl_test.go
index e978fbb..f88c188 100644
--- a/go/analysis/passes/asmdecl/asmdecl_test.go
+++ b/go/analysis/passes/asmdecl/asmdecl_test.go
@@ -5,13 +5,33 @@
 package asmdecl_test
 
 import (
+	"os"
+	"strings"
 	"testing"
 
 	"golang.org/x/tools/go/analysis/analysistest"
 	"golang.org/x/tools/go/analysis/passes/asmdecl"
 )
 
+var goosarches = []string{
+	"linux/amd64",  // asm1.s, asm4.s
+	"linux/386",    // asm2.s
+	"linux/arm",    // asm3.s
+	"linux/mips64", // asm5.s
+	"linux/s390x",  // asm6.s
+	"linux/ppc64",  // asm7.s
+	"linux/mips",   // asm8.s,
+	"js/wasm",      // asm9.s
+}
+
 func Test(t *testing.T) {
 	testdata := analysistest.TestData()
-	analysistest.Run(t, testdata, asmdecl.Analyzer, "a")
+	for _, goosarch := range goosarches {
+		t.Run(goosarch, func(t *testing.T) {
+			i := strings.Index(goosarch, "/")
+			os.Setenv("GOOS", goosarch[:i])
+			os.Setenv("GOARCH", goosarch[i+1:])
+			analysistest.Run(t, testdata, asmdecl.Analyzer, "a")
+		})
+	}
 }
diff --git a/go/analysis/passes/asmdecl/testdata/src/a/asm1.s b/go/analysis/passes/asmdecl/testdata/src/a/asm1.s
index 4fa31a2..9af8612 100644
--- a/go/analysis/passes/asmdecl/testdata/src/a/asm1.s
+++ b/go/analysis/passes/asmdecl/testdata/src/a/asm1.s
@@ -4,6 +4,11 @@
 
 // +build amd64
 
+// Commented-out code should be ignored.
+//
+//	TEXT ·unknown(SB),0,$0
+//		RET
+
 TEXT ·arg1(SB),0,$0-2
 	MOVB	x+0(FP), AX
 	// MOVB x+0(FP), AX // commented out instructions used to panic
@@ -152,6 +157,7 @@
 TEXT ·argstring(SB),0,$32 // want `wrong argument size 0; expected \$\.\.\.-32`
 	MOVW	x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); string base is 8-byte value`
 	MOVL	x+0(FP), AX // want `invalid MOVL of x\+0\(FP\); string base is 8-byte value`
+	LEAQ	x+0(FP), AX // ok
 	MOVQ	x+0(FP), AX
 	MOVW	x_base+0(FP), AX // want `invalid MOVW of x_base\+0\(FP\); string base is 8-byte value`
 	MOVL	x_base+0(FP), AX // want `invalid MOVL of x_base\+0\(FP\); string base is 8-byte value`
diff --git a/go/analysis/passes/asmdecl/testdata/src/a/asm3.s b/go/analysis/passes/asmdecl/testdata/src/a/asm3.s
index 71980e2..a269e0e 100644
--- a/go/analysis/passes/asmdecl/testdata/src/a/asm3.s
+++ b/go/analysis/passes/asmdecl/testdata/src/a/asm3.s
@@ -186,6 +186,6 @@
 TEXT ·noframe2(SB),NOFRAME,$0-4
 	MOVW	0(R13), AX // Okay; caller's saved LR
 	MOVW	x+4(R13), AX // Okay; x argument
-	MOVW	8(R13), AX // want `use of 8\(R13\) points beyond argument frame`
-	MOVW	12(R13), AX // want `use of 12\(R13\) points beyond argument frame`
+	MOVW	8(R13), AX // Okay - NOFRAME is assumed special
+	MOVW	12(R13), AX // Okay - NOFRAME is assumed special
 	RET
diff --git a/go/analysis/passes/asmdecl/testdata/src/a/asm8.s b/go/analysis/passes/asmdecl/testdata/src/a/asm8.s
index ba49d7d..1e736db 100644
--- a/go/analysis/passes/asmdecl/testdata/src/a/asm8.s
+++ b/go/analysis/passes/asmdecl/testdata/src/a/asm8.s
@@ -47,6 +47,7 @@
 	MOVH	x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); int64 is 8-byte value`
 	MOVH	y+8(FP), R1 // want `invalid MOVH of y\+8\(FP\); uint64 is 8-byte value`
 	MOVW	x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); int64 is 8-byte value containing x_lo\+0\(FP\) and x_hi\+4\(FP\)`
+	MOVW	$x+0(FP), R1 // ok
 	MOVW	x_lo+0(FP), R1
 	MOVW	x_hi+4(FP), R1
 	MOVW	y+8(FP), R1 // want `invalid MOVW of y\+8\(FP\); uint64 is 8-byte value containing y_lo\+8\(FP\) and y_hi\+12\(FP\)`
diff --git a/go/analysis/passes/asmdecl/testdata/src/a/asm9.s b/go/analysis/passes/asmdecl/testdata/src/a/asm9.s
new file mode 100644
index 0000000..8c9b6ec
--- /dev/null
+++ b/go/analysis/passes/asmdecl/testdata/src/a/asm9.s
@@ -0,0 +1,13 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build wasm
+
+TEXT ·returnint(SB),NOSPLIT|NOFRAME,$0-8
+	MOVD	24(SP), R1 // ok to access beyond stack frame with NOFRAME
+	CallImport // interpreted as writing result
+	RET
+
+TEXT ·returnbyte(SB),$0-9
+	RET // want `RET without writing`
diff --git a/go/analysis/passes/errorsas/errorsas.go b/go/analysis/passes/errorsas/errorsas.go
index 3683c75..8dcbaaa 100644
--- a/go/analysis/passes/errorsas/errorsas.go
+++ b/go/analysis/passes/errorsas/errorsas.go
@@ -29,6 +29,13 @@
 }
 
 func run(pass *analysis.Pass) (interface{}, error) {
+	switch pass.Pkg.Path() {
+	case "errors", "errors_test":
+		// These packages know how to use their own APIs.
+		// Sometimes they are testing what happens to incorrect programs.
+		return nil, nil
+	}
+
 	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
 
 	nodeFilter := []ast.Node{
diff --git a/go/analysis/passes/stdmethods/stdmethods.go b/go/analysis/passes/stdmethods/stdmethods.go
index 72530a0..bc1db7e 100644
--- a/go/analysis/passes/stdmethods/stdmethods.go
+++ b/go/analysis/passes/stdmethods/stdmethods.go
@@ -116,6 +116,13 @@
 	args := sign.Params()
 	results := sign.Results()
 
+	// Special case: WriteTo with more than one argument,
+	// not trying at all to implement io.WriterTo,
+	// comes up often enough to skip.
+	if id.Name == "WriteTo" && args.Len() > 1 {
+		return
+	}
+
 	// Do the =s (if any) all match?
 	if !matchParams(pass, expect.args, args, "=") || !matchParams(pass, expect.results, results, "=") {
 		return
diff --git a/go/analysis/passes/stdmethods/testdata/src/a/a.go b/go/analysis/passes/stdmethods/testdata/src/a/a.go
index de240a3..f660d32 100644
--- a/go/analysis/passes/stdmethods/testdata/src/a/a.go
+++ b/go/analysis/passes/stdmethods/testdata/src/a/a.go
@@ -7,6 +7,7 @@
 import (
 	"encoding/xml"
 	"fmt"
+	"io"
 )
 
 type T int
@@ -28,6 +29,10 @@
 	return nil
 }
 
+func (U) WriteTo(w io.Writer) {} // want `method WriteTo\(w io.Writer\) should have signature WriteTo\(io.Writer\) \(int64, error\)`
+
+func (T) WriteTo(w io.Writer, more, args int) {} // ok - clearly not io.WriterTo
+
 type I interface {
 	ReadByte() byte // want `should have signature ReadByte\(\) \(byte, error\)`
 }
diff --git a/go/analysis/passes/structtag/structtag.go b/go/analysis/passes/structtag/structtag.go
index 2b67c37..bcdb042 100644
--- a/go/analysis/passes/structtag/structtag.go
+++ b/go/analysis/passes/structtag/structtag.go
@@ -56,6 +56,13 @@
 
 // checkCanonicalFieldTag checks a single struct field tag.
 func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, seen *map[[2]string]token.Pos) {
+	switch pass.Pkg.Path() {
+	case "encoding/json", "encoding/xml":
+		// These packages know how to use their own APIs.
+		// Sometimes they are testing what happens to incorrect programs.
+		return
+	}
+
 	for _, key := range checkTagDups {
 		checkTagDuplicates(pass, tag, key, field, field, seen)
 	}
diff --git a/go/analysis/passes/unmarshal/unmarshal.go b/go/analysis/passes/unmarshal/unmarshal.go
index 6cf4358..d019ece 100644
--- a/go/analysis/passes/unmarshal/unmarshal.go
+++ b/go/analysis/passes/unmarshal/unmarshal.go
@@ -29,6 +29,13 @@
 }
 
 func run(pass *analysis.Pass) (interface{}, error) {
+	switch pass.Pkg.Path() {
+	case "encoding/gob", "encoding/json", "encoding/xml":
+		// These packages know how to use their own APIs.
+		// Sometimes they are testing what happens to incorrect programs.
+		return nil, nil
+	}
+
 	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
 
 	nodeFilter := []ast.Node{