cmd/compile: add -asan option

The -asan option causes the compiler to add instrumentation for the
C/C++ address sanitizer.  Every memory read/write will be replaced
by a call to asanread/asanwrite.

This CL also inserts asan instrumentation during SSA building.

This CL passes tests but is not usable by itself. The actual
implementation of asanread/asanwrite in the runtime package, and
support for -asan in the go tool and tests, will follow in subsequent
CLs.

Updates #44853.

Change-Id: Ia18c9c5d5c351857420d2f6835f0daec2ad31096
Reviewed-on: https://go-review.googlesource.com/c/go/+/298611
Trust: fannie zhang <Fannie.Zhang@arm.com>
Run-TryBot: fannie zhang <Fannie.Zhang@arm.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
diff --git a/src/cmd/compile/doc.go b/src/cmd/compile/doc.go
index b68ef27..ef7fa86 100644
--- a/src/cmd/compile/doc.go
+++ b/src/cmd/compile/doc.go
@@ -44,6 +44,8 @@
 		Print compiler version and exit.
 	-asmhdr file
 		Write assembly header to file.
+	-asan
+		Insert calls to C/C++ address sanitizer.
 	-buildid id
 		Record id as the build id in the export metadata.
 	-blockprofile file
diff --git a/src/cmd/compile/internal/base/base.go b/src/cmd/compile/internal/base/base.go
index 4c2516f..be6d49f 100644
--- a/src/cmd/compile/internal/base/base.go
+++ b/src/cmd/compile/internal/base/base.go
@@ -67,6 +67,7 @@
 	"runtime",
 	"runtime/race",
 	"runtime/msan",
+	"runtime/asan",
 	"internal/cpu",
 }
 
diff --git a/src/cmd/compile/internal/base/flag.go b/src/cmd/compile/internal/base/flag.go
index 241f5da..51938e8 100644
--- a/src/cmd/compile/internal/base/flag.go
+++ b/src/cmd/compile/internal/base/flag.go
@@ -84,6 +84,7 @@
 
 	// Longer names
 	AsmHdr             string       "help:\"write assembly header to `file`\""
+	ASan               bool         "help:\"build code compatible with C/C++ address sanitizer\""
 	Bench              string       "help:\"append benchmark times to `file`\""
 	BlockProfile       string       "help:\"write block profile to `file`\""
 	BuildID            string       "help:\"record `id` as the build id in the export metadata\""
@@ -177,6 +178,9 @@
 	if Flag.MSan && !sys.MSanSupported(buildcfg.GOOS, buildcfg.GOARCH) {
 		log.Fatalf("%s/%s does not support -msan", buildcfg.GOOS, buildcfg.GOARCH)
 	}
+	if Flag.ASan && !sys.ASanSupported(buildcfg.GOOS, buildcfg.GOARCH) {
+		log.Fatalf("%s/%s does not support -asan", buildcfg.GOOS, buildcfg.GOARCH)
+	}
 	if Flag.Race && !sys.RaceDetectorSupported(buildcfg.GOOS, buildcfg.GOARCH) {
 		log.Fatalf("%s/%s does not support -race", buildcfg.GOOS, buildcfg.GOARCH)
 	}
@@ -217,12 +221,16 @@
 		}
 		Flag.LowerO = p + suffix
 	}
-
-	if Flag.Race && Flag.MSan {
+	switch {
+	case Flag.Race && Flag.MSan:
 		log.Fatal("cannot use both -race and -msan")
+	case Flag.Race && Flag.ASan:
+		log.Fatal("cannot use both -race and -asan")
+	case Flag.MSan && Flag.ASan:
+		log.Fatal("cannot use both -msan and -asan")
 	}
-	if Flag.Race || Flag.MSan {
-		// -race and -msan imply -d=checkptr for now.
+	if Flag.Race || Flag.MSan || Flag.ASan {
+		// -race, -msan and -asan imply -d=checkptr for now.
 		if Debug.Checkptr == -1 { // if not set explicitly
 			Debug.Checkptr = 1
 		}
diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go
index 74b2157..ed81ef7 100644
--- a/src/cmd/compile/internal/gc/main.go
+++ b/src/cmd/compile/internal/gc/main.go
@@ -107,7 +107,7 @@
 	// Record flags that affect the build result. (And don't
 	// record flags that don't, since that would cause spurious
 	// changes in the binary.)
-	dwarfgen.RecordFlags("B", "N", "l", "msan", "race", "shared", "dynlink", "dwarf", "dwarflocationlists", "dwarfbasentries", "smallframes", "spectre")
+	dwarfgen.RecordFlags("B", "N", "l", "msan", "race", "asan", "shared", "dynlink", "dwarf", "dwarflocationlists", "dwarfbasentries", "smallframes", "spectre")
 
 	if !base.EnableTrace && base.Flag.LowerT {
 		log.Fatalf("compiler not built with support for -t")
@@ -149,11 +149,12 @@
 	if base.Compiling(base.NoInstrumentPkgs) {
 		base.Flag.Race = false
 		base.Flag.MSan = false
+		base.Flag.ASan = false
 	}
 
 	ssagen.Arch.LinkArch.Init(base.Ctxt)
 	startProfile()
-	if base.Flag.Race || base.Flag.MSan {
+	if base.Flag.Race || base.Flag.MSan || base.Flag.ASan {
 		base.Flag.Cfg.Instrumenting = true
 	}
 	if base.Flag.Dwarf {
diff --git a/src/cmd/compile/internal/ir/func.go b/src/cmd/compile/internal/ir/func.go
index 18d0b02..41c9607 100644
--- a/src/cmd/compile/internal/ir/func.go
+++ b/src/cmd/compile/internal/ir/func.go
@@ -201,7 +201,7 @@
 	funcNilCheckDisabled         // disable nil checks when compiling this function
 	funcInlinabilityChecked      // inliner has already determined whether the function is inlinable
 	funcExportInline             // include inline body in export data
-	funcInstrumentBody           // add race/msan instrumentation during SSA construction
+	funcInstrumentBody           // add race/msan/asan instrumentation during SSA construction
 	funcOpenCodedDeferDisallowed // can't do open-coded defers
 	funcClosureCalled            // closure is only immediately called; used by escape analysis
 )
diff --git a/src/cmd/compile/internal/ir/symtab.go b/src/cmd/compile/internal/ir/symtab.go
index 1435e43..b204a1d 100644
--- a/src/cmd/compile/internal/ir/symtab.go
+++ b/src/cmd/compile/internal/ir/symtab.go
@@ -15,6 +15,8 @@
 	AssertE2I2        *obj.LSym
 	AssertI2I         *obj.LSym
 	AssertI2I2        *obj.LSym
+	Asanread          *obj.LSym
+	Asanwrite         *obj.LSym
 	CheckPtrAlignment *obj.LSym
 	Deferproc         *obj.LSym
 	DeferprocStack    *obj.LSym
diff --git a/src/cmd/compile/internal/noder/import.go b/src/cmd/compile/internal/noder/import.go
index 0aaf894..58dffba 100644
--- a/src/cmd/compile/internal/noder/import.go
+++ b/src/cmd/compile/internal/noder/import.go
@@ -127,6 +127,8 @@
 			suffix = "_race"
 		} else if base.Flag.MSan {
 			suffix = "_msan"
+		} else if base.Flag.ASan {
+			suffix = "_asan"
 		}
 
 		if file, err := os.Open(fmt.Sprintf("%s/pkg/%s_%s%s/%s.a", buildcfg.GOROOT, buildcfg.GOOS, buildcfg.GOARCH, suffix, path)); err == nil {
diff --git a/src/cmd/compile/internal/reflectdata/reflect.go b/src/cmd/compile/internal/reflectdata/reflect.go
index b4ed96c..ce41b8c 100644
--- a/src/cmd/compile/internal/reflectdata/reflect.go
+++ b/src/cmd/compile/internal/reflectdata/reflect.go
@@ -1413,6 +1413,9 @@
 		if base.Flag.MSan {
 			dimportpath(types.NewPkg("runtime/msan", ""))
 		}
+		if base.Flag.ASan {
+			dimportpath(types.NewPkg("runtime/asan", ""))
+		}
 
 		dimportpath(types.NewPkg("main", ""))
 	}
diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go
index 5a958a5..6b595ea 100644
--- a/src/cmd/compile/internal/ssagen/ssa.go
+++ b/src/cmd/compile/internal/ssagen/ssa.go
@@ -108,6 +108,8 @@
 	ir.Syms.Msanread = typecheck.LookupRuntimeFunc("msanread")
 	ir.Syms.Msanwrite = typecheck.LookupRuntimeFunc("msanwrite")
 	ir.Syms.Msanmove = typecheck.LookupRuntimeFunc("msanmove")
+	ir.Syms.Asanread = typecheck.LookupRuntimeFunc("asanread")
+	ir.Syms.Asanwrite = typecheck.LookupRuntimeFunc("asanwrite")
 	ir.Syms.Newobject = typecheck.LookupRuntimeFunc("newobject")
 	ir.Syms.Newproc = typecheck.LookupRuntimeFunc("newproc")
 	ir.Syms.Panicdivide = typecheck.LookupRuntimeFunc("panicdivide")
@@ -1245,10 +1247,10 @@
 }
 
 // instrumentFields instruments a read/write operation on addr.
-// If it is instrumenting for MSAN and t is a struct type, it instruments
+// If it is instrumenting for MSAN or ASAN and t is a struct type, it instruments
 // operation for each field, instead of for the whole struct.
 func (s *state) instrumentFields(t *types.Type, addr *ssa.Value, kind instrumentKind) {
-	if !base.Flag.MSan || !t.IsStruct() {
+	if !(base.Flag.MSan || base.Flag.ASan) || !t.IsStruct() {
 		s.instrument(t, addr, kind)
 		return
 	}
@@ -1327,6 +1329,16 @@
 		default:
 			panic("unreachable")
 		}
+	} else if base.Flag.ASan {
+		switch kind {
+		case instrumentRead:
+			fn = ir.Syms.Asanread
+		case instrumentWrite:
+			fn = ir.Syms.Asanwrite
+		default:
+			panic("unreachable")
+		}
+		needWidth = true
 	} else {
 		panic("unreachable")
 	}
@@ -3002,7 +3014,7 @@
 		}
 		// If n is addressable and can't be represented in
 		// SSA, then load just the selected field. This
-		// prevents false memory dependencies in race/msan
+		// prevents false memory dependencies in race/msan/asan
 		// instrumentation.
 		if ir.IsAddressable(n) && !s.canSSA(n) {
 			p := s.addr(n)
diff --git a/src/cmd/compile/internal/typecheck/builtin.go b/src/cmd/compile/internal/typecheck/builtin.go
index 524360e..67597ce 100644
--- a/src/cmd/compile/internal/typecheck/builtin.go
+++ b/src/cmd/compile/internal/typecheck/builtin.go
@@ -192,6 +192,8 @@
 	{"msanread", funcTag, 140},
 	{"msanwrite", funcTag, 140},
 	{"msanmove", funcTag, 141},
+	{"asanread", funcTag, 140},
+	{"asanwrite", funcTag, 140},
 	{"checkptrAlignment", funcTag, 142},
 	{"checkptrArithmetic", funcTag, 144},
 	{"libfuzzerTraceCmp1", funcTag, 145},
diff --git a/src/cmd/compile/internal/typecheck/builtin/runtime.go b/src/cmd/compile/internal/typecheck/builtin/runtime.go
index 66641fb..04ae4f2 100644
--- a/src/cmd/compile/internal/typecheck/builtin/runtime.go
+++ b/src/cmd/compile/internal/typecheck/builtin/runtime.go
@@ -250,6 +250,10 @@
 func msanwrite(addr, size uintptr)
 func msanmove(dst, src, size uintptr)
 
+// address sanitizer
+func asanread(addr, size uintptr)
+func asanwrite(addr, size uintptr)
+
 func checkptrAlignment(unsafe.Pointer, *byte, uintptr)
 func checkptrArithmetic(unsafe.Pointer, []unsafe.Pointer)
 
diff --git a/src/cmd/internal/sys/supported.go b/src/cmd/internal/sys/supported.go
index 0d2bad9..e6c56fb 100644
--- a/src/cmd/internal/sys/supported.go
+++ b/src/cmd/internal/sys/supported.go
@@ -34,6 +34,17 @@
 	}
 }
 
+// ASanSupported reports whether goos/goarch supports the address
+// sanitizer option.
+func ASanSupported(goos, goarch string) bool {
+	switch goos {
+	case "linux":
+		return goarch == "arm64" || goarch == "amd64"
+	default:
+		return false
+	}
+}
+
 // MustLinkExternal reports whether goos/goarch requires external linking.
 // (This is the opposite of internal/testenv.CanInternalLink. Keep them in sync.)
 func MustLinkExternal(goos, goarch string) bool {