From 9ae8f71c9431d287893443fa2b7fbdb72a9b56a2 Mon Sep 17 00:00:00 2001
From: Jeremy Faller
Date: Mon, 3 Aug 2020 13:19:46 -0400
Subject: [dev.link] cmd/link: stop renumbering files for pclntab generation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Creates two new symbols: runtime.cutab, and runtime.filetab, and strips
the filenames out of runtime.pclntab_old.
All stats are for cmd/compile.
Time:
Pclntab_GC 48.2ms ± 3% 45.5ms ± 9% -5.47% (p=0.004 n=9+9)
Alloc/op:
Pclntab_GC 30.0MB ± 0% 29.5MB ± 0% -1.88% (p=0.000 n=10+10)
Allocs/op:
Pclntab_GC 90.4k ± 0% 73.1k ± 0% -19.11% (p=0.000 n=10+10)
live-B:
Pclntab_GC 29.1M ± 0% 29.2M ± 0% +0.10% (p=0.000 n=10+10)
binary sizes:
NEW: 18565600
OLD: 18532768
The size differences in the binary are caused by the increased size of
the Func objects, and (less likely) some extra alignment padding needed
as a result. This is probably the maximum increase in size we'll size
from the pclntab reworking.
Change-Id: Idd95a9b159fea46f7701cfe6506813b88257fbea
Reviewed-on: https://go-review.googlesource.com/c/go/+/246497
Run-TryBot: Jeremy Faller
TryBot-Result: Gobot Gobot
Reviewed-by: Than McIntosh
Reviewed-by: Austin Clements
---
src/cmd/link/internal/ld/data.go | 4 +
src/cmd/link/internal/ld/link.go | 1 -
src/cmd/link/internal/ld/pcln.go | 303 ++++++++++++++++++++-----------------
src/cmd/link/internal/ld/symtab.go | 12 +-
4 files changed, 175 insertions(+), 145 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go
index dc7096ea8c..a551d46403 100644
--- a/src/cmd/link/internal/ld/data.go
+++ b/src/cmd/link/internal/ld/data.go
@@ -1923,6 +1923,8 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pclntab", 0), sect)
ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pcheader", 0), sect)
ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.funcnametab", 0), sect)
+ ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.cutab", 0), sect)
+ ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.filetab", 0), sect)
ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pclntab_old", 0), sect)
ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.epclntab", 0), sect)
if ctxt.HeadType == objabi.Haix {
@@ -2507,6 +2509,8 @@ func (ctxt *Link) address() []*sym.Segment {
ctxt.xdefine("runtime.pclntab", sym.SRODATA, int64(pclntab.Vaddr))
ctxt.defineInternal("runtime.pcheader", sym.SRODATA)
ctxt.defineInternal("runtime.funcnametab", sym.SRODATA)
+ ctxt.defineInternal("runtime.cutab", sym.SRODATA)
+ ctxt.defineInternal("runtime.filetab", sym.SRODATA)
ctxt.defineInternal("runtime.pclntab_old", sym.SRODATA)
ctxt.xdefine("runtime.epclntab", sym.SRODATA, int64(pclntab.Vaddr+pclntab.Length))
ctxt.xdefine("runtime.noptrdata", sym.SNOPTRDATA, int64(noptr.Vaddr))
diff --git a/src/cmd/link/internal/ld/link.go b/src/cmd/link/internal/ld/link.go
index a2c8552e94..f26d051a49 100644
--- a/src/cmd/link/internal/ld/link.go
+++ b/src/cmd/link/internal/ld/link.go
@@ -71,7 +71,6 @@ type Link struct {
LibraryByPkg map[string]*sym.Library
Shlibs []Shlib
Textp []loader.Sym
- NumFilesyms int
Moduledata loader.Sym
PackageFile map[string]string
diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go
index 30e0bdc839..c7535f6a61 100644
--- a/src/cmd/link/internal/ld/pcln.go
+++ b/src/cmd/link/internal/ld/pcln.go
@@ -6,16 +6,11 @@ package ld
import (
"cmd/internal/goobj"
- "cmd/internal/obj"
"cmd/internal/objabi"
- "cmd/internal/src"
"cmd/internal/sys"
"cmd/link/internal/loader"
"cmd/link/internal/sym"
- "encoding/binary"
"fmt"
- "log"
- "math"
"os"
"path/filepath"
"strings"
@@ -23,18 +18,13 @@ import (
// oldPclnState holds state information used during pclntab generation. Here
// 'ldr' is just a pointer to the context's loader, 'deferReturnSym' is the
-// index for the symbol "runtime.deferreturn", 'nameToOffset' is a helper
-// function for capturing function names, 'numberedFiles' records the file
-// number assigned to a given file symbol, 'filepaths' is a slice of expanded
-// paths (indexed by file number).
+// index for the symbol "runtime.deferreturn",
//
// NB: This is deprecated, and will be eliminated when pclntab_old is
// eliminated.
type oldPclnState struct {
ldr *loader.Loader
deferReturnSym loader.Sym
- numberedFiles map[string]int64
- filepaths []string
}
// pclntab holds the state needed for pclntab generation.
@@ -42,9 +32,6 @@ type pclntab struct {
// The first and last functions found.
firstFunc, lastFunc loader.Sym
- // The offset to the filetab.
- filetabOffset int32
-
// Running total size of pclntab.
size int64
@@ -54,6 +41,8 @@ type pclntab struct {
pcheader loader.Sym
funcnametab loader.Sym
findfunctab loader.Sym
+ cutab loader.Sym
+ filetab loader.Sym
// The number of functions + number of TEXT sections - 1. This is such an
// unexpected value because platforms that have more than one TEXT section
@@ -64,6 +53,9 @@ type pclntab struct {
// On most platforms this is the number of reachable functions.
nfunc int32
+ // The number of filenames in runtime.filetab.
+ nfiles uint32
+
// maps the function symbol to offset in runtime.funcnametab
// This doesn't need to reside in the state once pclntab_old's been
// deleted -- it can live in generateFuncnametab.
@@ -89,11 +81,6 @@ func makeOldPclnState(ctxt *Link) *oldPclnState {
state := &oldPclnState{
ldr: ldr,
deferReturnSym: drs,
- numberedFiles: make(map[string]int64),
- // NB: initial entry in filepaths below is to reserve the zero value,
- // so that when we do a map lookup in numberedFiles fails, it will not
- // return a value slot in filepaths.
- filepaths: []string{""},
}
return state
@@ -153,78 +140,6 @@ func ftabaddstring(ftab *loader.SymbolBuilder, s string) int32 {
return int32(start)
}
-// numberfile assigns a file number to the file if it hasn't been assigned already.
-// This funciton looks at a CU's file at index [i], and if it's a new filename,
-// stores that filename in the global file table, and adds it to the map lookup
-// for renumbering pcfile.
-func (state *oldPclnState) numberfile(cu *sym.CompilationUnit, i goobj.CUFileIndex) int64 {
- file := cu.FileTable[i]
- if val, ok := state.numberedFiles[file]; ok {
- return val
- }
- path := file
- if strings.HasPrefix(path, src.FileSymPrefix) {
- path = file[len(src.FileSymPrefix):]
- }
- val := int64(len(state.filepaths))
- state.numberedFiles[file] = val
- state.filepaths = append(state.filepaths, expandGoroot(path))
- return val
-}
-
-func (state *oldPclnState) fileVal(cu *sym.CompilationUnit, i int32) int64 {
- file := cu.FileTable[i]
- if val, ok := state.numberedFiles[file]; ok {
- return val
- }
- panic("should have been numbered first")
-}
-
-func (state *oldPclnState) renumberfiles(ctxt *Link, cu *sym.CompilationUnit, fi loader.FuncInfo, d *sym.Pcdata) {
- // Give files numbers.
- nf := fi.NumFile()
- for i := uint32(0); i < nf; i++ {
- state.numberfile(cu, fi.File(int(i)))
- }
-
- buf := make([]byte, binary.MaxVarintLen32)
- newval := int32(-1)
- var out sym.Pcdata
- it := obj.NewPCIter(uint32(ctxt.Arch.MinLC))
- for it.Init(d.P); !it.Done; it.Next() {
- // value delta
- oldval := it.Value
-
- var val int32
- if oldval == -1 {
- val = -1
- } else {
- if oldval < 0 || oldval >= int32(len(cu.FileTable)) {
- log.Fatalf("bad pcdata %d", oldval)
- }
- val = int32(state.fileVal(cu, oldval))
- }
-
- dv := val - newval
- newval = val
-
- // value
- n := binary.PutVarint(buf, int64(dv))
- out.P = append(out.P, buf[:n]...)
-
- // pc delta
- pc := (it.NextPC - it.PC) / it.PCScale
- n = binary.PutUvarint(buf, uint64(pc))
- out.P = append(out.P, buf[:n]...)
- }
-
- // terminating value delta
- // we want to write varint-encoded 0, which is just 0
- out.P = append(out.P, 0)
-
- *d = out
-}
-
// onlycsymbol looks at a symbol's name to report whether this is a
// symbol that is referenced by C code
func onlycsymbol(sname string) bool {
@@ -308,12 +223,7 @@ func (state *oldPclnState) genInlTreeSym(cu *sym.CompilationUnit, fi loader.Func
ninl := fi.NumInlTree()
for i := 0; i < int(ninl); i++ {
call := fi.InlTree(i)
- // Usually, call.File is already numbered since the file
- // shows up in the Pcfile table. However, two inlined calls
- // might overlap exactly so that only the innermost file
- // appears in the Pcfile table. In that case, this assigns
- // the outer file a number.
- val := state.numberfile(cu, call.File)
+ val := call.File
nameoff, ok := newState.funcNameOffset[call.Func]
if !ok {
panic("couldn't find function name offset")
@@ -359,11 +269,14 @@ func (state *pclntab) generatePCHeader(ctxt *Link) {
header.SetUint8(ctxt.Arch, 6, uint8(ctxt.Arch.MinLC))
header.SetUint8(ctxt.Arch, 7, uint8(ctxt.Arch.PtrSize))
off := header.SetUint(ctxt.Arch, 8, uint64(state.nfunc))
+ off = header.SetUint(ctxt.Arch, off, uint64(state.nfiles))
off = writeSymOffset(off, state.funcnametab)
+ off = writeSymOffset(off, state.cutab)
+ off = writeSymOffset(off, state.filetab)
off = writeSymOffset(off, state.pclntab)
}
- size := int64(8 + 3*ctxt.Arch.PtrSize)
+ size := int64(8 + 6*ctxt.Arch.PtrSize)
state.pcheader = state.addGeneratedSym(ctxt, "runtime.pcheader", size, writeHeader)
}
@@ -417,6 +330,139 @@ func (state *pclntab) generateFuncnametab(ctxt *Link, container loader.Bitmap) {
state.funcnametab = state.addGeneratedSym(ctxt, "runtime.funcnametab", size, writeFuncNameTab)
}
+// walkFilenames walks the filenames in the all reachable functions.
+func walkFilenames(ctxt *Link, container loader.Bitmap, f func(*sym.CompilationUnit, goobj.CUFileIndex)) {
+ ldr := ctxt.loader
+
+ // Loop through all functions, finding the filenames we need.
+ for _, ls := range ctxt.Textp {
+ s := loader.Sym(ls)
+ if !emitPcln(ctxt, s, container) {
+ continue
+ }
+
+ fi := ldr.FuncInfo(s)
+ if !fi.Valid() {
+ continue
+ }
+ fi.Preload()
+
+ cu := ldr.SymUnit(s)
+ for i, nf := 0, int(fi.NumFile()); i < nf; i++ {
+ f(cu, fi.File(i))
+ }
+ for i, ninl := 0, int(fi.NumInlTree()); i < ninl; i++ {
+ call := fi.InlTree(i)
+ f(cu, call.File)
+ }
+ }
+}
+
+// generateFilenameTabs creates LUTs needed for filename lookup. Returns a slice
+// of the index at which each CU begins in runtime.cutab.
+//
+// Function objects keep track of the files they reference to print the stack.
+// This function creates a per-CU list of filenames if CU[M] references
+// files[1-N], the following is generated:
+//
+// runtime.cutab:
+// CU[M]
+// offsetToFilename[0]
+// offsetToFilename[1]
+// ..
+//
+// runtime.filetab
+// filename[0]
+// filename[1]
+//
+// Looking up a filename then becomes:
+// 0) Given a func, and filename index [K]
+// 1) Get Func.CUIndex: M := func.cuOffset
+// 2) Find filename offset: fileOffset := runtime.cutab[M+K]
+// 3) Get the filename: getcstring(runtime.filetab[fileOffset])
+func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.CompilationUnit, container loader.Bitmap) []uint32 {
+ // On a per-CU basis, keep track of all the filenames we need.
+ //
+ // Note, that we store the filenames in a separate section in the object
+ // files, and deduplicate based on the actual value. It would be better to
+ // store the filenames as symbols, using content addressable symbols (and
+ // then not loading extra filenames), and just use the hash value of the
+ // symbol name to do this cataloging.
+ //
+ // TOOD: Store filenames as symbols. (Note this would be easiest if you
+ // also move strings to ALWAYS using the larger content addressable hash
+ // function, and use that hash value for uniqueness testing.)
+ cuEntries := make([]goobj.CUFileIndex, len(compUnits))
+ fileOffsets := make(map[string]uint32)
+
+ // Walk the filenames.
+ // We store the total filename string length we need to load, and the max
+ // file index we've seen per CU so we can calculate how large the
+ // CU->global table needs to be.
+ var fileSize int64
+ walkFilenames(ctxt, container, func(cu *sym.CompilationUnit, i goobj.CUFileIndex) {
+ // Note we use the raw filename for lookup, but use the expanded filename
+ // when we save the size.
+ filename := cu.FileTable[i]
+ if _, ok := fileOffsets[filename]; !ok {
+ fileOffsets[filename] = uint32(fileSize)
+ fileSize += int64(len(expandFile(filename)) + 1) // NULL terminate
+ }
+
+ // Find the maximum file index we've seen.
+ if cuEntries[cu.PclnIndex] < i+1 {
+ cuEntries[cu.PclnIndex] = i + 1 // Store max + 1
+ }
+ })
+
+ // Calculate the size of the runtime.cutab variable.
+ var totalEntries uint32
+ cuOffsets := make([]uint32, len(cuEntries))
+ for i, entries := range cuEntries {
+ // Note, cutab is a slice of uint32, so an offset to a cu's entry is just the
+ // running total of all cu indices we've needed to store so far, not the
+ // number of bytes we've stored so far.
+ cuOffsets[i] = totalEntries
+ totalEntries += uint32(entries)
+ }
+
+ // Write cutab.
+ writeCutab := func(ctxt *Link, s loader.Sym) {
+ sb := ctxt.loader.MakeSymbolUpdater(s)
+
+ var off int64
+ for i, max := range cuEntries {
+ // Write the per CU LUT.
+ cu := compUnits[i]
+ for j := goobj.CUFileIndex(0); j < max; j++ {
+ fileOffset, ok := fileOffsets[cu.FileTable[j]]
+ if !ok {
+ // We're looping through all possible file indices. It's possible a file's
+ // been deadcode eliminated, and although it's a valid file in the CU, it's
+ // not needed in this binary. When that happens, use an invalid offset.
+ fileOffset = ^uint32(0)
+ }
+ off = sb.SetUint32(ctxt.Arch, off, fileOffset)
+ }
+ }
+ }
+ state.cutab = state.addGeneratedSym(ctxt, "runtime.cutab", int64(totalEntries*4), writeCutab)
+
+ // Write filetab.
+ writeFiletab := func(ctxt *Link, s loader.Sym) {
+ sb := ctxt.loader.MakeSymbolUpdater(s)
+
+ // Write the strings.
+ for filename, loc := range fileOffsets {
+ sb.AddStringAt(int64(loc), expandFile(filename))
+ }
+ }
+ state.nfiles = uint32(len(fileOffsets))
+ state.filetab = state.addGeneratedSym(ctxt, "runtime.filetab", fileSize, writeFiletab)
+
+ return cuOffsets
+}
+
// pclntab initializes the pclntab symbol with
// runtime function and file name information.
@@ -425,7 +471,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
// Go 1.2's symtab layout is documented in golang.org/s/go12symtab, but the
// layout and data has changed since that time.
//
- // As of July 2020, here's the layout of pclntab:
+ // As of August 2020, here's the layout of pclntab:
//
// .gopclntab/__gopclntab [elf/macho section]
// runtime.pclntab
@@ -438,17 +484,23 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
// offset to runtime.pclntab_old from beginning of runtime.pcheader
//
// runtime.funcnametab
- // []list of null terminated function names
+ // []list of null terminated function names
+ //
+ // runtime.cutab
+ // for i=0..#CUs
+ // for j=0..#max used file index in CU[i]
+ // uint32 offset into runtime.filetab for the filename[j]
+ //
+ // runtime.filetab
+ // []null terminated filename strings
//
// runtime.pclntab_old
// function table, alternating PC and offset to func struct [each entry thearch.ptrsize bytes]
// end PC [thearch.ptrsize bytes]
- // offset to file table [4 bytes]
// func structures, pcdata tables.
- // filetable
oldState := makeOldPclnState(ctxt)
- state, _ := makePclntab(ctxt, container)
+ state, compUnits := makePclntab(ctxt, container)
ldr := ctxt.loader
state.carrier = ldr.LookupOrCreateSym("runtime.pclntab", 0)
@@ -461,6 +513,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
state.pclntab = ldr.LookupOrCreateSym("runtime.pclntab_old", 0)
state.generatePCHeader(ctxt)
state.generateFuncnametab(ctxt, container)
+ cuOffsets := state.generateFilenameTabs(ctxt, compUnits, container)
funcdataBytes := int64(0)
ldr.SetCarrierSym(state.pclntab, state.carrier)
@@ -583,7 +636,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
// fixed size of struct, checked below
off := funcstart
- end := funcstart + int32(ctxt.Arch.PtrSize) + 3*4 + 5*4 + int32(len(pcdata))*4 + int32(len(funcdata))*int32(ctxt.Arch.PtrSize)
+ end := funcstart + int32(ctxt.Arch.PtrSize) + 3*4 + 6*4 + int32(len(pcdata))*4 + int32(len(funcdata))*int32(ctxt.Arch.PtrSize)
if len(funcdata) > 0 && (end&int32(ctxt.Arch.PtrSize-1) != 0) {
end += 4
}
@@ -616,17 +669,6 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
pcsp = sym.Pcdata{P: fi.Pcsp()}
pcfile = sym.Pcdata{P: fi.Pcfile()}
pcline = sym.Pcdata{P: fi.Pcline()}
- oldState.renumberfiles(ctxt, cu, fi, &pcfile)
- if false {
- // Sanity check the new numbering
- it := obj.NewPCIter(uint32(ctxt.Arch.MinLC))
- for it.Init(pcfile.P); !it.Done; it.Next() {
- if it.Value < 1 || it.Value > int32(len(oldState.numberedFiles)) {
- ctxt.Errorf(s, "bad file number in pcfile: %d not in range [1, %d]\n", it.Value, len(oldState.numberedFiles))
- errorexit()
- }
- }
- }
}
if fi.Valid() && fi.NumInlTree() > 0 {
@@ -641,15 +683,12 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
off = writepctab(off, pcline.P)
off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(len(pcdata))))
- // Store the compilation unit index.
- cuIdx := ^uint16(0)
+ // Store the offset to compilation unit's file table.
+ cuIdx := ^uint32(0)
if cu := ldr.SymUnit(s); cu != nil {
- if cu.PclnIndex > math.MaxUint16 {
- panic("cu limit reached.")
- }
- cuIdx = uint16(cu.PclnIndex)
+ cuIdx = cuOffsets[cu.PclnIndex]
}
- off = int32(ftab.SetUint16(ctxt.Arch, int64(off), cuIdx))
+ off = int32(ftab.SetUint32(ctxt.Arch, int64(off), cuIdx))
// funcID uint8
var funcID objabi.FuncID
@@ -658,6 +697,8 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
}
off = int32(ftab.SetUint8(ctxt.Arch, int64(off), uint8(funcID)))
+ off += 2 // pad
+
// nfuncdata must be the final entry.
off = int32(ftab.SetUint8(ctxt.Arch, int64(off), uint8(len(funcdata))))
for i := range pcdata {
@@ -694,26 +735,8 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
// Final entry of table is just end pc.
setAddr(ftab, ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize), state.lastFunc, ldr.SymSize(state.lastFunc))
- // Start file table.
- dSize := len(ftab.Data())
- start := int32(dSize)
- start += int32(-dSize) & (int32(ctxt.Arch.PtrSize) - 1)
- state.filetabOffset = start
- ftab.SetUint32(ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize)+int64(ctxt.Arch.PtrSize), uint32(start))
-
- nf := len(oldState.numberedFiles)
- ftab.Grow(int64(start) + int64((nf+1)*4))
- ftab.SetUint32(ctxt.Arch, int64(start), uint32(nf+1))
- for i := nf; i > 0; i-- {
- path := oldState.filepaths[i]
- val := int64(i)
- ftab.SetUint32(ctxt.Arch, int64(start)+val*4, uint32(ftabaddstring(ftab, path)))
- }
-
ftab.SetSize(int64(len(ftab.Data())))
- ctxt.NumFilesyms = len(oldState.numberedFiles)
-
if ctxt.Debugvlog != 0 {
ctxt.Logf("pclntab=%d bytes, funcdata total %d bytes\n", ftab.Size(), funcdataBytes)
}
diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go
index bc880955b8..d05b98f04a 100644
--- a/src/cmd/link/internal/ld/symtab.go
+++ b/src/cmd/link/internal/ld/symtab.go
@@ -619,6 +619,14 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind {
moduledata.AddAddr(ctxt.Arch, pcln.funcnametab)
moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.funcnametab)))
moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.funcnametab)))
+ // The cutab slice
+ moduledata.AddAddr(ctxt.Arch, pcln.cutab)
+ moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.cutab)))
+ moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.cutab)))
+ // The filetab slice
+ moduledata.AddAddr(ctxt.Arch, pcln.filetab)
+ moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.filetab)))
+ moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.filetab)))
// The pclntab slice
moduledata.AddAddr(ctxt.Arch, pcln.pclntab)
moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.pclntab)))
@@ -627,10 +635,6 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind {
moduledata.AddAddr(ctxt.Arch, pcln.pclntab)
moduledata.AddUint(ctxt.Arch, uint64(pcln.nfunc+1))
moduledata.AddUint(ctxt.Arch, uint64(pcln.nfunc+1))
- // The filetab slice
- moduledata.AddAddrPlus(ctxt.Arch, pcln.pclntab, int64(pcln.filetabOffset))
- moduledata.AddUint(ctxt.Arch, uint64(ctxt.NumFilesyms)+1)
- moduledata.AddUint(ctxt.Arch, uint64(ctxt.NumFilesyms)+1)
// findfunctab
moduledata.AddAddr(ctxt.Arch, pcln.findfunctab)
// minpc, maxpc
--
cgit v1.3
From b249703e3c53cd7f1e5f808fb2f03714fec44b43 Mon Sep 17 00:00:00 2001
From: Jeremy Faller
Date: Wed, 12 Aug 2020 12:54:03 -0400
Subject: [dev.link] cmd/compile, cmd/asm: add length to hashed symbols
While working on deduplicating pcdata, I found that the following hashed
symbols would result in the same:
[] == [0,0,0,0....]
This makes using content addressable symbols untenable for pcdata.
Adding the length to the hash keeps the dream alive.
No difference in binary size (darwin, cmd/compile), spurious
improvements in DWARF phase memory.
Change-Id: I21101f7754a3d870922b0dea39c947cc8509432f
Reviewed-on: https://go-review.googlesource.com/c/go/+/247903
Run-TryBot: Jeremy Faller
TryBot-Result: Gobot Gobot
Reviewed-by: Than McIntosh
Reviewed-by: Austin Clements
Reviewed-by: Cherry Zhang
---
src/cmd/internal/obj/objfile.go | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
(limited to 'src/cmd')
diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go
index 7bc4f4992e..8234697d72 100644
--- a/src/cmd/internal/obj/objfile.go
+++ b/src/cmd/internal/obj/objfile.go
@@ -372,10 +372,22 @@ func contentHash64(s *LSym) goobj.Hash64Type {
// hashed symbols.
func (w *writer) contentHash(s *LSym) goobj.HashType {
h := sha1.New()
+ var tmp [14]byte
+
+ // Include the size of the symbol in the hash.
+ // This preserves the length of symbols, preventing the following two symbols
+ // from hashing the same:
+ //
+ // [2]int{1,2} ≠ [10]int{1,2,0,0,0...}
+ //
+ // In this case, if the smaller symbol is alive, the larger is not kept unless
+ // needed.
+ binary.LittleEndian.PutUint64(tmp[:8], uint64(s.Size))
+ h.Write(tmp[:8])
+
// The compiler trims trailing zeros _sometimes_. We just do
// it always.
h.Write(bytes.TrimRight(s.P, "\x00"))
- var tmp [14]byte
for i := range s.R {
r := &s.R[i]
binary.LittleEndian.PutUint32(tmp[:4], uint32(r.Off))
--
cgit v1.3
From 5387cdcb24a07f5d0d49d5105ced2b69e6aafde9 Mon Sep 17 00:00:00 2001
From: Jeremy Faller
Date: Fri, 7 Aug 2020 11:31:20 -0400
Subject: [dev.link] cmd/link, cmd/compile: create content addressable pcdata
syms
Switch pcdata over to content addressable symbols. This is the last
step before removing these from pclntab_old.
No meaningful benchmarks changes come from this work.
Change-Id: I3f74f3d6026a278babe437c8010e22992c92bd89
Reviewed-on: https://go-review.googlesource.com/c/go/+/247399
Reviewed-by: Austin Clements
Reviewed-by: Than McIntosh
---
src/cmd/internal/goobj/funcinfo.go | 82 ++++++++++++++++++----------------
src/cmd/internal/goobj/objfile.go | 12 +++--
src/cmd/internal/obj/link.go | 15 +++----
src/cmd/internal/obj/objfile.go | 71 ++++++++++++++++++++++-------
src/cmd/internal/obj/pcln.go | 55 ++++++++++++++---------
src/cmd/internal/objfile/goobj.go | 16 ++++---
src/cmd/link/internal/ld/dwarf.go | 2 +-
src/cmd/link/internal/ld/lib.go | 2 +-
src/cmd/link/internal/ld/pcln.go | 13 +++---
src/cmd/link/internal/loader/loader.go | 46 +++++++++----------
10 files changed, 182 insertions(+), 132 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/internal/goobj/funcinfo.go b/src/cmd/internal/goobj/funcinfo.go
index e0e6068b4b..2cca8f6c4e 100644
--- a/src/cmd/internal/goobj/funcinfo.go
+++ b/src/cmd/internal/goobj/funcinfo.go
@@ -23,12 +23,11 @@ type FuncInfo struct {
Locals uint32
FuncID objabi.FuncID
- Pcsp uint32
- Pcfile uint32
- Pcline uint32
- Pcinline uint32
- Pcdata []uint32
- PcdataEnd uint32
+ Pcsp SymRef
+ Pcfile SymRef
+ Pcline SymRef
+ Pcinline SymRef
+ Pcdata []SymRef
Funcdataoff []uint32
File []CUFileIndex
@@ -41,20 +40,24 @@ func (a *FuncInfo) Write(w *bytes.Buffer) {
binary.LittleEndian.PutUint32(b[:], x)
w.Write(b[:])
}
+ writeSymRef := func(s SymRef) {
+ writeUint32(s.PkgIdx)
+ writeUint32(s.SymIdx)
+ }
writeUint32(a.Args)
writeUint32(a.Locals)
writeUint32(uint32(a.FuncID))
- writeUint32(a.Pcsp)
- writeUint32(a.Pcfile)
- writeUint32(a.Pcline)
- writeUint32(a.Pcinline)
+ writeSymRef(a.Pcsp)
+ writeSymRef(a.Pcfile)
+ writeSymRef(a.Pcline)
+ writeSymRef(a.Pcinline)
writeUint32(uint32(len(a.Pcdata)))
- for _, x := range a.Pcdata {
- writeUint32(x)
+ for _, sym := range a.Pcdata {
+ writeSymRef(sym)
}
- writeUint32(a.PcdataEnd)
+
writeUint32(uint32(len(a.Funcdataoff)))
for _, x := range a.Funcdataoff {
writeUint32(x)
@@ -75,21 +78,23 @@ func (a *FuncInfo) Read(b []byte) {
b = b[4:]
return x
}
+ readSymIdx := func() SymRef {
+ return SymRef{readUint32(), readUint32()}
+ }
a.Args = readUint32()
a.Locals = readUint32()
a.FuncID = objabi.FuncID(readUint32())
- a.Pcsp = readUint32()
- a.Pcfile = readUint32()
- a.Pcline = readUint32()
- a.Pcinline = readUint32()
- pcdatalen := readUint32()
- a.Pcdata = make([]uint32, pcdatalen)
+ a.Pcsp = readSymIdx()
+ a.Pcfile = readSymIdx()
+ a.Pcline = readSymIdx()
+ a.Pcinline = readSymIdx()
+ a.Pcdata = make([]SymRef, readUint32())
for i := range a.Pcdata {
- a.Pcdata[i] = readUint32()
+ a.Pcdata[i] = readSymIdx()
}
- a.PcdataEnd = readUint32()
+
funcdataofflen := readUint32()
a.Funcdataoff = make([]uint32, funcdataofflen)
for i := range a.Funcdataoff {
@@ -127,11 +132,13 @@ type FuncInfoLengths struct {
func (*FuncInfo) ReadFuncInfoLengths(b []byte) FuncInfoLengths {
var result FuncInfoLengths
- const numpcdataOff = 28
+ // Offset to the number of pcdata values. This value is determined by counting
+ // the number of bytes until we write pcdata to the file.
+ const numpcdataOff = 44
result.NumPcdata = binary.LittleEndian.Uint32(b[numpcdataOff:])
result.PcdataOff = numpcdataOff + 4
- numfuncdataoffOff := result.PcdataOff + 4*(result.NumPcdata+1)
+ numfuncdataoffOff := result.PcdataOff + 8*result.NumPcdata
result.NumFuncdataoff = binary.LittleEndian.Uint32(b[numfuncdataoffOff:])
result.FuncdataoffOff = numfuncdataoffOff + 4
@@ -154,29 +161,28 @@ func (*FuncInfo) ReadLocals(b []byte) uint32 { return binary.LittleEndian.Uint32
func (*FuncInfo) ReadFuncID(b []byte) uint32 { return binary.LittleEndian.Uint32(b[8:]) }
-// return start and end offsets.
-func (*FuncInfo) ReadPcsp(b []byte) (uint32, uint32) {
- return binary.LittleEndian.Uint32(b[12:]), binary.LittleEndian.Uint32(b[16:])
+func (*FuncInfo) ReadPcsp(b []byte) SymRef {
+ return SymRef{binary.LittleEndian.Uint32(b[12:]), binary.LittleEndian.Uint32(b[16:])}
}
-// return start and end offsets.
-func (*FuncInfo) ReadPcfile(b []byte) (uint32, uint32) {
- return binary.LittleEndian.Uint32(b[16:]), binary.LittleEndian.Uint32(b[20:])
+func (*FuncInfo) ReadPcfile(b []byte) SymRef {
+ return SymRef{binary.LittleEndian.Uint32(b[20:]), binary.LittleEndian.Uint32(b[24:])}
}
-// return start and end offsets.
-func (*FuncInfo) ReadPcline(b []byte) (uint32, uint32) {
- return binary.LittleEndian.Uint32(b[20:]), binary.LittleEndian.Uint32(b[24:])
+func (*FuncInfo) ReadPcline(b []byte) SymRef {
+ return SymRef{binary.LittleEndian.Uint32(b[28:]), binary.LittleEndian.Uint32(b[32:])}
}
-// return start and end offsets.
-func (*FuncInfo) ReadPcinline(b []byte, pcdataoffset uint32) (uint32, uint32) {
- return binary.LittleEndian.Uint32(b[24:]), binary.LittleEndian.Uint32(b[pcdataoffset:])
+func (*FuncInfo) ReadPcinline(b []byte) SymRef {
+ return SymRef{binary.LittleEndian.Uint32(b[36:]), binary.LittleEndian.Uint32(b[40:])}
}
-// return start and end offsets.
-func (*FuncInfo) ReadPcdata(b []byte, pcdataoffset uint32, k uint32) (uint32, uint32) {
- return binary.LittleEndian.Uint32(b[pcdataoffset+4*k:]), binary.LittleEndian.Uint32(b[pcdataoffset+4+4*k:])
+func (*FuncInfo) ReadPcdata(b []byte) []SymRef {
+ syms := make([]SymRef, binary.LittleEndian.Uint32(b[44:]))
+ for i := range syms {
+ syms[i] = SymRef{binary.LittleEndian.Uint32(b[48+i*8:]), binary.LittleEndian.Uint32(b[52+i*8:])}
+ }
+ return syms
}
func (*FuncInfo) ReadFuncdataoff(b []byte, funcdataofffoff uint32, k uint32) int64 {
diff --git a/src/cmd/internal/goobj/objfile.go b/src/cmd/internal/goobj/objfile.go
index 5d4a253024..9a64f96cd6 100644
--- a/src/cmd/internal/goobj/objfile.go
+++ b/src/cmd/internal/goobj/objfile.go
@@ -421,8 +421,11 @@ const (
AuxDwarfLoc
AuxDwarfRanges
AuxDwarfLines
-
- // TODO: more. Pcdata?
+ AuxPcsp
+ AuxPcfile
+ AuxPcline
+ AuxPcinline
+ AuxPcdata
)
func (a *Aux) Type() uint8 { return a[0] }
@@ -827,11 +830,6 @@ func (r *Reader) Data(i uint32) []byte {
return r.BytesAt(base+off, int(end-off))
}
-// AuxDataBase returns the base offset of the aux data block.
-func (r *Reader) PcdataBase() uint32 {
- return r.h.Offsets[BlkPcdata]
-}
-
// NRefName returns the number of referenced symbol names.
func (r *Reader) NRefName() int {
return int(r.h.Offsets[BlkRefName+1]-r.h.Offsets[BlkRefName]) / RefNameSize
diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go
index dc47e51be9..11fab63065 100644
--- a/src/cmd/internal/obj/link.go
+++ b/src/cmd/internal/obj/link.go
@@ -624,11 +624,12 @@ func (s *LSym) CanBeAnSSASym() {
}
type Pcln struct {
- Pcsp Pcdata
- Pcfile Pcdata
- Pcline Pcdata
- Pcinline Pcdata
- Pcdata []Pcdata
+ // Aux symbols for pcln
+ Pcsp *LSym
+ Pcfile *LSym
+ Pcline *LSym
+ Pcinline *LSym
+ Pcdata []*LSym
Funcdata []*LSym
Funcdataoff []int64
UsedFiles map[goobj.CUFileIndex]struct{} // file indices used while generating pcfile
@@ -650,10 +651,6 @@ type Auto struct {
Gotype *LSym
}
-type Pcdata struct {
- P []byte
-}
-
// Link holds the context for writing object code from a compiler
// to be linker input or for reading that input into the linker.
type Link struct {
diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go
index 8234697d72..a2bbdff24e 100644
--- a/src/cmd/internal/obj/objfile.go
+++ b/src/cmd/internal/obj/objfile.go
@@ -185,7 +185,11 @@ func WriteObjFile(ctxt *Link, b *bio.Writer) {
// Pcdata
h.Offsets[goobj.BlkPcdata] = w.Offset()
for _, s := range ctxt.Text { // iteration order must match genFuncInfoSyms
- if s.Func != nil {
+ // Because of the phase order, it's possible that we try to write an invalid
+ // object file, and the Pcln variables haven't been filled in. As such, we
+ // need to check that Pcsp exists, and assume the other pcln variables exist
+ // as well. Tests like test/fixedbugs/issue22200.go demonstrate this issue.
+ if s.Func != nil && s.Func.Pcln.Pcsp != nil {
pc := &s.Func.Pcln
w.Bytes(pc.Pcsp.P)
w.Bytes(pc.Pcfile.P)
@@ -478,6 +482,22 @@ func (w *writer) Aux(s *LSym) {
if s.Func.dwarfDebugLinesSym != nil && s.Func.dwarfDebugLinesSym.Size != 0 {
w.aux1(goobj.AuxDwarfLines, s.Func.dwarfDebugLinesSym)
}
+ if s.Func.Pcln.Pcsp != nil && s.Func.Pcln.Pcsp.Size != 0 {
+ w.aux1(goobj.AuxPcsp, s.Func.Pcln.Pcsp)
+ }
+ if s.Func.Pcln.Pcfile != nil && s.Func.Pcln.Pcfile.Size != 0 {
+ w.aux1(goobj.AuxPcfile, s.Func.Pcln.Pcfile)
+ }
+ if s.Func.Pcln.Pcline != nil && s.Func.Pcln.Pcline.Size != 0 {
+ w.aux1(goobj.AuxPcline, s.Func.Pcln.Pcline)
+ }
+ if s.Func.Pcln.Pcinline != nil && s.Func.Pcln.Pcinline.Size != 0 {
+ w.aux1(goobj.AuxPcinline, s.Func.Pcln.Pcinline)
+ }
+ for _, pcSym := range s.Func.Pcln.Pcdata {
+ w.aux1(goobj.AuxPcdata, pcSym)
+ }
+
}
}
@@ -559,6 +579,19 @@ func nAuxSym(s *LSym) int {
if s.Func.dwarfDebugLinesSym != nil && s.Func.dwarfDebugLinesSym.Size != 0 {
n++
}
+ if s.Func.Pcln.Pcsp != nil && s.Func.Pcln.Pcsp.Size != 0 {
+ n++
+ }
+ if s.Func.Pcln.Pcfile != nil && s.Func.Pcln.Pcfile.Size != 0 {
+ n++
+ }
+ if s.Func.Pcln.Pcline != nil && s.Func.Pcln.Pcline.Size != 0 {
+ n++
+ }
+ if s.Func.Pcln.Pcinline != nil && s.Func.Pcln.Pcinline.Size != 0 {
+ n++
+ }
+ n += len(s.Func.Pcln.Pcdata)
}
return n
}
@@ -566,7 +599,17 @@ func nAuxSym(s *LSym) int {
// generate symbols for FuncInfo.
func genFuncInfoSyms(ctxt *Link) {
infosyms := make([]*LSym, 0, len(ctxt.Text))
- var pcdataoff uint32
+ hashedsyms := make([]*LSym, 0, 4*len(ctxt.Text))
+ preparePcSym := func(s *LSym) *LSym {
+ if s == nil {
+ return s
+ }
+ s.PkgIdx = goobj.PkgIdxHashed
+ s.SymIdx = int32(len(hashedsyms) + len(ctxt.hasheddefs))
+ s.Set(AttrIndexed, true)
+ hashedsyms = append(hashedsyms, s)
+ return s
+ }
var b bytes.Buffer
symidx := int32(len(ctxt.defs))
for _, s := range ctxt.Text {
@@ -579,20 +622,14 @@ func genFuncInfoSyms(ctxt *Link) {
FuncID: objabi.FuncID(s.Func.FuncID),
}
pc := &s.Func.Pcln
- o.Pcsp = pcdataoff
- pcdataoff += uint32(len(pc.Pcsp.P))
- o.Pcfile = pcdataoff
- pcdataoff += uint32(len(pc.Pcfile.P))
- o.Pcline = pcdataoff
- pcdataoff += uint32(len(pc.Pcline.P))
- o.Pcinline = pcdataoff
- pcdataoff += uint32(len(pc.Pcinline.P))
- o.Pcdata = make([]uint32, len(pc.Pcdata))
- for i, pcd := range pc.Pcdata {
- o.Pcdata[i] = pcdataoff
- pcdataoff += uint32(len(pcd.P))
- }
- o.PcdataEnd = pcdataoff
+ o.Pcsp = makeSymRef(preparePcSym(pc.Pcsp))
+ o.Pcfile = makeSymRef(preparePcSym(pc.Pcfile))
+ o.Pcline = makeSymRef(preparePcSym(pc.Pcline))
+ o.Pcinline = makeSymRef(preparePcSym(pc.Pcinline))
+ o.Pcdata = make([]goobj.SymRef, len(pc.Pcdata))
+ for i, pcSym := range pc.Pcdata {
+ o.Pcdata[i] = makeSymRef(preparePcSym(pcSym))
+ }
o.Funcdataoff = make([]uint32, len(pc.Funcdataoff))
for i, x := range pc.Funcdataoff {
o.Funcdataoff[i] = uint32(x)
@@ -642,9 +679,9 @@ func genFuncInfoSyms(ctxt *Link) {
}
}
ctxt.defs = append(ctxt.defs, infosyms...)
+ ctxt.hasheddefs = append(ctxt.hasheddefs, hashedsyms...)
}
-// debugDumpAux is a dumper for selected aux symbols.
func writeAuxSymDebug(ctxt *Link, par *LSym, aux *LSym) {
// Most aux symbols (ex: funcdata) are not interesting--
// pick out just the DWARF ones for now.
diff --git a/src/cmd/internal/obj/pcln.go b/src/cmd/internal/obj/pcln.go
index 1f7ccf47ef..7750637796 100644
--- a/src/cmd/internal/obj/pcln.go
+++ b/src/cmd/internal/obj/pcln.go
@@ -6,6 +6,7 @@ package obj
import (
"cmd/internal/goobj"
+ "cmd/internal/objabi"
"encoding/binary"
"log"
)
@@ -14,16 +15,19 @@ import (
// returned by valfunc parameterized by arg. The invocation of valfunc to update the
// current value is, for each p,
//
-// val = valfunc(func, val, p, 0, arg);
-// record val as value at p->pc;
-// val = valfunc(func, val, p, 1, arg);
+// sym = valfunc(func, p, 0, arg);
+// record sym.P as value at p->pc;
+// sym = valfunc(func, p, 1, arg);
//
// where func is the function, val is the current value, p is the instruction being
// considered, and arg can be used to further parameterize valfunc.
-func funcpctab(ctxt *Link, dst *Pcdata, func_ *LSym, desc string, valfunc func(*Link, *LSym, int32, *Prog, int32, interface{}) int32, arg interface{}) {
+func funcpctab(ctxt *Link, func_ *LSym, desc string, valfunc func(*Link, *LSym, int32, *Prog, int32, interface{}) int32, arg interface{}) *LSym {
dbg := desc == ctxt.Debugpcln
-
- dst.P = dst.P[:0]
+ dst := []byte{}
+ sym := &LSym{
+ Type: objabi.SRODATA,
+ Attribute: AttrContentAddressable,
+ }
if dbg {
ctxt.Logf("funcpctab %s [valfunc=%s]\n", func_.Name, desc)
@@ -32,7 +36,8 @@ func funcpctab(ctxt *Link, dst *Pcdata, func_ *LSym, desc string, valfunc func(*
val := int32(-1)
oldval := val
if func_.Func.Text == nil {
- return
+ // Return the emtpy symbol we've built so far.
+ return sym
}
pc := func_.Func.Text.Pc
@@ -88,13 +93,13 @@ func funcpctab(ctxt *Link, dst *Pcdata, func_ *LSym, desc string, valfunc func(*
if started {
pcdelta := (p.Pc - pc) / int64(ctxt.Arch.MinLC)
n := binary.PutUvarint(buf, uint64(pcdelta))
- dst.P = append(dst.P, buf[:n]...)
+ dst = append(dst, buf[:n]...)
pc = p.Pc
}
delta := val - oldval
n := binary.PutVarint(buf, int64(delta))
- dst.P = append(dst.P, buf[:n]...)
+ dst = append(dst, buf[:n]...)
oldval = val
started = true
val = valfunc(ctxt, func_, val, p, 1, arg)
@@ -109,18 +114,22 @@ func funcpctab(ctxt *Link, dst *Pcdata, func_ *LSym, desc string, valfunc func(*
ctxt.Diag("negative pc offset: %v", v)
}
n := binary.PutUvarint(buf, uint64(v))
- dst.P = append(dst.P, buf[:n]...)
+ dst = append(dst, buf[:n]...)
// add terminating varint-encoded 0, which is just 0
- dst.P = append(dst.P, 0)
+ dst = append(dst, 0)
}
if dbg {
- ctxt.Logf("wrote %d bytes to %p\n", len(dst.P), dst)
- for _, p := range dst.P {
+ ctxt.Logf("wrote %d bytes to %p\n", len(dst), dst)
+ for _, p := range dst {
ctxt.Logf(" %02x", p)
}
ctxt.Logf("\n")
}
+
+ sym.Size = int64(len(dst))
+ sym.P = dst
+ return sym
}
// pctofileline computes either the file number (arg == 0)
@@ -268,18 +277,17 @@ func linkpcln(ctxt *Link, cursym *LSym) {
}
}
- pcln.Pcdata = make([]Pcdata, npcdata)
- pcln.Pcdata = pcln.Pcdata[:npcdata]
+ pcln.Pcdata = make([]*LSym, npcdata)
pcln.Funcdata = make([]*LSym, nfuncdata)
pcln.Funcdataoff = make([]int64, nfuncdata)
pcln.Funcdataoff = pcln.Funcdataoff[:nfuncdata]
- funcpctab(ctxt, &pcln.Pcsp, cursym, "pctospadj", pctospadj, nil)
- funcpctab(ctxt, &pcln.Pcfile, cursym, "pctofile", pctofileline, pcln)
- funcpctab(ctxt, &pcln.Pcline, cursym, "pctoline", pctofileline, nil)
+ pcln.Pcsp = funcpctab(ctxt, cursym, "pctospadj", pctospadj, nil)
+ pcln.Pcfile = funcpctab(ctxt, cursym, "pctofile", pctofileline, pcln)
+ pcln.Pcline = funcpctab(ctxt, cursym, "pctoline", pctofileline, nil)
pcinlineState := new(pcinlineState)
- funcpctab(ctxt, &pcln.Pcinline, cursym, "pctoinline", pcinlineState.pctoinline, nil)
+ pcln.Pcinline = funcpctab(ctxt, cursym, "pctoinline", pcinlineState.pctoinline, nil)
for _, inlMark := range cursym.Func.InlMarks {
pcinlineState.setParentPC(ctxt, int(inlMark.id), int32(inlMark.p.Pc))
}
@@ -309,9 +317,14 @@ func linkpcln(ctxt *Link, cursym *LSym) {
// pcdata.
for i := 0; i < npcdata; i++ {
if (havepc[i/32]>>uint(i%32))&1 == 0 {
- continue
+ // use an empty symbol.
+ pcln.Pcdata[i] = &LSym{
+ Type: objabi.SRODATA,
+ Attribute: AttrContentAddressable,
+ }
+ } else {
+ pcln.Pcdata[i] = funcpctab(ctxt, cursym, "pctopcdata", pctopcdata, interface{}(uint32(i)))
}
- funcpctab(ctxt, &pcln.Pcdata[i], cursym, "pctopcdata", pctopcdata, interface{}(uint32(i)))
}
// funcdata
diff --git a/src/cmd/internal/objfile/goobj.go b/src/cmd/internal/objfile/goobj.go
index e838f58aed..8eecebb1df 100644
--- a/src/cmd/internal/objfile/goobj.go
+++ b/src/cmd/internal/objfile/goobj.go
@@ -236,7 +236,15 @@ func (f *goobjFile) PCToLine(pc uint64) (string, int, *gosym.Func) {
if arch == nil {
return "", 0, nil
}
- pcdataBase := r.PcdataBase()
+ getSymData := func(s goobj.SymRef) []byte {
+ if s.PkgIdx != goobj.PkgIdxHashed {
+ // We don't need the data for non-hashed symbols, yet.
+ panic("not supported")
+ }
+ i := uint32(s.SymIdx + uint32(r.NSym()+r.NHashed64def()))
+ return r.BytesAt(r.DataOff(i), r.DataSize(i))
+ }
+
ndef := uint32(r.NSym() + r.NHashed64def() + r.NHasheddef() + r.NNonpkgdef())
for i := uint32(0); i < ndef; i++ {
osym := r.Sym(i)
@@ -262,11 +270,9 @@ func (f *goobjFile) PCToLine(pc uint64) (string, int, *gosym.Func) {
b := r.BytesAt(r.DataOff(isym), r.DataSize(isym))
var info *goobj.FuncInfo
lengths := info.ReadFuncInfoLengths(b)
- off, end := info.ReadPcline(b)
- pcline := r.BytesAt(pcdataBase+off, int(end-off))
+ pcline := getSymData(info.ReadPcline(b))
line := int(pcValue(pcline, pc-addr, arch))
- off, end = info.ReadPcfile(b)
- pcfile := r.BytesAt(pcdataBase+off, int(end-off))
+ pcfile := getSymData(info.ReadPcfile(b))
fileID := pcValue(pcfile, pc-addr, arch)
globalFileID := info.ReadFile(b, lengths.FileOff, uint32(fileID))
fileName := r.File(int(globalFileID))
diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go
index d1f2ac583d..2b95ad5a67 100644
--- a/src/cmd/link/internal/ld/dwarf.go
+++ b/src/cmd/link/internal/ld/dwarf.go
@@ -1421,7 +1421,7 @@ func (d *dwctxt) writeframes(fs loader.Sym) dwarfSecInfo {
deltaBuf = dwarf.AppendUleb128(deltaBuf, uint64(thearch.Dwarfreglr))
}
- for pcsp.Init(fpcsp); !pcsp.Done; pcsp.Next() {
+ for pcsp.Init(d.linkctxt.loader.Data(fpcsp)); !pcsp.Done; pcsp.Next() {
nextpc := pcsp.NextPC
// pciterinit goes up to the end of the function,
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
index 09c7bbfb53..caa4566190 100644
--- a/src/cmd/link/internal/ld/lib.go
+++ b/src/cmd/link/internal/ld/lib.go
@@ -2252,7 +2252,7 @@ func (sc *stkChk) check(up *chain, depth int) int {
var ch1 chain
pcsp := obj.NewPCIter(uint32(ctxt.Arch.MinLC))
ri := 0
- for pcsp.Init(info.Pcsp()); !pcsp.Done; pcsp.Next() {
+ for pcsp.Init(ldr.Data(info.Pcsp())); !pcsp.Done; pcsp.Next() {
// pcsp.value is in effect for [pcsp.pc, pcsp.nextpc).
// Check stack size in effect for this span.
diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go
index c7535f6a61..e9fd5937e7 100644
--- a/src/cmd/link/internal/ld/pcln.go
+++ b/src/cmd/link/internal/ld/pcln.go
@@ -592,9 +592,8 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
fi := ldr.FuncInfo(s)
if fi.Valid() {
fi.Preload()
- npc := fi.NumPcdata()
- for i := uint32(0); i < npc; i++ {
- pcdata = append(pcdata, sym.Pcdata{P: fi.Pcdata(int(i))})
+ for _, dataSym := range fi.Pcdata() {
+ pcdata = append(pcdata, sym.Pcdata{P: ldr.Data(dataSym)})
}
nfd := fi.NumFuncdataoff()
for i := uint32(0); i < nfd; i++ {
@@ -666,15 +665,15 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
cu := ldr.SymUnit(s)
if fi.Valid() {
- pcsp = sym.Pcdata{P: fi.Pcsp()}
- pcfile = sym.Pcdata{P: fi.Pcfile()}
- pcline = sym.Pcdata{P: fi.Pcline()}
+ pcsp = sym.Pcdata{P: ldr.Data(fi.Pcsp())}
+ pcfile = sym.Pcdata{P: ldr.Data(fi.Pcfile())}
+ pcline = sym.Pcdata{P: ldr.Data(fi.Pcline())}
}
if fi.Valid() && fi.NumInlTree() > 0 {
its := oldState.genInlTreeSym(cu, fi, ctxt.Arch, state)
funcdata[objabi.FUNCDATA_InlTree] = its
- pcdata[objabi.PCDATA_InlTreeIndex] = sym.Pcdata{P: fi.Pcinline()}
+ pcdata[objabi.PCDATA_InlTreeIndex] = sym.Pcdata{P: ldr.Data(fi.Pcinline())}
}
// pcdata
diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go
index 8fd10b0848..f149e3c831 100644
--- a/src/cmd/link/internal/loader/loader.go
+++ b/src/cmd/link/internal/loader/loader.go
@@ -1878,19 +1878,24 @@ func (fi *FuncInfo) FuncID() objabi.FuncID {
return objabi.FuncID((*goobj.FuncInfo)(nil).ReadFuncID(fi.data))
}
-func (fi *FuncInfo) Pcsp() []byte {
- pcsp, end := (*goobj.FuncInfo)(nil).ReadPcsp(fi.data)
- return fi.r.BytesAt(fi.r.PcdataBase()+pcsp, int(end-pcsp))
+func (fi *FuncInfo) Pcsp() Sym {
+ sym := (*goobj.FuncInfo)(nil).ReadPcsp(fi.data)
+ return fi.l.resolve(fi.r, sym)
}
-func (fi *FuncInfo) Pcfile() []byte {
- pcf, end := (*goobj.FuncInfo)(nil).ReadPcfile(fi.data)
- return fi.r.BytesAt(fi.r.PcdataBase()+pcf, int(end-pcf))
+func (fi *FuncInfo) Pcfile() Sym {
+ sym := (*goobj.FuncInfo)(nil).ReadPcfile(fi.data)
+ return fi.l.resolve(fi.r, sym)
}
-func (fi *FuncInfo) Pcline() []byte {
- pcln, end := (*goobj.FuncInfo)(nil).ReadPcline(fi.data)
- return fi.r.BytesAt(fi.r.PcdataBase()+pcln, int(end-pcln))
+func (fi *FuncInfo) Pcline() Sym {
+ sym := (*goobj.FuncInfo)(nil).ReadPcline(fi.data)
+ return fi.l.resolve(fi.r, sym)
+}
+
+func (fi *FuncInfo) Pcinline() Sym {
+ sym := (*goobj.FuncInfo)(nil).ReadPcinline(fi.data)
+ return fi.l.resolve(fi.r, sym)
}
// Preload has to be called prior to invoking the various methods
@@ -1899,27 +1904,16 @@ func (fi *FuncInfo) Preload() {
fi.lengths = (*goobj.FuncInfo)(nil).ReadFuncInfoLengths(fi.data)
}
-func (fi *FuncInfo) Pcinline() []byte {
+func (fi *FuncInfo) Pcdata() []Sym {
if !fi.lengths.Initialized {
panic("need to call Preload first")
}
- pcinl, end := (*goobj.FuncInfo)(nil).ReadPcinline(fi.data, fi.lengths.PcdataOff)
- return fi.r.BytesAt(fi.r.PcdataBase()+pcinl, int(end-pcinl))
-}
-
-func (fi *FuncInfo) NumPcdata() uint32 {
- if !fi.lengths.Initialized {
- panic("need to call Preload first")
- }
- return fi.lengths.NumPcdata
-}
-
-func (fi *FuncInfo) Pcdata(k int) []byte {
- if !fi.lengths.Initialized {
- panic("need to call Preload first")
+ syms := (*goobj.FuncInfo)(nil).ReadPcdata(fi.data)
+ ret := make([]Sym, len(syms))
+ for i := range ret {
+ ret[i] = fi.l.resolve(fi.r, syms[i])
}
- pcdat, end := (*goobj.FuncInfo)(nil).ReadPcdata(fi.data, fi.lengths.PcdataOff, uint32(k))
- return fi.r.BytesAt(fi.r.PcdataBase()+pcdat, int(end-pcdat))
+ return ret
}
func (fi *FuncInfo) NumFuncdataoff() uint32 {
--
cgit v1.3
From 26407b22129e2e54db269c1a92826521addd8d56 Mon Sep 17 00:00:00 2001
From: Jeremy Faller
Date: Wed, 12 Aug 2020 19:26:53 -0400
Subject: [dev.link] cmd/{compile,link}: remove pcdata tables from pclntab_old
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Move the pctables out of pclntab_old. Creates a new generator symbol,
runtime.pctab, which holds all the deduplicated pctables. Also, tightens
up some of the types in runtime.
Darwin, cmd/compile statistics:
alloc/op
Pclntab_GC 26.4MB ± 0% 13.8MB ± 0%
allocs/op
Pclntab_GC 89.9k ± 0% 86.4k ± 0%
liveB
Pclntab_GC 25.5M ± 0% 24.2M ± 0%
No significant change in binary size.
Change-Id: I1560fd4421f8a210f8d4b508fbc54e1780e338f9
Reviewed-on: https://go-review.googlesource.com/c/go/+/248332
Run-TryBot: Jeremy Faller
TryBot-Result: Gobot Gobot
Reviewed-by: Cherry Zhang
---
src/cmd/link/internal/ld/data.go | 2 +
src/cmd/link/internal/ld/pcln.go | 143 +++++++++++++++++---------
src/cmd/link/internal/ld/symtab.go | 4 +
src/cmd/link/internal/loader/symbolbuilder.go | 9 ++
src/cmd/link/internal/sym/symbol.go | 4 -
src/debug/gosym/pclntab.go | 10 +-
src/runtime/runtime2.go | 8 +-
src/runtime/symtab.go | 28 ++---
8 files changed, 136 insertions(+), 72 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go
index a551d46403..2aecbfbeb5 100644
--- a/src/cmd/link/internal/ld/data.go
+++ b/src/cmd/link/internal/ld/data.go
@@ -1925,6 +1925,7 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.funcnametab", 0), sect)
ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.cutab", 0), sect)
ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.filetab", 0), sect)
+ ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pctab", 0), sect)
ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pclntab_old", 0), sect)
ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.epclntab", 0), sect)
if ctxt.HeadType == objabi.Haix {
@@ -2511,6 +2512,7 @@ func (ctxt *Link) address() []*sym.Segment {
ctxt.defineInternal("runtime.funcnametab", sym.SRODATA)
ctxt.defineInternal("runtime.cutab", sym.SRODATA)
ctxt.defineInternal("runtime.filetab", sym.SRODATA)
+ ctxt.defineInternal("runtime.pctab", sym.SRODATA)
ctxt.defineInternal("runtime.pclntab_old", sym.SRODATA)
ctxt.xdefine("runtime.epclntab", sym.SRODATA, int64(pclntab.Vaddr+pclntab.Length))
ctxt.xdefine("runtime.noptrdata", sym.SNOPTRDATA, int64(noptr.Vaddr))
diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go
index e9fd5937e7..576f1c3780 100644
--- a/src/cmd/link/internal/ld/pcln.go
+++ b/src/cmd/link/internal/ld/pcln.go
@@ -43,6 +43,7 @@ type pclntab struct {
findfunctab loader.Sym
cutab loader.Sym
filetab loader.Sym
+ pctab loader.Sym
// The number of functions + number of TEXT sections - 1. This is such an
// unexpected value because platforms that have more than one TEXT section
@@ -273,10 +274,11 @@ func (state *pclntab) generatePCHeader(ctxt *Link) {
off = writeSymOffset(off, state.funcnametab)
off = writeSymOffset(off, state.cutab)
off = writeSymOffset(off, state.filetab)
+ off = writeSymOffset(off, state.pctab)
off = writeSymOffset(off, state.pclntab)
}
- size := int64(8 + 6*ctxt.Arch.PtrSize)
+ size := int64(8 + 7*ctxt.Arch.PtrSize)
state.pcheader = state.addGeneratedSym(ctxt, "runtime.pcheader", size, writeHeader)
}
@@ -463,6 +465,68 @@ func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.Compilat
return cuOffsets
}
+// generatePctab creates the runtime.pctab variable, holding all the
+// deduplicated pcdata.
+func (state *pclntab) generatePctab(ctxt *Link, container loader.Bitmap) {
+ ldr := ctxt.loader
+
+ // Pctab offsets of 0 are considered invalid in the runtime. We respect
+ // that by just padding a single byte at the beginning of runtime.pctab,
+ // that way no real offsets can be zero.
+ size := int64(1)
+
+ // Walk the functions, finding offset to store each pcdata.
+ seen := make(map[loader.Sym]struct{})
+ saveOffset := func(pcSym loader.Sym) {
+ if _, ok := seen[pcSym]; !ok {
+ datSize := ldr.SymSize(pcSym)
+ if datSize != 0 {
+ ldr.SetSymValue(pcSym, size)
+ } else {
+ // Invalid PC data, record as zero.
+ ldr.SetSymValue(pcSym, 0)
+ }
+ size += datSize
+ seen[pcSym] = struct{}{}
+ }
+ }
+ for _, s := range ctxt.Textp {
+ if !emitPcln(ctxt, s, container) {
+ continue
+ }
+ fi := ldr.FuncInfo(s)
+ if !fi.Valid() {
+ continue
+ }
+ fi.Preload()
+
+ pcSyms := []loader.Sym{fi.Pcsp(), fi.Pcfile(), fi.Pcline()}
+ for _, pcSym := range pcSyms {
+ saveOffset(pcSym)
+ }
+ for _, pcSym := range fi.Pcdata() {
+ saveOffset(pcSym)
+ }
+ if fi.NumInlTree() > 0 {
+ saveOffset(fi.Pcinline())
+ }
+ }
+
+ // TODO: There is no reason we need a generator for this variable, and it
+ // could be moved to a carrier symbol. However, carrier symbols containing
+ // carrier symbols don't work yet (as of Aug 2020). Once this is fixed,
+ // runtime.pctab could just be a carrier sym.
+ writePctab := func(ctxt *Link, s loader.Sym) {
+ ldr := ctxt.loader
+ sb := ldr.MakeSymbolUpdater(s)
+ for sym := range seen {
+ sb.SetBytesAt(ldr.SymValue(sym), ldr.Data(sym))
+ }
+ }
+
+ state.pctab = state.addGeneratedSym(ctxt, "runtime.pctab", size, writePctab)
+}
+
// pclntab initializes the pclntab symbol with
// runtime function and file name information.
@@ -494,6 +558,9 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
// runtime.filetab
// []null terminated filename strings
//
+ // runtime.pctab
+ // []byte of deduplicated pc data.
+ //
// runtime.pclntab_old
// function table, alternating PC and offset to func struct [each entry thearch.ptrsize bytes]
// end PC [thearch.ptrsize bytes]
@@ -514,6 +581,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
state.generatePCHeader(ctxt)
state.generateFuncnametab(ctxt, container)
cuOffsets := state.generateFilenameTabs(ctxt, compUnits, container)
+ state.generatePctab(ctxt, container)
funcdataBytes := int64(0)
ldr.SetCarrierSym(state.pclntab, state.carrier)
@@ -525,21 +593,6 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
ftab.Grow(int64(state.nfunc)*2*int64(ctxt.Arch.PtrSize) + int64(ctxt.Arch.PtrSize) + 4)
- szHint := len(ctxt.Textp) * 2
- pctaboff := make(map[string]uint32, szHint)
- writepctab := func(off int32, p []byte) int32 {
- start, ok := pctaboff[string(p)]
- if !ok {
- if len(p) > 0 {
- start = uint32(len(ftab.Data()))
- ftab.AddBytes(p)
- }
- pctaboff[string(p)] = start
- }
- newoff := int32(ftab.SetUint32(ctxt.Arch, int64(off), start))
- return newoff
- }
-
setAddr := (*loader.SymbolBuilder).SetAddrPlus
if ctxt.IsExe() && ctxt.IsInternal() {
// Internal linking static executable. At this point the function
@@ -555,10 +608,6 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
}
}
- pcsp := sym.Pcdata{}
- pcfile := sym.Pcdata{}
- pcline := sym.Pcdata{}
- pcdata := []sym.Pcdata{}
funcdata := []loader.Sym{}
funcdataoff := []int64{}
@@ -583,18 +632,13 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
}
prevFunc = s
- pcsp.P = pcsp.P[:0]
- pcline.P = pcline.P[:0]
- pcfile.P = pcfile.P[:0]
- pcdata = pcdata[:0]
+ var numPCData int32
funcdataoff = funcdataoff[:0]
funcdata = funcdata[:0]
fi := ldr.FuncInfo(s)
if fi.Valid() {
fi.Preload()
- for _, dataSym := range fi.Pcdata() {
- pcdata = append(pcdata, sym.Pcdata{P: ldr.Data(dataSym)})
- }
+ numPCData = int32(len(fi.Pcdata()))
nfd := fi.NumFuncdataoff()
for i := uint32(0); i < nfd; i++ {
funcdataoff = append(funcdataoff, fi.Funcdataoff(int(i)))
@@ -602,15 +646,12 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
funcdata = fi.Funcdata(funcdata)
}
+ writeInlPCData := false
if fi.Valid() && fi.NumInlTree() > 0 {
-
- if len(pcdata) <= objabi.PCDATA_InlTreeIndex {
- // Create inlining pcdata table.
- newpcdata := make([]sym.Pcdata, objabi.PCDATA_InlTreeIndex+1)
- copy(newpcdata, pcdata)
- pcdata = newpcdata
+ writeInlPCData = true
+ if numPCData <= objabi.PCDATA_InlTreeIndex {
+ numPCData = objabi.PCDATA_InlTreeIndex + 1
}
-
if len(funcdataoff) <= objabi.FUNCDATA_InlTree {
// Create inline tree funcdata.
newfuncdata := make([]loader.Sym, objabi.FUNCDATA_InlTree+1)
@@ -635,7 +676,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
// fixed size of struct, checked below
off := funcstart
- end := funcstart + int32(ctxt.Arch.PtrSize) + 3*4 + 6*4 + int32(len(pcdata))*4 + int32(len(funcdata))*int32(ctxt.Arch.PtrSize)
+ end := funcstart + int32(ctxt.Arch.PtrSize) + 3*4 + 6*4 + numPCData*4 + int32(len(funcdata))*int32(ctxt.Arch.PtrSize)
if len(funcdata) > 0 && (end&int32(ctxt.Arch.PtrSize-1) != 0) {
end += 4
}
@@ -664,23 +705,21 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
off = int32(ftab.SetUint32(ctxt.Arch, int64(off), deferreturn))
cu := ldr.SymUnit(s)
- if fi.Valid() {
- pcsp = sym.Pcdata{P: ldr.Data(fi.Pcsp())}
- pcfile = sym.Pcdata{P: ldr.Data(fi.Pcfile())}
- pcline = sym.Pcdata{P: ldr.Data(fi.Pcline())}
- }
if fi.Valid() && fi.NumInlTree() > 0 {
its := oldState.genInlTreeSym(cu, fi, ctxt.Arch, state)
funcdata[objabi.FUNCDATA_InlTree] = its
- pcdata[objabi.PCDATA_InlTreeIndex] = sym.Pcdata{P: ldr.Data(fi.Pcinline())}
}
// pcdata
- off = writepctab(off, pcsp.P)
- off = writepctab(off, pcfile.P)
- off = writepctab(off, pcline.P)
- off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(len(pcdata))))
+ if fi.Valid() {
+ off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcsp()))))
+ off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcfile()))))
+ off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcline()))))
+ } else {
+ off += 12
+ }
+ off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(numPCData)))
// Store the offset to compilation unit's file table.
cuIdx := ^uint32(0)
@@ -700,9 +739,17 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
// nfuncdata must be the final entry.
off = int32(ftab.SetUint8(ctxt.Arch, int64(off), uint8(len(funcdata))))
- for i := range pcdata {
- off = writepctab(off, pcdata[i].P)
+
+ // Output the pcdata.
+ if fi.Valid() {
+ for i, pcSym := range fi.Pcdata() {
+ ftab.SetUint32(ctxt.Arch, int64(off+int32(i*4)), uint32(ldr.SymValue(pcSym)))
+ }
+ if writeInlPCData {
+ ftab.SetUint32(ctxt.Arch, int64(off+objabi.PCDATA_InlTreeIndex*4), uint32(ldr.SymValue(fi.Pcinline())))
+ }
}
+ off += numPCData * 4
// funcdata, must be pointer-aligned and we're only int32-aligned.
// Missing funcdata will be 0 (nil pointer).
@@ -724,7 +771,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
}
if off != end {
- ctxt.Errorf(s, "bad math in functab: funcstart=%d off=%d but end=%d (npcdata=%d nfuncdata=%d ptrsize=%d)", funcstart, off, end, len(pcdata), len(funcdata), ctxt.Arch.PtrSize)
+ ctxt.Errorf(s, "bad math in functab: funcstart=%d off=%d but end=%d (npcdata=%d nfuncdata=%d ptrsize=%d)", funcstart, off, end, numPCData, len(funcdata), ctxt.Arch.PtrSize)
errorexit()
}
diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go
index d05b98f04a..520aaa44c2 100644
--- a/src/cmd/link/internal/ld/symtab.go
+++ b/src/cmd/link/internal/ld/symtab.go
@@ -627,6 +627,10 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind {
moduledata.AddAddr(ctxt.Arch, pcln.filetab)
moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.filetab)))
moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.filetab)))
+ // The pctab slice
+ moduledata.AddAddr(ctxt.Arch, pcln.pctab)
+ moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.pctab)))
+ moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.pctab)))
// The pclntab slice
moduledata.AddAddr(ctxt.Arch, pcln.pclntab)
moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.pclntab)))
diff --git a/src/cmd/link/internal/loader/symbolbuilder.go b/src/cmd/link/internal/loader/symbolbuilder.go
index e14d89a927..c0c723d7f0 100644
--- a/src/cmd/link/internal/loader/symbolbuilder.go
+++ b/src/cmd/link/internal/loader/symbolbuilder.go
@@ -336,6 +336,15 @@ func (sb *SymbolBuilder) Addstring(str string) int64 {
return r
}
+func (sb *SymbolBuilder) SetBytesAt(off int64, b []byte) int64 {
+ datLen := int64(len(b))
+ if off+datLen > int64(len(sb.data)) {
+ panic("attempt to write past end of buffer")
+ }
+ copy(sb.data[off:off+datLen], b)
+ return off + datLen
+}
+
func (sb *SymbolBuilder) addSymRef(tgt Sym, add int64, typ objabi.RelocType, rsize int) int64 {
if sb.kind == 0 {
sb.kind = sym.SDATA
diff --git a/src/cmd/link/internal/sym/symbol.go b/src/cmd/link/internal/sym/symbol.go
index 1a4165ebf7..70cf36a87e 100644
--- a/src/cmd/link/internal/sym/symbol.go
+++ b/src/cmd/link/internal/sym/symbol.go
@@ -33,7 +33,3 @@ func VersionToABI(v int) (obj.ABI, bool) {
}
return ^obj.ABI(0), false
}
-
-type Pcdata struct {
- P []byte
-}
diff --git a/src/debug/gosym/pclntab.go b/src/debug/gosym/pclntab.go
index 21edddda20..a72f9847d7 100644
--- a/src/debug/gosym/pclntab.go
+++ b/src/debug/gosym/pclntab.go
@@ -58,6 +58,7 @@ type LineTable struct {
functab []byte
nfunctab uint32
filetab []byte
+ pctab []byte // points to the pctables.
nfiletab uint32
funcNames map[uint32]string // cache the function names
strings map[uint32]string // interned substrings of Data, keyed by offset
@@ -235,6 +236,8 @@ func (t *LineTable) parsePclnTab() {
offset = t.uintptr(t.Data[8+4*t.ptrsize:])
t.filetab = t.Data[offset:]
offset = t.uintptr(t.Data[8+5*t.ptrsize:])
+ t.pctab = t.Data[offset:]
+ offset = t.uintptr(t.Data[8+6*t.ptrsize:])
t.funcdata = t.Data[offset:]
t.functab = t.Data[offset:]
functabsize := t.nfunctab*2*t.ptrsize + t.ptrsize
@@ -244,6 +247,7 @@ func (t *LineTable) parsePclnTab() {
t.funcdata = t.Data
t.funcnametab = t.Data
t.functab = t.Data[8+t.ptrsize:]
+ t.pctab = t.Data
functabsize := t.nfunctab*2*t.ptrsize + t.ptrsize
fileoff := t.binary.Uint32(t.functab[functabsize:])
t.functab = t.functab[:functabsize]
@@ -373,7 +377,7 @@ func (t *LineTable) step(p *[]byte, pc *uint64, val *int32, first bool) bool {
// off is the offset to the beginning of the pc-value table,
// and entry is the start PC for the corresponding function.
func (t *LineTable) pcvalue(off uint32, entry, targetpc uint64) int32 {
- p := t.funcdata[off:]
+ p := t.pctab[off:]
val := int32(-1)
pc := entry
@@ -396,8 +400,8 @@ func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum,
return 0
}
- fp := t.funcdata[filetab:]
- fl := t.funcdata[linetab:]
+ fp := t.pctab[filetab:]
+ fl := t.pctab[linetab:]
fileVal := int32(-1)
filePC := entry
lineVal := int32(-1)
diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go
index 5a79c7e6ec..755c409078 100644
--- a/src/runtime/runtime2.go
+++ b/src/runtime/runtime2.go
@@ -800,10 +800,10 @@ type _func struct {
args int32 // in/out args size
deferreturn uint32 // offset of start of a deferreturn call instruction from entry, if any.
- pcsp int32
- pcfile int32
- pcln int32
- npcdata int32
+ pcsp uint32
+ pcfile uint32
+ pcln uint32
+ npcdata uint32
cuOffset uint32 // runtime.cutab offset of this function's CU
funcID funcID // set for certain special runtime functions
_ [2]byte // pad
diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go
index fbd9315522..0610f75179 100644
--- a/src/runtime/symtab.go
+++ b/src/runtime/symtab.go
@@ -345,6 +345,7 @@ type pcHeader struct {
funcnameOffset uintptr // offset to the funcnametab variable from pcHeader
cuOffset uintptr // offset to the cutab variable from pcHeader
filetabOffset uintptr // offset to the filetab variable from pcHeader
+ pctabOffset uintptr // offset to the pctab varible from pcHeader
pclnOffset uintptr // offset to the pclntab variable from pcHeader
}
@@ -358,6 +359,7 @@ type moduledata struct {
funcnametab []byte
cutab []uint32
filetab []byte
+ pctab []byte
pclntable []byte
ftab []functab
findfunctab uintptr
@@ -721,7 +723,7 @@ type pcvalueCache struct {
type pcvalueCacheEnt struct {
// targetpc and off together are the key of this cache entry.
targetpc uintptr
- off int32
+ off uint32
// val is the value of this cached pcvalue entry.
val int32
}
@@ -736,7 +738,7 @@ func pcvalueCacheKey(targetpc uintptr) uintptr {
// Returns the PCData value, and the PC where this value starts.
// TODO: the start PC is returned only when cache is nil.
-func pcvalue(f funcInfo, off int32, targetpc uintptr, cache *pcvalueCache, strict bool) (int32, uintptr) {
+func pcvalue(f funcInfo, off uint32, targetpc uintptr, cache *pcvalueCache, strict bool) (int32, uintptr) {
if off == 0 {
return -1, 0
}
@@ -770,7 +772,7 @@ func pcvalue(f funcInfo, off int32, targetpc uintptr, cache *pcvalueCache, stric
return -1, 0
}
datap := f.datap
- p := datap.pclntable[off:]
+ p := datap.pctab[off:]
pc := f.entry
prevpc := pc
val := int32(-1)
@@ -812,7 +814,7 @@ func pcvalue(f funcInfo, off int32, targetpc uintptr, cache *pcvalueCache, stric
print("runtime: invalid pc-encoded table f=", funcname(f), " pc=", hex(pc), " targetpc=", hex(targetpc), " tab=", p, "\n")
- p = datap.pclntable[off:]
+ p = datap.pctab[off:]
pc = f.entry
val = -1
for {
@@ -893,7 +895,7 @@ func funcspdelta(f funcInfo, targetpc uintptr, cache *pcvalueCache) int32 {
// funcMaxSPDelta returns the maximum spdelta at any point in f.
func funcMaxSPDelta(f funcInfo) int32 {
datap := f.datap
- p := datap.pclntable[f.pcsp:]
+ p := datap.pctab[f.pcsp:]
pc := f.entry
val := int32(-1)
max := int32(0)
@@ -909,20 +911,20 @@ func funcMaxSPDelta(f funcInfo) int32 {
}
}
-func pcdatastart(f funcInfo, table int32) int32 {
- return *(*int32)(add(unsafe.Pointer(&f.nfuncdata), unsafe.Sizeof(f.nfuncdata)+uintptr(table)*4))
+func pcdatastart(f funcInfo, table uint32) uint32 {
+ return *(*uint32)(add(unsafe.Pointer(&f.nfuncdata), unsafe.Sizeof(f.nfuncdata)+uintptr(table)*4))
}
-func pcdatavalue(f funcInfo, table int32, targetpc uintptr, cache *pcvalueCache) int32 {
- if table < 0 || table >= f.npcdata {
+func pcdatavalue(f funcInfo, table uint32, targetpc uintptr, cache *pcvalueCache) int32 {
+ if table >= f.npcdata {
return -1
}
r, _ := pcvalue(f, pcdatastart(f, table), targetpc, cache, true)
return r
}
-func pcdatavalue1(f funcInfo, table int32, targetpc uintptr, cache *pcvalueCache, strict bool) int32 {
- if table < 0 || table >= f.npcdata {
+func pcdatavalue1(f funcInfo, table uint32, targetpc uintptr, cache *pcvalueCache, strict bool) int32 {
+ if table >= f.npcdata {
return -1
}
r, _ := pcvalue(f, pcdatastart(f, table), targetpc, cache, strict)
@@ -931,8 +933,8 @@ func pcdatavalue1(f funcInfo, table int32, targetpc uintptr, cache *pcvalueCache
// Like pcdatavalue, but also return the start PC of this PCData value.
// It doesn't take a cache.
-func pcdatavalue2(f funcInfo, table int32, targetpc uintptr) (int32, uintptr) {
- if table < 0 || table >= f.npcdata {
+func pcdatavalue2(f funcInfo, table uint32, targetpc uintptr) (int32, uintptr) {
+ if table >= f.npcdata {
return -1, 0
}
return pcvalue(f, pcdatastart(f, table), targetpc, nil, true)
--
cgit v1.3
From ac5c406ef0ab20e2a11f57470271266ef4265221 Mon Sep 17 00:00:00 2001
From: Jeremy Faller
Date: Thu, 13 Aug 2020 12:21:18 -0400
Subject: [dev.link] cmd/link: clean up some pclntab state
Clean up some pclntab state, specifically:
1) Remove the oldPclnState type.
2) Move a structure out of pclnState, that was holding some memory.
3) Stop passing container around everywhere and calling emitPcln. Use a
slice of function symbols instead.
Change-Id: I74e916564cd769a706750d024e55ee0d811a79da
Reviewed-on: https://go-review.googlesource.com/c/go/+/248379
Run-TryBot: Jeremy Faller
TryBot-Result: Gobot Gobot
Reviewed-by: Austin Clements
Reviewed-by: Cherry Zhang
---
src/cmd/link/internal/ld/pcln.go | 135 ++++++++++++++-------------------------
1 file changed, 47 insertions(+), 88 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go
index 576f1c3780..33476ec292 100644
--- a/src/cmd/link/internal/ld/pcln.go
+++ b/src/cmd/link/internal/ld/pcln.go
@@ -16,17 +16,6 @@ import (
"strings"
)
-// oldPclnState holds state information used during pclntab generation. Here
-// 'ldr' is just a pointer to the context's loader, 'deferReturnSym' is the
-// index for the symbol "runtime.deferreturn",
-//
-// NB: This is deprecated, and will be eliminated when pclntab_old is
-// eliminated.
-type oldPclnState struct {
- ldr *loader.Loader
- deferReturnSym loader.Sym
-}
-
// pclntab holds the state needed for pclntab generation.
type pclntab struct {
// The first and last functions found.
@@ -56,12 +45,6 @@ type pclntab struct {
// The number of filenames in runtime.filetab.
nfiles uint32
-
- // maps the function symbol to offset in runtime.funcnametab
- // This doesn't need to reside in the state once pclntab_old's been
- // deleted -- it can live in generateFuncnametab.
- // TODO(jfaller): Delete me!
- funcNameOffset map[loader.Sym]int32
}
// addGeneratedSym adds a generator symbol to pclntab, returning the new Sym.
@@ -76,35 +59,26 @@ func (state *pclntab) addGeneratedSym(ctxt *Link, name string, size int64, f gen
return s
}
-func makeOldPclnState(ctxt *Link) *oldPclnState {
- ldr := ctxt.loader
- drs := ldr.Lookup("runtime.deferreturn", sym.SymVerABIInternal)
- state := &oldPclnState{
- ldr: ldr,
- deferReturnSym: drs,
- }
-
- return state
-}
-
// makePclntab makes a pclntab object, and assembles all the compilation units
-// we'll need to write pclntab.
-func makePclntab(ctxt *Link, container loader.Bitmap) (*pclntab, []*sym.CompilationUnit) {
+// we'll need to write pclntab. Returns the pclntab structure, a slice of the
+// CompilationUnits we need, and a slice of the function symbols we need to
+// generate pclntab.
+func makePclntab(ctxt *Link, container loader.Bitmap) (*pclntab, []*sym.CompilationUnit, []loader.Sym) {
ldr := ctxt.loader
- state := &pclntab{
- funcNameOffset: make(map[loader.Sym]int32),
- }
+ state := &pclntab{}
// Gather some basic stats and info.
seenCUs := make(map[*sym.CompilationUnit]struct{})
prevSect := ldr.SymSect(ctxt.Textp[0])
compUnits := []*sym.CompilationUnit{}
+ funcs := []loader.Sym{}
for _, s := range ctxt.Textp {
if !emitPcln(ctxt, s, container) {
continue
}
+ funcs = append(funcs, s)
state.nfunc++
if state.firstFunc == 0 {
state.firstFunc = s
@@ -130,15 +104,7 @@ func makePclntab(ctxt *Link, container loader.Bitmap) (*pclntab, []*sym.Compilat
compUnits = append(compUnits, cu)
}
}
- return state, compUnits
-}
-
-func ftabaddstring(ftab *loader.SymbolBuilder, s string) int32 {
- start := len(ftab.Data())
- ftab.Grow(int64(start + len(s) + 1)) // make room for s plus trailing NUL
- ftd := ftab.Data()
- copy(ftd[start:], s)
- return int32(start)
+ return state, compUnits, funcs
}
// onlycsymbol looks at a symbol's name to report whether this is a
@@ -163,11 +129,13 @@ func emitPcln(ctxt *Link, s loader.Sym, container loader.Bitmap) bool {
return !container.Has(s)
}
-func (state *oldPclnState) computeDeferReturn(target *Target, s loader.Sym) uint32 {
+func computeDeferReturn(ctxt *Link, deferReturnSym, s loader.Sym) uint32 {
+ ldr := ctxt.loader
+ target := ctxt.Target
deferreturn := uint32(0)
lastWasmAddr := uint32(0)
- relocs := state.ldr.Relocs(s)
+ relocs := ldr.Relocs(s)
for ri := 0; ri < relocs.Count(); ri++ {
r := relocs.At(ri)
if target.IsWasm() && r.Type() == objabi.R_ADDR {
@@ -178,7 +146,7 @@ func (state *oldPclnState) computeDeferReturn(target *Target, s loader.Sym) uint
// set the resumption point to PC_B.
lastWasmAddr = uint32(r.Add())
}
- if r.Type().IsDirectCall() && (r.Sym() == state.deferReturnSym || state.ldr.IsDeferReturnTramp(r.Sym())) {
+ if r.Type().IsDirectCall() && (r.Sym() == deferReturnSym || ldr.IsDeferReturnTramp(r.Sym())) {
if target.IsWasm() {
deferreturn = lastWasmAddr - 1
} else {
@@ -211,8 +179,8 @@ func (state *oldPclnState) computeDeferReturn(target *Target, s loader.Sym) uint
// genInlTreeSym generates the InlTree sym for a function with the
// specified FuncInfo.
-func (state *oldPclnState) genInlTreeSym(cu *sym.CompilationUnit, fi loader.FuncInfo, arch *sys.Arch, newState *pclntab) loader.Sym {
- ldr := state.ldr
+func genInlTreeSym(ctxt *Link, cu *sym.CompilationUnit, fi loader.FuncInfo, arch *sys.Arch, nameOffsets map[loader.Sym]uint32) loader.Sym {
+ ldr := ctxt.loader
its := ldr.CreateExtSym("", 0)
inlTreeSym := ldr.MakeSymbolUpdater(its)
// Note: the generated symbol is given a type of sym.SGOFUNC, as a
@@ -225,7 +193,7 @@ func (state *oldPclnState) genInlTreeSym(cu *sym.CompilationUnit, fi loader.Func
for i := 0; i < int(ninl); i++ {
call := fi.InlTree(i)
val := call.File
- nameoff, ok := newState.funcNameOffset[call.Func]
+ nameoff, ok := nameOffsets[call.Func]
if !ok {
panic("couldn't find function name offset")
}
@@ -282,16 +250,12 @@ func (state *pclntab) generatePCHeader(ctxt *Link) {
state.pcheader = state.addGeneratedSym(ctxt, "runtime.pcheader", size, writeHeader)
}
-// walkFuncs iterates over the Textp, calling a function for each unique
+// walkFuncs iterates over the funcs, calling a function for each unique
// function and inlined function.
-func (state *pclntab) walkFuncs(ctxt *Link, container loader.Bitmap, f func(loader.Sym)) {
+func walkFuncs(ctxt *Link, funcs []loader.Sym, f func(loader.Sym)) {
ldr := ctxt.loader
seen := make(map[loader.Sym]struct{})
- for _, ls := range ctxt.Textp {
- s := loader.Sym(ls)
- if !emitPcln(ctxt, s, container) {
- continue
- }
+ for _, s := range funcs {
if _, ok := seen[s]; !ok {
f(s)
seen[s] = struct{}{}
@@ -312,37 +276,37 @@ func (state *pclntab) walkFuncs(ctxt *Link, container loader.Bitmap, f func(load
}
}
-// generateFuncnametab creates the function name table.
-func (state *pclntab) generateFuncnametab(ctxt *Link, container loader.Bitmap) {
+// generateFuncnametab creates the function name table. Returns a map of
+// func symbol to the name offset in runtime.funcnamtab.
+func (state *pclntab) generateFuncnametab(ctxt *Link, funcs []loader.Sym) map[loader.Sym]uint32 {
+ nameOffsets := make(map[loader.Sym]uint32, state.nfunc)
+
// Write the null terminated strings.
writeFuncNameTab := func(ctxt *Link, s loader.Sym) {
symtab := ctxt.loader.MakeSymbolUpdater(s)
- for s, off := range state.funcNameOffset {
+ for s, off := range nameOffsets {
symtab.AddStringAt(int64(off), ctxt.loader.SymName(s))
}
}
// Loop through the CUs, and calculate the size needed.
var size int64
- state.walkFuncs(ctxt, container, func(s loader.Sym) {
- state.funcNameOffset[s] = int32(size)
+ walkFuncs(ctxt, funcs, func(s loader.Sym) {
+ nameOffsets[s] = uint32(size)
size += int64(ctxt.loader.SymNameLen(s)) + 1 // NULL terminate
})
state.funcnametab = state.addGeneratedSym(ctxt, "runtime.funcnametab", size, writeFuncNameTab)
+ return nameOffsets
}
-// walkFilenames walks the filenames in the all reachable functions.
-func walkFilenames(ctxt *Link, container loader.Bitmap, f func(*sym.CompilationUnit, goobj.CUFileIndex)) {
+// walkFilenames walks funcs, calling a function for each filename used in each
+// function's line table.
+func walkFilenames(ctxt *Link, funcs []loader.Sym, f func(*sym.CompilationUnit, goobj.CUFileIndex)) {
ldr := ctxt.loader
// Loop through all functions, finding the filenames we need.
- for _, ls := range ctxt.Textp {
- s := loader.Sym(ls)
- if !emitPcln(ctxt, s, container) {
- continue
- }
-
+ for _, s := range funcs {
fi := ldr.FuncInfo(s)
if !fi.Valid() {
continue
@@ -382,7 +346,7 @@ func walkFilenames(ctxt *Link, container loader.Bitmap, f func(*sym.CompilationU
// 1) Get Func.CUIndex: M := func.cuOffset
// 2) Find filename offset: fileOffset := runtime.cutab[M+K]
// 3) Get the filename: getcstring(runtime.filetab[fileOffset])
-func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.CompilationUnit, container loader.Bitmap) []uint32 {
+func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.CompilationUnit, funcs []loader.Sym) []uint32 {
// On a per-CU basis, keep track of all the filenames we need.
//
// Note, that we store the filenames in a separate section in the object
@@ -402,7 +366,7 @@ func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.Compilat
// file index we've seen per CU so we can calculate how large the
// CU->global table needs to be.
var fileSize int64
- walkFilenames(ctxt, container, func(cu *sym.CompilationUnit, i goobj.CUFileIndex) {
+ walkFilenames(ctxt, funcs, func(cu *sym.CompilationUnit, i goobj.CUFileIndex) {
// Note we use the raw filename for lookup, but use the expanded filename
// when we save the size.
filename := cu.FileTable[i]
@@ -467,7 +431,7 @@ func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.Compilat
// generatePctab creates the runtime.pctab variable, holding all the
// deduplicated pcdata.
-func (state *pclntab) generatePctab(ctxt *Link, container loader.Bitmap) {
+func (state *pclntab) generatePctab(ctxt *Link, funcs []loader.Sym) {
ldr := ctxt.loader
// Pctab offsets of 0 are considered invalid in the runtime. We respect
@@ -490,10 +454,7 @@ func (state *pclntab) generatePctab(ctxt *Link, container loader.Bitmap) {
seen[pcSym] = struct{}{}
}
}
- for _, s := range ctxt.Textp {
- if !emitPcln(ctxt, s, container) {
- continue
- }
+ for _, s := range funcs {
fi := ldr.FuncInfo(s)
if !fi.Valid() {
continue
@@ -566,8 +527,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
// end PC [thearch.ptrsize bytes]
// func structures, pcdata tables.
- oldState := makeOldPclnState(ctxt)
- state, compUnits := makePclntab(ctxt, container)
+ state, compUnits, funcs := makePclntab(ctxt, container)
ldr := ctxt.loader
state.carrier = ldr.LookupOrCreateSym("runtime.pclntab", 0)
@@ -579,9 +539,12 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
// rational form.
state.pclntab = ldr.LookupOrCreateSym("runtime.pclntab_old", 0)
state.generatePCHeader(ctxt)
- state.generateFuncnametab(ctxt, container)
- cuOffsets := state.generateFilenameTabs(ctxt, compUnits, container)
- state.generatePctab(ctxt, container)
+ nameOffsets := state.generateFuncnametab(ctxt, funcs)
+ cuOffsets := state.generateFilenameTabs(ctxt, compUnits, funcs)
+ state.generatePctab(ctxt, funcs)
+
+ // Used to when computing defer return.
+ deferReturnSym := ldr.Lookup("runtime.deferreturn", sym.SymVerABIInternal)
funcdataBytes := int64(0)
ldr.SetCarrierSym(state.pclntab, state.carrier)
@@ -613,11 +576,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
var nfunc int32
prevFunc := ctxt.Textp[0]
- for _, s := range ctxt.Textp {
- if !emitPcln(ctxt, s, container) {
- continue
- }
-
+ for _, s := range funcs {
thisSect := ldr.SymSect(s)
prevSect := ldr.SymSect(prevFunc)
if thisSect != prevSect {
@@ -686,7 +645,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
off = int32(setAddr(ftab, ctxt.Arch, int64(off), s, 0))
// name int32
- nameoff, ok := state.funcNameOffset[s]
+ nameoff, ok := nameOffsets[s]
if !ok {
panic("couldn't find function name offset")
}
@@ -701,13 +660,13 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
off = int32(ftab.SetUint32(ctxt.Arch, int64(off), args))
// deferreturn
- deferreturn := oldState.computeDeferReturn(&ctxt.Target, s)
+ deferreturn := computeDeferReturn(ctxt, deferReturnSym, s)
off = int32(ftab.SetUint32(ctxt.Arch, int64(off), deferreturn))
cu := ldr.SymUnit(s)
if fi.Valid() && fi.NumInlTree() > 0 {
- its := oldState.genInlTreeSym(cu, fi, ctxt.Arch, state)
+ its := genInlTreeSym(ctxt, cu, fi, ctxt.Arch, nameOffsets)
funcdata[objabi.FUNCDATA_InlTree] = its
}
--
cgit v1.3
From 5402d40d5b041399392b29e4543f5fc4506197bd Mon Sep 17 00:00:00 2001
From: Jeremy Faller
Date: Tue, 18 Aug 2020 16:35:26 -0400
Subject: [dev.link] cmd/link: fix memory growth on dev.link
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
CL 247399 caused memory growth in the linker. Fix this by adjusting how
we preallocate the number of symbols we'll need.
cmd/compile (Darwin), alloc/op:
Loadlib_GC 33.5MB ± 0% 27.3MB ± 0%
Change-Id: I34997329ea4412716114df97fc9dad6ad0c171ee
Reviewed-on: https://go-review.googlesource.com/c/go/+/249024
Run-TryBot: Jeremy Faller
Reviewed-by: Cherry Zhang
Reviewed-by: Austin Clements
TryBot-Result: Gobot Gobot
---
src/cmd/link/internal/ld/lib.go | 2 +-
src/cmd/link/internal/loader/loader.go | 36 ++++++++++++++++++++++------------
2 files changed, 25 insertions(+), 13 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
index caa4566190..a01bdefa37 100644
--- a/src/cmd/link/internal/ld/lib.go
+++ b/src/cmd/link/internal/ld/lib.go
@@ -543,7 +543,7 @@ func (ctxt *Link) loadlib() {
}
// Add non-package symbols and references of externally defined symbols.
- ctxt.loader.LoadNonpkgSyms(ctxt.Arch)
+ ctxt.loader.LoadSyms(ctxt.Arch)
// Load symbols from shared libraries, after all Go object symbols are loaded.
for _, lib := range ctxt.Library {
diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go
index f149e3c831..ea9cd1bd2e 100644
--- a/src/cmd/link/internal/loader/loader.go
+++ b/src/cmd/link/internal/loader/loader.go
@@ -328,7 +328,7 @@ func NewLoader(flags uint32, elfsetstring elfsetstringFunc, reporter *ErrorRepor
ldr := &Loader{
start: make(map[*oReader]Sym),
objs: []objIdx{{}, {extReader, 0}}, // reserve index 0 for nil symbol, 1 for external symbols
- objSyms: make([]objSym, 1, 100000), // reserve index 0 for nil symbol
+ objSyms: make([]objSym, 1, 1), // This will get overwritten later.
extReader: extReader,
symsByName: [2]map[string]Sym{make(map[string]Sym, 80000), make(map[string]Sym, 50000)}, // preallocate ~2MB for ABI0 and ~1MB for ABI1 symbols
objByPkg: make(map[string]*oReader),
@@ -2016,8 +2016,9 @@ func (l *Loader) FuncInfo(i Sym) FuncInfo {
return FuncInfo{}
}
-// Preload a package: add autolibs, add defined package symbols to the symbol table.
-// Does not add non-package symbols yet, which will be done in LoadNonpkgSyms.
+// Preload a package: adds autolib.
+// Does not add defined package or non-packaged symbols to the symbol table.
+// These are done in LoadSyms.
// Does not read symbol data.
// Returns the fingerprint of the object.
func (l *Loader) Preload(localSymVersion int, f *bio.Reader, lib *sym.Library, unit *sym.CompilationUnit, length int64) goobj.FingerprintType {
@@ -2060,8 +2061,6 @@ func (l *Loader) Preload(localSymVersion int, f *bio.Reader, lib *sym.Library, u
}
l.addObj(lib.Pkg, or)
- st := loadState{l: l}
- st.preloadSyms(or, pkgDef)
// The caller expects us consuming all the data
f.MustSeek(length, os.SEEK_CUR)
@@ -2144,17 +2143,30 @@ func (st *loadState) preloadSyms(r *oReader, kind int) {
}
}
-// Add hashed (content-addressable) symbols, non-package symbols, and
+// Add syms, hashed (content-addressable) symbols, non-package symbols, and
// references to external symbols (which are always named).
-func (l *Loader) LoadNonpkgSyms(arch *sys.Arch) {
+func (l *Loader) LoadSyms(arch *sys.Arch) {
+ // Allocate space for symbols, making a guess as to how much space we need.
+ // This function was determined empirically by looking at the cmd/compile on
+ // Darwin, and picking factors for hashed and hashed64 syms.
+ var symSize, hashedSize, hashed64Size int
+ for _, o := range l.objs[goObjStart:] {
+ symSize += o.r.ndef + o.r.nhasheddef/2 + o.r.nhashed64def/2 + o.r.NNonpkgdef()
+ hashedSize += o.r.nhasheddef / 2
+ hashed64Size += o.r.nhashed64def / 2
+ }
+ // Index 0 is invalid for symbols.
+ l.objSyms = make([]objSym, 1, symSize)
+
l.npkgsyms = l.NSym()
- // Preallocate some space (a few hundreds KB) for some symbols.
- // As of Go 1.15, linking cmd/compile has ~8000 hashed64 symbols and
- // ~13000 hashed symbols.
st := loadState{
l: l,
- hashed64Syms: make(map[uint64]symAndSize, 10000),
- hashedSyms: make(map[goobj.HashType]symAndSize, 15000),
+ hashed64Syms: make(map[uint64]symAndSize, hashed64Size),
+ hashedSyms: make(map[goobj.HashType]symAndSize, hashedSize),
+ }
+
+ for _, o := range l.objs[goObjStart:] {
+ st.preloadSyms(o.r, pkgDef)
}
for _, o := range l.objs[goObjStart:] {
st.preloadSyms(o.r, hashed64Def)
--
cgit v1.3
From bdb480fd623e58d0d1d0689a3755367379ea57bc Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Tue, 8 Sep 2020 15:28:43 +0700
Subject: cmd/compile: fix mishandling of unsafe-uintptr arguments in go/defer
Currently, the statement:
go g(uintptr(f()))
gets rewritten into:
tmp := f()
newproc(8, g, uintptr(tmp))
runtime.KeepAlive(tmp)
which doesn't guarantee that tmp is still alive by time the g call is
scheduled to run.
This CL fixes the issue, by wrapping g call in a closure:
go func(p unsafe.Pointer) {
g(uintptr(p))
}(f())
then this will be rewritten into:
tmp := f()
go func(p unsafe.Pointer) {
g(uintptr(p))
runtime.KeepAlive(p)
}(tmp)
runtime.KeepAlive(tmp) // superfluous, but harmless
So the unsafe.Pointer p will be kept alive at the time g call runs.
Updates #24491
Change-Id: Ic10821251cbb1b0073daec92b82a866c6ebaf567
Reviewed-on: https://go-review.googlesource.com/c/go/+/253457
Run-TryBot: Cuong Manh Le
Reviewed-by: Matthew Dempsky
TryBot-Result: Gobot Gobot
---
src/cmd/compile/internal/gc/order.go | 1 +
src/cmd/compile/internal/gc/syntax.go | 41 +++++++++++++++++---------
src/cmd/compile/internal/gc/walk.go | 54 ++++++++++++++++++++++++++++-------
test/fixedbugs/issue24491.go | 45 +++++++++++++++++++++++++++++
4 files changed, 117 insertions(+), 24 deletions(-)
create mode 100644 test/fixedbugs/issue24491.go
(limited to 'src/cmd')
diff --git a/src/cmd/compile/internal/gc/order.go b/src/cmd/compile/internal/gc/order.go
index aa91160e5c..412f073a8d 100644
--- a/src/cmd/compile/internal/gc/order.go
+++ b/src/cmd/compile/internal/gc/order.go
@@ -502,6 +502,7 @@ func (o *Order) call(n *Node) {
x := o.copyExpr(arg.Left, arg.Left.Type, false)
x.Name.SetKeepalive(true)
arg.Left = x
+ n.SetNeedsWrapper(true)
}
}
diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go
index 47e5e59156..5580f789c5 100644
--- a/src/cmd/compile/internal/gc/syntax.go
+++ b/src/cmd/compile/internal/gc/syntax.go
@@ -141,19 +141,20 @@ const (
nodeInitorder, _ // tracks state during init1; two bits
_, _ // second nodeInitorder bit
_, nodeHasBreak
- _, nodeNoInline // used internally by inliner to indicate that a function call should not be inlined; set for OCALLFUNC and OCALLMETH only
- _, nodeImplicit // implicit OADDR or ODEREF; ++/-- statement represented as OASOP; or ANDNOT lowered to OAND
- _, nodeIsDDD // is the argument variadic
- _, nodeDiag // already printed error about this
- _, nodeColas // OAS resulting from :=
- _, nodeNonNil // guaranteed to be non-nil
- _, nodeTransient // storage can be reused immediately after this statement
- _, nodeBounded // bounds check unnecessary
- _, nodeHasCall // expression contains a function call
- _, nodeLikely // if statement condition likely
- _, nodeHasVal // node.E contains a Val
- _, nodeHasOpt // node.E contains an Opt
- _, nodeEmbedded // ODCLFIELD embedded type
+ _, nodeNoInline // used internally by inliner to indicate that a function call should not be inlined; set for OCALLFUNC and OCALLMETH only
+ _, nodeImplicit // implicit OADDR or ODEREF; ++/-- statement represented as OASOP; or ANDNOT lowered to OAND
+ _, nodeIsDDD // is the argument variadic
+ _, nodeDiag // already printed error about this
+ _, nodeColas // OAS resulting from :=
+ _, nodeNonNil // guaranteed to be non-nil
+ _, nodeTransient // storage can be reused immediately after this statement
+ _, nodeBounded // bounds check unnecessary
+ _, nodeHasCall // expression contains a function call
+ _, nodeLikely // if statement condition likely
+ _, nodeHasVal // node.E contains a Val
+ _, nodeHasOpt // node.E contains an Opt
+ _, nodeEmbedded // ODCLFIELD embedded type
+ _, nodeNeedsWrapper // OCALLxxx node that needs to be wrapped
)
func (n *Node) Class() Class { return Class(n.flags.get3(nodeClass)) }
@@ -286,6 +287,20 @@ func (n *Node) SetIota(x int64) {
n.Xoffset = x
}
+func (n *Node) NeedsWrapper() bool {
+ return n.flags&nodeNeedsWrapper != 0
+}
+
+// SetNeedsWrapper indicates that OCALLxxx node needs to be wrapped by a closure.
+func (n *Node) SetNeedsWrapper(b bool) {
+ switch n.Op {
+ case OCALLFUNC, OCALLMETH, OCALLINTER:
+ default:
+ Fatalf("Node.SetNeedsWrapper %v", n.Op)
+ }
+ n.flags.set(nodeNeedsWrapper, b)
+}
+
// mayBeShared reports whether n may occur in multiple places in the AST.
// Extra care must be taken when mutating such a node.
func (n *Node) mayBeShared() bool {
diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go
index 0158af8700..ab7f857031 100644
--- a/src/cmd/compile/internal/gc/walk.go
+++ b/src/cmd/compile/internal/gc/walk.go
@@ -232,7 +232,11 @@ func walkstmt(n *Node) *Node {
n.Left = copyany(n.Left, &n.Ninit, true)
default:
- n.Left = walkexpr(n.Left, &n.Ninit)
+ if n.Left.NeedsWrapper() {
+ n.Left = wrapCall(n.Left, &n.Ninit)
+ } else {
+ n.Left = walkexpr(n.Left, &n.Ninit)
+ }
}
case OFOR, OFORUNTIL:
@@ -3857,6 +3861,14 @@ func candiscard(n *Node) bool {
// builtin(a1, a2, a3)
// }(x, y, z)
// for print, println, and delete.
+//
+// Rewrite
+// go f(x, y, uintptr(unsafe.Pointer(z)))
+// into
+// go func(a1, a2, a3) {
+// builtin(a1, a2, uintptr(a3))
+// }(x, y, unsafe.Pointer(z))
+// for function contains unsafe-uintptr arguments.
var wrapCall_prgen int
@@ -3868,9 +3880,17 @@ func wrapCall(n *Node, init *Nodes) *Node {
init.AppendNodes(&n.Ninit)
}
+ isBuiltinCall := n.Op != OCALLFUNC && n.Op != OCALLMETH && n.Op != OCALLINTER
+ // origArgs keeps track of what argument is uintptr-unsafe/unsafe-uintptr conversion.
+ origArgs := make([]*Node, n.List.Len())
t := nod(OTFUNC, nil, nil)
for i, arg := range n.List.Slice() {
s := lookupN("a", i)
+ if !isBuiltinCall && arg.Op == OCONVNOP && arg.Type.Etype == TUINTPTR && arg.Left.Type.Etype == TUNSAFEPTR {
+ origArgs[i] = arg
+ arg = arg.Left
+ n.List.SetIndex(i, arg)
+ }
t.List.Append(symfield(s, arg.Type))
}
@@ -3878,10 +3898,22 @@ func wrapCall(n *Node, init *Nodes) *Node {
sym := lookupN("wrap·", wrapCall_prgen)
fn := dclfunc(sym, t)
- a := nod(n.Op, nil, nil)
- a.List.Set(paramNnames(t.Type))
- a = typecheck(a, ctxStmt)
- fn.Nbody.Set1(a)
+ args := paramNnames(t.Type)
+ for i, origArg := range origArgs {
+ if origArg == nil {
+ continue
+ }
+ arg := nod(origArg.Op, args[i], nil)
+ arg.Type = origArg.Type
+ args[i] = arg
+ }
+ call := nod(n.Op, nil, nil)
+ if !isBuiltinCall {
+ call.Op = OCALL
+ call.Left = n.Left
+ }
+ call.List.Set(args)
+ fn.Nbody.Set1(call)
funcbody()
@@ -3889,12 +3921,12 @@ func wrapCall(n *Node, init *Nodes) *Node {
typecheckslice(fn.Nbody.Slice(), ctxStmt)
xtop = append(xtop, fn)
- a = nod(OCALL, nil, nil)
- a.Left = fn.Func.Nname
- a.List.Set(n.List.Slice())
- a = typecheck(a, ctxStmt)
- a = walkexpr(a, init)
- return a
+ call = nod(OCALL, nil, nil)
+ call.Left = fn.Func.Nname
+ call.List.Set(n.List.Slice())
+ call = typecheck(call, ctxStmt)
+ call = walkexpr(call, init)
+ return call
}
// substArgTypes substitutes the given list of types for
diff --git a/test/fixedbugs/issue24491.go b/test/fixedbugs/issue24491.go
new file mode 100644
index 0000000000..4703368793
--- /dev/null
+++ b/test/fixedbugs/issue24491.go
@@ -0,0 +1,45 @@
+// run
+
+// Copyright 2020 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.
+
+// This test makes sure unsafe-uintptr arguments are handled correctly.
+
+package main
+
+import (
+ "runtime"
+ "unsafe"
+)
+
+var done = make(chan bool, 1)
+
+func setup() unsafe.Pointer {
+ s := "ok"
+ runtime.SetFinalizer(&s, func(p *string) { *p = "FAIL" })
+ return unsafe.Pointer(&s)
+}
+
+//go:noinline
+//go:uintptrescapes
+func test(s string, p uintptr) {
+ runtime.GC()
+ if *(*string)(unsafe.Pointer(p)) != "ok" {
+ panic(s + " return unexpected result")
+ }
+ done <- true
+}
+
+func main() {
+ test("normal", uintptr(setup()))
+ <-done
+
+ go test("go", uintptr(setup()))
+ <-done
+
+ func() {
+ defer test("defer", uintptr(setup()))
+ }()
+ <-done
+}
--
cgit v1.3
From 9cf88333e8255155be4e136c572883bb5ad546bd Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Wed, 9 Sep 2020 12:06:18 +0700
Subject: cmd/compile: consistently use Type.IsUnsafePtr()
Passes toolstash-check.
Change-Id: Iaeae7cc20e26af733642c7c8c7ca0a059e5b07b2
Reviewed-on: https://go-review.googlesource.com/c/go/+/253657
Run-TryBot: Cuong Manh Le
TryBot-Result: Gobot Gobot
Reviewed-by: Matthew Dempsky
---
src/cmd/compile/internal/gc/escape.go | 6 +++---
src/cmd/compile/internal/gc/ssa.go | 2 +-
src/cmd/compile/internal/gc/subr.go | 4 ++--
src/cmd/compile/internal/gc/walk.go | 10 +++++-----
4 files changed, 11 insertions(+), 11 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/compile/internal/gc/escape.go b/src/cmd/compile/internal/gc/escape.go
index d5cca4a38b..dc469e276c 100644
--- a/src/cmd/compile/internal/gc/escape.go
+++ b/src/cmd/compile/internal/gc/escape.go
@@ -485,7 +485,7 @@ func (e *Escape) exprSkipInit(k EscHole, n *Node) {
e.discard(max)
case OCONV, OCONVNOP:
- if checkPtr(e.curfn, 2) && n.Type.Etype == TUNSAFEPTR && n.Left.Type.IsPtr() {
+ if checkPtr(e.curfn, 2) && n.Type.IsUnsafePtr() && n.Left.Type.IsPtr() {
// When -d=checkptr=2 is enabled, treat
// conversions to unsafe.Pointer as an
// escaping operation. This allows better
@@ -493,7 +493,7 @@ func (e *Escape) exprSkipInit(k EscHole, n *Node) {
// easily detect object boundaries on the heap
// than the stack.
e.assignHeap(n.Left, "conversion to unsafe.Pointer", n)
- } else if n.Type.Etype == TUNSAFEPTR && n.Left.Type.Etype == TUINTPTR {
+ } else if n.Type.IsUnsafePtr() && n.Left.Type.Etype == TUINTPTR {
e.unsafeValue(k, n.Left)
} else {
e.expr(k, n.Left)
@@ -625,7 +625,7 @@ func (e *Escape) unsafeValue(k EscHole, n *Node) {
switch n.Op {
case OCONV, OCONVNOP:
- if n.Left.Type.Etype == TUNSAFEPTR {
+ if n.Left.Type.IsUnsafePtr() {
e.expr(k, n.Left)
} else {
e.discard(n.Left)
diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go
index 52083d999e..89644cd3f2 100644
--- a/src/cmd/compile/internal/gc/ssa.go
+++ b/src/cmd/compile/internal/gc/ssa.go
@@ -2113,7 +2113,7 @@ func (s *state) expr(n *Node) *ssa.Value {
}
// unsafe.Pointer <--> *T
- if to.Etype == TUNSAFEPTR && from.IsPtrShaped() || from.Etype == TUNSAFEPTR && to.IsPtrShaped() {
+ if to.IsUnsafePtr() && from.IsPtrShaped() || from.IsUnsafePtr() && to.IsPtrShaped() {
return v
}
diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go
index 8fa3fca50f..6d0a40c287 100644
--- a/src/cmd/compile/internal/gc/subr.go
+++ b/src/cmd/compile/internal/gc/subr.go
@@ -781,12 +781,12 @@ func convertop(srcConstant bool, src, dst *types.Type, why *string) Op {
}
// 8. src is a pointer or uintptr and dst is unsafe.Pointer.
- if (src.IsPtr() || src.Etype == TUINTPTR) && dst.Etype == TUNSAFEPTR {
+ if (src.IsPtr() || src.Etype == TUINTPTR) && dst.IsUnsafePtr() {
return OCONVNOP
}
// 9. src is unsafe.Pointer and dst is a pointer or uintptr.
- if src.Etype == TUNSAFEPTR && (dst.IsPtr() || dst.Etype == TUINTPTR) {
+ if src.IsUnsafePtr() && (dst.IsPtr() || dst.Etype == TUINTPTR) {
return OCONVNOP
}
diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go
index ab7f857031..a9fefb3ddd 100644
--- a/src/cmd/compile/internal/gc/walk.go
+++ b/src/cmd/compile/internal/gc/walk.go
@@ -958,11 +958,11 @@ opswitch:
case OCONV, OCONVNOP:
n.Left = walkexpr(n.Left, init)
if n.Op == OCONVNOP && checkPtr(Curfn, 1) {
- if n.Type.IsPtr() && n.Left.Type.Etype == TUNSAFEPTR { // unsafe.Pointer to *T
+ if n.Type.IsPtr() && n.Left.Type.IsUnsafePtr() { // unsafe.Pointer to *T
n = walkCheckPtrAlignment(n, init, nil)
break
}
- if n.Type.Etype == TUNSAFEPTR && n.Left.Type.Etype == TUINTPTR { // uintptr to unsafe.Pointer
+ if n.Type.IsUnsafePtr() && n.Left.Type.Etype == TUINTPTR { // uintptr to unsafe.Pointer
n = walkCheckPtrArithmetic(n, init)
break
}
@@ -1127,7 +1127,7 @@ opswitch:
n.List.SetSecond(walkexpr(n.List.Second(), init))
case OSLICE, OSLICEARR, OSLICESTR, OSLICE3, OSLICE3ARR:
- checkSlice := checkPtr(Curfn, 1) && n.Op == OSLICE3ARR && n.Left.Op == OCONVNOP && n.Left.Left.Type.Etype == TUNSAFEPTR
+ checkSlice := checkPtr(Curfn, 1) && n.Op == OSLICE3ARR && n.Left.Op == OCONVNOP && n.Left.Left.Type.IsUnsafePtr()
if checkSlice {
n.Left.Left = walkexpr(n.Left.Left, init)
} else {
@@ -3886,7 +3886,7 @@ func wrapCall(n *Node, init *Nodes) *Node {
t := nod(OTFUNC, nil, nil)
for i, arg := range n.List.Slice() {
s := lookupN("a", i)
- if !isBuiltinCall && arg.Op == OCONVNOP && arg.Type.Etype == TUINTPTR && arg.Left.Type.Etype == TUNSAFEPTR {
+ if !isBuiltinCall && arg.Op == OCONVNOP && arg.Type.Etype == TUINTPTR && arg.Left.Type.IsUnsafePtr() {
origArgs[i] = arg
arg = arg.Left
n.List.SetIndex(i, arg)
@@ -4041,7 +4041,7 @@ func walkCheckPtrArithmetic(n *Node, init *Nodes) *Node {
walk(n.Left)
}
case OCONVNOP:
- if n.Left.Type.Etype == TUNSAFEPTR {
+ if n.Left.Type.IsUnsafePtr() {
n.Left = cheapexpr(n.Left, init)
originals = append(originals, convnop(n.Left, types.Types[TUNSAFEPTR]))
}
--
cgit v1.3
From 518369601ca2499cea68af86451f17d2856895f8 Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Wed, 9 Sep 2020 12:09:26 +0700
Subject: cmd/compile: add Type.IsUintptr() to detect type is an uintptr
Passes toolstash-check.
Change-Id: I7051d45eafbfd4dea73a3d4b5ea6cff39d76cbc1
Reviewed-on: https://go-review.googlesource.com/c/go/+/253658
Run-TryBot: Cuong Manh Le
TryBot-Result: Gobot Gobot
Reviewed-by: Matthew Dempsky
---
src/cmd/compile/internal/gc/esc.go | 6 +++---
src/cmd/compile/internal/gc/escape.go | 2 +-
src/cmd/compile/internal/gc/subr.go | 4 ++--
src/cmd/compile/internal/gc/walk.go | 4 ++--
src/cmd/compile/internal/types/type.go | 5 +++++
5 files changed, 13 insertions(+), 8 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/compile/internal/gc/esc.go b/src/cmd/compile/internal/gc/esc.go
index 4b843aba35..375331d1f5 100644
--- a/src/cmd/compile/internal/gc/esc.go
+++ b/src/cmd/compile/internal/gc/esc.go
@@ -377,7 +377,7 @@ func (e *Escape) paramTag(fn *Node, narg int, f *types.Field) string {
// This really doesn't have much to do with escape analysis per se,
// but we are reusing the ability to annotate an individual function
// argument and pass those annotations along to importing code.
- if f.Type.Etype == TUINTPTR {
+ if f.Type.IsUintptr() {
if Debug['m'] != 0 {
Warnl(f.Pos, "assuming %v is unsafe uintptr", name())
}
@@ -407,13 +407,13 @@ func (e *Escape) paramTag(fn *Node, narg int, f *types.Field) string {
}
if fn.Func.Pragma&UintptrEscapes != 0 {
- if f.Type.Etype == TUINTPTR {
+ if f.Type.IsUintptr() {
if Debug['m'] != 0 {
Warnl(f.Pos, "marking %v as escaping uintptr", name())
}
return uintptrEscapesTag
}
- if f.IsDDD() && f.Type.Elem().Etype == TUINTPTR {
+ if f.IsDDD() && f.Type.Elem().IsUintptr() {
// final argument is ...uintptr.
if Debug['m'] != 0 {
Warnl(f.Pos, "marking %v as escaping ...uintptr", name())
diff --git a/src/cmd/compile/internal/gc/escape.go b/src/cmd/compile/internal/gc/escape.go
index dc469e276c..75da439bb7 100644
--- a/src/cmd/compile/internal/gc/escape.go
+++ b/src/cmd/compile/internal/gc/escape.go
@@ -493,7 +493,7 @@ func (e *Escape) exprSkipInit(k EscHole, n *Node) {
// easily detect object boundaries on the heap
// than the stack.
e.assignHeap(n.Left, "conversion to unsafe.Pointer", n)
- } else if n.Type.IsUnsafePtr() && n.Left.Type.Etype == TUINTPTR {
+ } else if n.Type.IsUnsafePtr() && n.Left.Type.IsUintptr() {
e.unsafeValue(k, n.Left)
} else {
e.expr(k, n.Left)
diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go
index 6d0a40c287..d3ba53ff0c 100644
--- a/src/cmd/compile/internal/gc/subr.go
+++ b/src/cmd/compile/internal/gc/subr.go
@@ -781,12 +781,12 @@ func convertop(srcConstant bool, src, dst *types.Type, why *string) Op {
}
// 8. src is a pointer or uintptr and dst is unsafe.Pointer.
- if (src.IsPtr() || src.Etype == TUINTPTR) && dst.IsUnsafePtr() {
+ if (src.IsPtr() || src.IsUintptr()) && dst.IsUnsafePtr() {
return OCONVNOP
}
// 9. src is unsafe.Pointer and dst is a pointer or uintptr.
- if src.IsUnsafePtr() && (dst.IsPtr() || dst.Etype == TUINTPTR) {
+ if src.IsUnsafePtr() && (dst.IsPtr() || dst.IsUintptr()) {
return OCONVNOP
}
diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go
index a9fefb3ddd..361de7e0f3 100644
--- a/src/cmd/compile/internal/gc/walk.go
+++ b/src/cmd/compile/internal/gc/walk.go
@@ -962,7 +962,7 @@ opswitch:
n = walkCheckPtrAlignment(n, init, nil)
break
}
- if n.Type.IsUnsafePtr() && n.Left.Type.Etype == TUINTPTR { // uintptr to unsafe.Pointer
+ if n.Type.IsUnsafePtr() && n.Left.Type.IsUintptr() { // uintptr to unsafe.Pointer
n = walkCheckPtrArithmetic(n, init)
break
}
@@ -3886,7 +3886,7 @@ func wrapCall(n *Node, init *Nodes) *Node {
t := nod(OTFUNC, nil, nil)
for i, arg := range n.List.Slice() {
s := lookupN("a", i)
- if !isBuiltinCall && arg.Op == OCONVNOP && arg.Type.Etype == TUINTPTR && arg.Left.Type.IsUnsafePtr() {
+ if !isBuiltinCall && arg.Op == OCONVNOP && arg.Type.IsUintptr() && arg.Left.Type.IsUnsafePtr() {
origArgs[i] = arg
arg = arg.Left
n.List.SetIndex(i, arg)
diff --git a/src/cmd/compile/internal/types/type.go b/src/cmd/compile/internal/types/type.go
index e4b3d885d9..a777a5fd90 100644
--- a/src/cmd/compile/internal/types/type.go
+++ b/src/cmd/compile/internal/types/type.go
@@ -1230,6 +1230,11 @@ func (t *Type) IsUnsafePtr() bool {
return t.Etype == TUNSAFEPTR
}
+// IsUintptr reports whether t is an uintptr.
+func (t *Type) IsUintptr() bool {
+ return t.Etype == TUINTPTR
+}
+
// IsPtrShaped reports whether t is represented by a single machine pointer.
// In addition to regular Go pointer types, this includes map, channel, and
// function types and unsafe.Pointer. It does not include array or struct types
--
cgit v1.3
From bdad4285709d1c5e04458268880775087be63027 Mon Sep 17 00:00:00 2001
From: Jay Conrod
Date: Fri, 28 Aug 2020 15:05:44 -0400
Subject: cmd/go: make 'go get' preserve sums for content of new requirements
This preserves zip sums when 'go get' is run on a module that does not
have a package in the root directory. The zip must be fetched to
determine whether the package should be loaded, so we already load and
verify the sum.
Note that 'go mod tidy' may still remove these sums, since they
aren't needed to load packages.
Fixes #41103
Change-Id: I78f10a25f0392461fdc98518a7c92a38ee3233c3
Reviewed-on: https://go-review.googlesource.com/c/go/+/251880
Run-TryBot: Jay Conrod
TryBot-Result: Gobot Gobot
Reviewed-by: Bryan C. Mills
---
src/cmd/go/internal/modload/init.go | 38 +++++++++++++++++------
src/cmd/go/testdata/script/mod_get_sum_noroot.txt | 11 +++++++
2 files changed, 39 insertions(+), 10 deletions(-)
create mode 100644 src/cmd/go/testdata/script/mod_get_sum_noroot.txt
(limited to 'src/cmd')
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 6f93b88eab..8e8fb9e6a1 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -862,14 +862,11 @@ func WriteGoMod() {
}
}
- // Always update go.sum, even if we didn't change go.mod: we may have
- // downloaded modules that we didn't have before.
- modfetch.WriteGoSum(keepSums())
-
if !dirty && cfg.CmdName != "mod tidy" {
// The go.mod file has the same semantic content that it had before
// (but not necessarily the same exact bytes).
- // Ignore any intervening edits.
+ // Don't write go.mod, but write go.sum in case we added or trimmed sums.
+ modfetch.WriteGoSum(keepSums(true))
return
}
@@ -880,6 +877,9 @@ func WriteGoMod() {
defer func() {
// At this point we have determined to make the go.mod file on disk equal to new.
index = indexModFile(new, modFile, false)
+
+ // Update go.sum after releasing the side lock and refreshing the index.
+ modfetch.WriteGoSum(keepSums(true))
}()
// Make a best-effort attempt to acquire the side lock, only to exclude
@@ -920,7 +920,10 @@ func WriteGoMod() {
// the last load function like ImportPaths, LoadALL, etc.). It also contains
// entries for go.mod files needed for MVS (the version of these entries
// ends with "/go.mod").
-func keepSums() map[module.Version]bool {
+//
+// If addDirect is true, the set also includes sums for modules directly
+// required by go.mod, as represented by the index, with replacements applied.
+func keepSums(addDirect bool) map[module.Version]bool {
// Walk the module graph and keep sums needed by MVS.
modkey := func(m module.Version) module.Version {
return module.Version{Path: m.Path, Version: m.Version + "/go.mod"}
@@ -932,9 +935,6 @@ func keepSums() map[module.Version]bool {
walk = func(m module.Version) {
// If we build using a replacement module, keep the sum for the replacement,
// since that's the code we'll actually use during a build.
- //
- // TODO(golang.org/issue/29182): Perhaps we should keep both sums, and the
- // sums for both sets of transitive requirements.
r := Replacement(m)
if r.Path == "" {
keep[modkey(m)] = true
@@ -964,9 +964,27 @@ func keepSums() map[module.Version]bool {
}
}
+ // Add entries for modules directly required by go.mod.
+ if addDirect {
+ for m := range index.require {
+ var kept module.Version
+ if r := Replacement(m); r.Path != "" {
+ kept = r
+ } else {
+ kept = m
+ }
+ keep[kept] = true
+ keep[module.Version{Path: kept.Path, Version: kept.Version + "/go.mod"}] = true
+ }
+ }
+
return keep
}
func TrimGoSum() {
- modfetch.TrimGoSum(keepSums())
+ // Don't retain sums for direct requirements in go.mod. When TrimGoSum is
+ // called, go.mod has not been updated, and it may contain requirements on
+ // modules deleted from the build list.
+ addDirect := false
+ modfetch.TrimGoSum(keepSums(addDirect))
}
diff --git a/src/cmd/go/testdata/script/mod_get_sum_noroot.txt b/src/cmd/go/testdata/script/mod_get_sum_noroot.txt
new file mode 100644
index 0000000000..0d9a840e77
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_get_sum_noroot.txt
@@ -0,0 +1,11 @@
+# When 'go get' is invoked on a module without a package in the root directory,
+# it should add sums for the module's go.mod file and its content to go.sum.
+# Verifies golang.org/issue/41103.
+go mod init m
+go get rsc.io/QUOTE
+grep '^rsc.io/QUOTE v1.5.2/go.mod ' go.sum
+grep '^rsc.io/QUOTE v1.5.2 ' go.sum
+
+# Double-check rsc.io/QUOTE does not have a root package.
+! go list -mod=readonly rsc.io/QUOTE
+stderr '^cannot find module providing package rsc.io/QUOTE: import lookup disabled by -mod=readonly$'
--
cgit v1.3
From 83ed734df03fd11d71f06bc02906a723afaf3936 Mon Sep 17 00:00:00 2001
From: Cherry Zhang
Date: Tue, 11 Aug 2020 19:57:50 -0400
Subject: cmd/link: pre-resolve package reference
Pre-resolve package index references, so it doesn't need to do a
map lookup in every cross-package symbol reference resolution. It
increases the memory usage very slightly (O(# imported packages)).
Change-Id: Ia76c97ac51f1c2c2d5ea7ae34853850ec69ef0a8
Reviewed-on: https://go-review.googlesource.com/c/go/+/253604
Run-TryBot: Cherry Zhang
Reviewed-by: Than McIntosh
TryBot-Result: Gobot Gobot
---
src/cmd/link/internal/loader/loader.go | 36 +++++++++++++++++++++-------------
1 file changed, 22 insertions(+), 14 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go
index 8fd10b0848..43a0352e0b 100644
--- a/src/cmd/link/internal/loader/loader.go
+++ b/src/cmd/link/internal/loader/loader.go
@@ -93,11 +93,12 @@ type oReader struct {
version int // version of static symbol
flags uint32 // read from object file
pkgprefix string
- syms []Sym // Sym's global index, indexed by local index
- ndef int // cache goobj.Reader.NSym()
- nhashed64def int // cache goobj.Reader.NHashed64Def()
- nhasheddef int // cache goobj.Reader.NHashedDef()
- objidx uint32 // index of this reader in the objs slice
+ syms []Sym // Sym's global index, indexed by local index
+ pkg []uint32 // indices of referenced package by PkgIdx (index into loader.objs array)
+ ndef int // cache goobj.Reader.NSym()
+ nhashed64def int // cache goobj.Reader.NHashed64Def()
+ nhasheddef int // cache goobj.Reader.NHashedDef()
+ objidx uint32 // index of this reader in the objs slice
}
// Total number of defined symbols (package symbols, hashed symbols, and
@@ -219,7 +220,7 @@ type Loader struct {
deferReturnTramp map[Sym]bool // whether the symbol is a trampoline of a deferreturn call
- objByPkg map[string]*oReader // map package path to its Go object reader
+ objByPkg map[string]uint32 // map package path to the index of its Go object reader
anonVersion int // most recently assigned ext static sym pseudo-version
@@ -331,7 +332,7 @@ func NewLoader(flags uint32, elfsetstring elfsetstringFunc, reporter *ErrorRepor
objSyms: make([]objSym, 1, 100000), // reserve index 0 for nil symbol
extReader: extReader,
symsByName: [2]map[string]Sym{make(map[string]Sym, 80000), make(map[string]Sym, 50000)}, // preallocate ~2MB for ABI0 and ~1MB for ABI1 symbols
- objByPkg: make(map[string]*oReader),
+ objByPkg: make(map[string]uint32),
outer: make(map[Sym]Sym),
sub: make(map[Sym]Sym),
dynimplib: make(map[Sym]string),
@@ -370,7 +371,7 @@ func (l *Loader) addObj(pkg string, r *oReader) Sym {
}
pkg = objabi.PathToPrefix(pkg) // the object file contains escaped package path
if _, ok := l.objByPkg[pkg]; !ok {
- l.objByPkg[pkg] = r
+ l.objByPkg[pkg] = r.objidx
}
i := Sym(len(l.objSyms))
l.start[r] = i
@@ -635,12 +636,7 @@ func (l *Loader) resolve(r *oReader, s goobj.SymRef) Sym {
case goobj.PkgIdxSelf:
rr = r
default:
- pkg := r.Pkg(int(p))
- var ok bool
- rr, ok = l.objByPkg[pkg]
- if !ok {
- log.Fatalf("reference of nonexisted package %s, from %v", pkg, r.unit.Lib)
- }
+ rr = l.objs[r.pkg[p]].r
}
return l.toGlobal(rr, s.SymIdx)
}
@@ -2195,6 +2191,18 @@ func loadObjRefs(l *Loader, r *oReader, arch *sys.Arch) {
}
}
+ // referenced packages
+ npkg := r.NPkg()
+ r.pkg = make([]uint32, npkg)
+ for i := 1; i < npkg; i++ { // PkgIdx 0 is a dummy invalid package
+ pkg := r.Pkg(i)
+ objidx, ok := l.objByPkg[pkg]
+ if !ok {
+ log.Fatalf("reference of nonexisted package %s, from %v", pkg, r.unit.Lib)
+ }
+ r.pkg[i] = objidx
+ }
+
// load flags of package refs
for i, n := 0, r.NRefFlags(); i < n; i++ {
rf := r.RefFlags(i)
--
cgit v1.3
From 9ef3ee339634d1a349e7b9bb4cae32aacc326f61 Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Wed, 9 Sep 2020 23:47:41 +0700
Subject: cmd/link: remove unnecessary type conversion for nitablinks
It's already an uint64.
Change-Id: Ic4cdb957aa4f9245c1ea3f946bcb740f116dd04b
Reviewed-on: https://go-review.googlesource.com/c/go/+/253679
Run-TryBot: Cuong Manh Le
Reviewed-by: Than McIntosh
TryBot-Result: Gobot Gobot
---
src/cmd/link/internal/ld/symtab.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go
index bc880955b8..56363cdaae 100644
--- a/src/cmd/link/internal/ld/symtab.go
+++ b/src/cmd/link/internal/ld/symtab.go
@@ -681,8 +681,8 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind {
itablinkSym := ldr.Lookup("runtime.itablink", 0)
nitablinks := uint64(ldr.SymSize(itablinkSym)) / uint64(ctxt.Arch.PtrSize)
moduledata.AddAddr(ctxt.Arch, itablinkSym)
- moduledata.AddUint(ctxt.Arch, uint64(nitablinks))
- moduledata.AddUint(ctxt.Arch, uint64(nitablinks))
+ moduledata.AddUint(ctxt.Arch, nitablinks)
+ moduledata.AddUint(ctxt.Arch, nitablinks)
// The ptab slice
if ptab := ldr.Lookup("go.plugin.tabs", 0); ptab != 0 && ldr.AttrReachable(ptab) {
ldr.SetAttrLocal(ptab, true)
--
cgit v1.3
From b96d32bd92087470f85cfab99e289e609a593d03 Mon Sep 17 00:00:00 2001
From: "Bryan C. Mills"
Date: Wed, 1 Jul 2020 00:45:34 -0400
Subject: cmd/go/internal/modload: track which packages are in 'all' during
loading
If the user requests the 'all' pattern in addition to explicit roots
outside of 'all', we should not load the transitive dependencies of
those explicit roots as if they were *in* 'all'. Without the '-test'
flag, we should not load test dependencies of any package outside of
'all'. Even *with* the '-test' flag, we should only load test
dependencies of the requested roots, not test dependencies of other
packages that happen to be imported by those roots.
More precise tracking of membership in 'all' will be important when we
implement lazy loading, because membership in 'all' determines which
module dependencies we will record in the main module's go.mod file.
This change also reduces reliance on global state, factors out the
loading process into several smaller functions, and sets us up to
reuse the 'go mod vendor' version of the 'all' pattern for lazy
loading.
For #36460
Fixes #40799
Change-Id: I5ca21c86a860daee1316f732cea131a331d8ddf9
Reviewed-on: https://go-review.googlesource.com/c/go/+/240505
Run-TryBot: Bryan C. Mills
TryBot-Result: Gobot Gobot
Reviewed-by: Jay Conrod
Reviewed-by: Michael Matloob
---
src/cmd/go/internal/modload/load.go | 727 +++++++++++++++++++++---------
src/cmd/go/testdata/script/mod_notall.txt | 4 +-
2 files changed, 525 insertions(+), 206 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index 2a37f1d874..64ef60230e 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -4,6 +4,95 @@
package modload
+// This file contains the module-mode package loader, as well as some accessory
+// functions pertaining to the package import graph.
+//
+// There are several exported entry points into package loading (such as
+// ImportPathsQuiet and LoadALL), but they are all implemented in terms of
+// loadFromRoots, which itself manipulates an instance of the loader struct.
+//
+// Although most of the loading state is maintained in the loader struct,
+// one key piece - the build list - is a global, so that it can be modified
+// separate from the loading operation, such as during "go get"
+// upgrades/downgrades or in "go mod" operations.
+// TODO(#40775): It might be nice to make the loader take and return
+// a buildList rather than hard-coding use of the global.
+//
+// Loading is an iterative process. On each iteration, we try to load the
+// requested packages and their transitive imports, then try to resolve modules
+// for any imported packages that are still missing.
+//
+// The first step of each iteration identifies a set of “root” packages.
+// Normally the root packages are exactly those matching the named pattern
+// arguments. However, for the "all" meta-pattern and related functions
+// (LoadALL, LoadVendor), the final set of packages is computed from the package
+// import graph, and therefore cannot be an initial input to loading that graph.
+// Instead, the root packages for the "all" pattern are those contained in the
+// main module, and allPatternIsRoot parameter to the loader instructs it to
+// dynamically expand those roots to the full "all" pattern as loading
+// progresses.
+//
+// The pkgInAll flag on each loadPkg instance tracks whether that
+// package is known to match the "all" meta-pattern.
+// A package matches the "all" pattern if:
+// - it is in the main module, or
+// - it is imported by any test in the main module, or
+// - it is imported by another package in "all", or
+// - the main module specifies a go version ≤ 1.15, and the package is imported
+// by a *test of* another package in "all".
+//
+// When we implement lazy loading, we will record the modules providing packages
+// in "all" even when we are only loading individual packages, so we set the
+// pkgInAll flag regardless of the whether the "all" pattern is a root.
+// (This is necessary to maintain the “import invariant” described in
+// https://golang.org/design/36460-lazy-module-loading.)
+//
+// Because "go mod vendor" prunes out the tests of vendored packages, the
+// behavior of the "all" pattern with -mod=vendor in Go 1.11–1.15 is the same
+// as the "all" pattern (regardless of the -mod flag) in 1.16+.
+// The allClosesOverTests parameter to the loader indicates whether the "all"
+// pattern should close over tests (as in Go 1.11–1.15) or stop at only those
+// packages transitively imported by the packages and tests in the main module
+// ("all" in Go 1.16+ and "go mod vendor" in Go 1.11+).
+//
+// Note that it is possible for a loaded package NOT to be in "all" even when we
+// are loading the "all" pattern. For example, packages that are transitive
+// dependencies of other roots named on the command line must be loaded, but are
+// not in "all". (The mod_notall test illustrates this behavior.)
+// Similarly, if the LoadTests flag is set but the "all" pattern does not close
+// over test dependencies, then when we load the test of a package that is in
+// "all" but outside the main module, the dependencies of that test will not
+// necessarily themselves be in "all". That configuration does not arise in Go
+// 1.11–1.15, but it will be possible with lazy loading in Go 1.16+.
+//
+// Loading proceeds from the roots, using a parallel work-queue with a limit on
+// the amount of active work (to avoid saturating disks, CPU cores, and/or
+// network connections). Each package is added to the queue the first time it is
+// imported by another package. When we have finished identifying the imports of
+// a package, we add the test for that package if it is needed. A test may be
+// needed if:
+// - the package matches a root pattern and tests of the roots were requested, or
+// - the package is in the main module and the "all" pattern is requested
+// (because the "all" pattern includes the dependencies of tests in the main
+// module), or
+// - the package is in "all" and the definition of "all" we are using includes
+// dependencies of tests (as is the case in Go ≤1.15).
+//
+// After all available packages have been loaded, we examine the results to
+// identify any requested or imported packages that are still missing, and if
+// so, which modules we could add to the module graph in order to make the
+// missing packages available. We add those to the module graph and iterate,
+// until either all packages resolve successfully or we cannot identify any
+// module that would resolve any remaining missing package.
+//
+// If the main module is “tidy” (that is, if "go mod tidy" is a no-op for it)
+// and all requested packages are in "all", then loading completes in a single
+// iteration.
+// TODO(bcmills): We should also be able to load in a single iteration if the
+// requested packages all come from modules that are themselves tidy, regardless
+// of whether those packages are in "all". Today, that requires two iterations
+// if those packages are not found in existing dependencies of the main module.
+
import (
"bytes"
"context"
@@ -14,8 +103,12 @@ import (
"path"
pathpkg "path"
"path/filepath"
+ "reflect"
+ "runtime"
"sort"
"strings"
+ "sync"
+ "sync/atomic"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
@@ -43,10 +136,6 @@ var buildList []module.Version
// loaded is the most recently-used package loader.
// It holds details about individual packages.
-//
-// Note that loaded.buildList is only valid during a load operation;
-// afterward, it is copied back into the global buildList,
-// which should be used instead.
var loaded *loader
// ImportPaths returns the set of packages matching the args (patterns),
@@ -63,7 +152,18 @@ func ImportPaths(ctx context.Context, patterns []string) []*search.Match {
// packages. The build tags should typically be imports.Tags() or
// imports.AnyTags(); a nil map has no special meaning.
func ImportPathsQuiet(ctx context.Context, patterns []string, tags map[string]bool) []*search.Match {
- updateMatches := func(matches []*search.Match, iterating bool) {
+ InitMod(ctx)
+
+ allPatternIsRoot := false
+ var matches []*search.Match
+ for _, pattern := range search.CleanPatterns(patterns) {
+ matches = append(matches, search.NewMatch(pattern))
+ if pattern == "all" {
+ allPatternIsRoot = true
+ }
+ }
+
+ updateMatches := func(ld *loader) {
for _, m := range matches {
switch {
case m.IsLocal():
@@ -90,7 +190,7 @@ func ImportPathsQuiet(ctx context.Context, patterns []string, tags map[string]bo
// indicates that.
ModRoot()
- if !iterating {
+ if ld != nil {
m.AddError(err)
}
continue
@@ -103,19 +203,18 @@ func ImportPathsQuiet(ctx context.Context, patterns []string, tags map[string]bo
case strings.Contains(m.Pattern(), "..."):
m.Errs = m.Errs[:0]
- matchPackages(ctx, m, loaded.tags, includeStd, buildList)
+ matchPackages(ctx, m, tags, includeStd, buildList)
case m.Pattern() == "all":
- loaded.testAll = true
- if iterating {
- // Enumerate the packages in the main module.
- // We'll load the dependencies as we find them.
+ if ld == nil {
+ // The initial roots are the packages in the main module.
+ // loadFromRoots will expand that to "all".
m.Errs = m.Errs[:0]
- matchPackages(ctx, m, loaded.tags, omitStd, []module.Version{Target})
+ matchPackages(ctx, m, tags, omitStd, []module.Version{Target})
} else {
// Starting with the packages in the main module,
// enumerate the full list of "all".
- m.Pkgs = loaded.computePatternAll(m.Pkgs)
+ m.Pkgs = ld.computePatternAll()
}
case m.Pattern() == "std" || m.Pattern() == "cmd":
@@ -129,25 +228,22 @@ func ImportPathsQuiet(ctx context.Context, patterns []string, tags map[string]bo
}
}
- InitMod(ctx)
-
- var matches []*search.Match
- for _, pattern := range search.CleanPatterns(patterns) {
- matches = append(matches, search.NewMatch(pattern))
- }
+ loaded = loadFromRoots(loaderParams{
+ tags: tags,
+ allPatternIsRoot: allPatternIsRoot,
+ allClosesOverTests: true, // until lazy loading in Go 1.16+
- loaded = newLoader(tags)
- loaded.load(func() []string {
- var roots []string
- updateMatches(matches, true)
- for _, m := range matches {
- roots = append(roots, m.Pkgs...)
- }
- return roots
+ listRoots: func() (roots []string) {
+ updateMatches(nil)
+ for _, m := range matches {
+ roots = append(roots, m.Pkgs...)
+ }
+ return roots
+ },
})
// One last pass to finalize wildcards.
- updateMatches(matches, false)
+ updateMatches(loaded)
checkMultiplePaths()
WriteGoMod()
@@ -347,12 +443,14 @@ func ImportFromFiles(ctx context.Context, gofiles []string) {
base.Fatalf("go: %v", err)
}
- loaded = newLoader(tags)
- loaded.load(func() []string {
- var roots []string
- roots = append(roots, imports...)
- roots = append(roots, testImports...)
- return roots
+ loaded = loadFromRoots(loaderParams{
+ tags: tags,
+ listRoots: func() (roots []string) {
+ roots = append(roots, imports...)
+ roots = append(roots, testImports...)
+ return roots
+ },
+ allClosesOverTests: true, // until lazy loading.
})
WriteGoMod()
}
@@ -397,9 +495,14 @@ func LoadBuildList(ctx context.Context) []module.Version {
return buildList
}
+// ReloadBuildList resets the state of loaded packages, then loads and returns
+// the build list set in SetBuildList.
func ReloadBuildList() []module.Version {
- loaded = newLoader(imports.Tags())
- loaded.load(func() []string { return nil })
+ loaded = loadFromRoots(loaderParams{
+ tags: imports.Tags(),
+ listRoots: func() []string { return nil },
+ allClosesOverTests: true, // until lazy loading, but doesn't matter because the root list is empty.
+ })
return buildList
}
@@ -410,6 +513,7 @@ func ReloadBuildList() []module.Version {
// This set is useful for deciding whether a particular import is needed
// anywhere in a module.
func LoadALL(ctx context.Context) []string {
+ InitMod(ctx)
return loadAll(ctx, true)
}
@@ -418,20 +522,18 @@ func LoadALL(ctx context.Context) []string {
// ignored completely.
// This set is useful for identifying the which packages to include in a vendor directory.
func LoadVendor(ctx context.Context) []string {
+ InitMod(ctx)
return loadAll(ctx, false)
}
-func loadAll(ctx context.Context, testAll bool) []string {
- InitMod(ctx)
-
- loaded = newLoader(imports.AnyTags())
- loaded.isALL = true
- loaded.testAll = testAll
- if !testAll {
- loaded.testRoots = true
- }
- all := TargetPackages(ctx, "...")
- loaded.load(func() []string { return all.Pkgs })
+func loadAll(ctx context.Context, closeOverTests bool) []string {
+ inTarget := TargetPackages(ctx, "...")
+ loaded = loadFromRoots(loaderParams{
+ tags: imports.AnyTags(),
+ listRoots: func() []string { return inTarget.Pkgs },
+ allPatternIsRoot: true,
+ allClosesOverTests: closeOverTests,
+ })
checkMultiplePaths()
WriteGoMod()
@@ -443,7 +545,7 @@ func loadAll(ctx context.Context, testAll bool) []string {
}
paths = append(paths, pkg.path)
}
- for _, err := range all.Errs {
+ for _, err := range inTarget.Errs {
base.Errorf("%v", err)
}
base.ExitIfErrors()
@@ -604,75 +706,157 @@ func Lookup(parentPath string, parentIsStd bool, path string) (dir, realPath str
// the required packages for a particular build,
// checking that the packages are available in the module set,
// and updating the module set if needed.
-// Loading is an iterative process: try to load all the needed packages,
-// but if imports are missing, try to resolve those imports, and repeat.
-//
-// Although most of the loading state is maintained in the loader struct,
-// one key piece - the build list - is a global, so that it can be modified
-// separate from the loading operation, such as during "go get"
-// upgrades/downgrades or in "go mod" operations.
-// TODO(rsc): It might be nice to make the loader take and return
-// a buildList rather than hard-coding use of the global.
type loader struct {
- tags map[string]bool // tags for scanDir
- testRoots bool // include tests for roots
- isALL bool // created with LoadALL
- testAll bool // include tests for all packages
- forceStdVendor bool // if true, load standard-library dependencies from the vendor subtree
+ loaderParams
+
+ forceStdVendor bool // if true, load standard-library dependencies from the vendor subtree
+
+ work *par.Queue
// reset on each iteration
roots []*loadPkg
- pkgs []*loadPkg
- work *par.Work // current work queue
- pkgCache *par.Cache // map from string to *loadPkg
+ pkgCache *par.Cache // package path (string) → *loadPkg
+ pkgs []*loadPkg // transitive closure of loaded packages and tests; populated in buildStacks
// computed at end of iterations
direct map[string]bool // imported directly by main module
}
+type loaderParams struct {
+ tags map[string]bool // tags for scanDir
+ listRoots func() []string
+ allPatternIsRoot bool // Is the "all" pattern an additional root?
+ allClosesOverTests bool // Does the "all" pattern include the transitive closure of tests of packages in "all"?
+}
+
// LoadTests controls whether the loaders load tests of the root packages.
var LoadTests bool
-func newLoader(tags map[string]bool) *loader {
- ld := new(loader)
- ld.tags = tags
- ld.testRoots = LoadTests
-
- // Inside the "std" and "cmd" modules, we prefer to use the vendor directory
- // unless the command explicitly changes the module graph.
- if !targetInGorootSrc || (cfg.CmdName != "get" && !strings.HasPrefix(cfg.CmdName, "mod ")) {
- ld.forceStdVendor = true
+func (ld *loader) reset() {
+ select {
+ case <-ld.work.Idle():
+ default:
+ panic("loader.reset when not idle")
}
- return ld
-}
-
-func (ld *loader) reset() {
ld.roots = nil
- ld.pkgs = nil
- ld.work = new(par.Work)
ld.pkgCache = new(par.Cache)
+ ld.pkgs = nil
}
// A loadPkg records information about a single loaded package.
type loadPkg struct {
- path string // import path
+ // Populated at construction time:
+ path string // import path
+ testOf *loadPkg
+
+ // Populated at construction time and updated by (*loader).applyPkgFlags:
+ flags atomicLoadPkgFlags
+
+ // Populated by (*loader).load:
mod module.Version // module providing package
dir string // directory containing source code
- imports []*loadPkg // packages imported by this one
err error // error loading package
- stack *loadPkg // package importing this one in minimal import stack for this pkg
- test *loadPkg // package with test imports, if we need test
- testOf *loadPkg
- testImports []string // test-only imports, saved for use by pkg.test.
+ imports []*loadPkg // packages imported by this one
+ testImports []string // test-only imports, saved for use by pkg.test.
+ inStd bool
+
+ // Populated by (*loader).pkgTest:
+ testOnce sync.Once
+ test *loadPkg
+
+ // Populated by postprocessing in (*loader).buildStacks:
+ stack *loadPkg // package importing this one in minimal import stack for this pkg
+}
+
+// loadPkgFlags is a set of flags tracking metadata about a package.
+type loadPkgFlags int8
+
+const (
+ // pkgInAll indicates that the package is in the "all" package pattern,
+ // regardless of whether we are loading the "all" package pattern.
+ //
+ // When the pkgInAll flag and pkgImportsLoaded flags are both set, the caller
+ // who set the last of those flags must propagate the pkgInAll marking to all
+ // of the imports of the marked package.
+ //
+ // A test is marked with pkgInAll if that test would promote the packages it
+ // imports to be in "all" (such as when the test is itself within the main
+ // module, or when ld.allClosesOverTests is true).
+ pkgInAll loadPkgFlags = 1 << iota
+
+ // pkgIsRoot indicates that the package matches one of the root package
+ // patterns requested by the caller.
+ //
+ // If LoadTests is set, then when pkgIsRoot and pkgImportsLoaded are both set,
+ // the caller who set the last of those flags must populate a test for the
+ // package (in the pkg.test field).
+ //
+ // If the "all" pattern is included as a root, then non-test packages in "all"
+ // are also roots (and must be marked pkgIsRoot).
+ pkgIsRoot
+
+ // pkgImportsLoaded indicates that the imports and testImports fields of a
+ // loadPkg have been populated.
+ pkgImportsLoaded
+)
+
+// has reports whether all of the flags in cond are set in f.
+func (f loadPkgFlags) has(cond loadPkgFlags) bool {
+ return f&cond == cond
+}
+
+// An atomicLoadPkgFlags stores a loadPkgFlags for which individual flags can be
+// added atomically.
+type atomicLoadPkgFlags struct {
+ bits int32
+}
+
+// update sets the given flags in af (in addition to any flags already set).
+//
+// update returns the previous flag state so that the caller may determine which
+// flags were newly-set.
+func (af *atomicLoadPkgFlags) update(flags loadPkgFlags) (old loadPkgFlags) {
+ for {
+ old := atomic.LoadInt32(&af.bits)
+ new := old | int32(flags)
+ if new == old || atomic.CompareAndSwapInt32(&af.bits, old, new) {
+ return loadPkgFlags(old)
+ }
+ }
+}
+
+// has reports whether all of the flags in cond are set in af.
+func (af *atomicLoadPkgFlags) has(cond loadPkgFlags) bool {
+ return loadPkgFlags(atomic.LoadInt32(&af.bits))&cond == cond
+}
+
+// isTest reports whether pkg is a test of another package.
+func (pkg *loadPkg) isTest() bool {
+ return pkg.testOf != nil
}
var errMissing = errors.New("cannot find package")
-// load attempts to load the build graph needed to process a set of root packages.
-// The set of root packages is defined by the addRoots function,
-// which must call add(path) with the import path of each root package.
-func (ld *loader) load(roots func() []string) {
+// loadFromRoots attempts to load the build graph needed to process a set of
+// root packages and their dependencies.
+//
+// The set of root packages is returned by the params.listRoots function, and
+// expanded to the full set of packages by tracing imports (and possibly tests)
+// as needed.
+func loadFromRoots(params loaderParams) *loader {
+ ld := &loader{
+ loaderParams: params,
+ work: par.NewQueue(runtime.GOMAXPROCS(0)),
+ }
+
+ // Inside the "std" and "cmd" modules, we prefer to use the vendor directory
+ // unless the command explicitly changes the module graph.
+ // TODO(bcmills): Is this still needed now that we have automatic vendoring?
+ if !targetInGorootSrc || (cfg.CmdName != "get" && !strings.HasPrefix(cfg.CmdName, "mod ")) {
+ ld.forceStdVendor = true
+ }
+
var err error
reqs := Reqs()
buildList, err = mvs.BuildList(Target, reqs)
@@ -680,47 +864,34 @@ func (ld *loader) load(roots func() []string) {
base.Fatalf("go: %v", err)
}
- added := make(map[string]bool)
+ addedModuleFor := make(map[string]bool)
for {
ld.reset()
- if roots != nil {
- // Note: the returned roots can change on each iteration,
- // since the expansion of package patterns depends on the
- // build list we're using.
- for _, path := range roots() {
- ld.work.Add(ld.pkg(path, true))
+
+ // Load the root packages and their imports.
+ // Note: the returned roots can change on each iteration,
+ // since the expansion of package patterns depends on the
+ // build list we're using.
+ inRoots := map[*loadPkg]bool{}
+ for _, path := range ld.listRoots() {
+ root := ld.pkg(path, pkgIsRoot)
+ if !inRoots[root] {
+ ld.roots = append(ld.roots, root)
+ inRoots[root] = true
}
}
- ld.work.Do(10, ld.doPkg)
+
+ // ld.pkg adds imported packages to the work queue and calls applyPkgFlags,
+ // which adds tests (and test dependencies) as needed.
+ //
+ // When all of the work in the queue has completed, we'll know that the
+ // transitive closure of dependencies has been loaded.
+ <-ld.work.Idle()
+
ld.buildStacks()
- numAdded := 0
- haveMod := make(map[module.Version]bool)
- for _, m := range buildList {
- haveMod[m] = true
- }
- modAddedBy := make(map[module.Version]*loadPkg)
- for _, pkg := range ld.pkgs {
- if err, ok := pkg.err.(*ImportMissingError); ok && err.Module.Path != "" {
- if err.newMissingVersion != "" {
- base.Fatalf("go: %s: package provided by %s at latest version %s but not at required version %s", pkg.stackText(), err.Module.Path, err.Module.Version, err.newMissingVersion)
- }
- fmt.Fprintf(os.Stderr, "go: found %s in %s %s\n", pkg.path, err.Module.Path, err.Module.Version)
- if added[pkg.path] {
- base.Fatalf("go: %s: looping trying to add package", pkg.stackText())
- }
- added[pkg.path] = true
- numAdded++
- if !haveMod[err.Module] {
- haveMod[err.Module] = true
- modAddedBy[err.Module] = pkg
- buildList = append(buildList, err.Module)
- }
- continue
- }
- // Leave other errors for Import or load.Packages to report.
- }
- base.ExitIfErrors()
- if numAdded == 0 {
+
+ modAddedBy := resolveMissingImports(addedModuleFor, ld.pkgs)
+ if len(modAddedBy) == 0 {
break
}
@@ -753,92 +924,257 @@ func (ld *loader) load(roots func() []string) {
}
}
- // Mix in direct markings (really, lack of indirect markings)
- // from go.mod, unless we scanned the whole module
- // and can therefore be sure we know better than go.mod.
- if !ld.isALL && modFile != nil {
+ // If we didn't scan all of the imports from the main module, or didn't use
+ // imports.AnyTags, then we didn't necessarily load every package that
+ // contributes “direct” imports — so we can't safely mark existing
+ // dependencies as indirect-only.
+ // Conservatively mark those dependencies as direct.
+ if modFile != nil && (!ld.allPatternIsRoot || !reflect.DeepEqual(ld.tags, imports.AnyTags())) {
for _, r := range modFile.Require {
if !r.Indirect {
ld.direct[r.Mod.Path] = true
}
}
}
+
+ return ld
}
-// pkg returns the *loadPkg for path, creating and queuing it if needed.
-// If the package should be tested, its test is created but not queued
-// (the test is queued after processing pkg).
-// If isRoot is true, the pkg is being queued as one of the roots of the work graph.
-func (ld *loader) pkg(path string, isRoot bool) *loadPkg {
- return ld.pkgCache.Do(path, func() interface{} {
- pkg := &loadPkg{
- path: path,
+// resolveMissingImports adds module dependencies to the global build list
+// in order to resolve missing packages from pkgs.
+//
+// The newly-resolved packages are added to the addedModuleFor map, and
+// resolveMissingImports returns a map from each newly-added module version to
+// the first package for which that module was added.
+func resolveMissingImports(addedModuleFor map[string]bool, pkgs []*loadPkg) (modAddedBy map[module.Version]*loadPkg) {
+ haveMod := make(map[module.Version]bool)
+ for _, m := range buildList {
+ haveMod[m] = true
+ }
+
+ modAddedBy = make(map[module.Version]*loadPkg)
+ for _, pkg := range pkgs {
+ if pkg.isTest() {
+ // If we are missing a test, we are also missing its non-test version, and
+ // we should only add the missing import once.
+ continue
}
- if ld.testRoots && isRoot || ld.testAll {
- test := &loadPkg{
- path: path,
- testOf: pkg,
+ if err, ok := pkg.err.(*ImportMissingError); ok && err.Module.Path != "" {
+ if err.newMissingVersion != "" {
+ base.Fatalf("go: %s: package provided by %s at latest version %s but not at required version %s", pkg.stackText(), err.Module.Path, err.Module.Version, err.newMissingVersion)
}
- pkg.test = test
+ fmt.Fprintf(os.Stderr, "go: found %s in %s %s\n", pkg.path, err.Module.Path, err.Module.Version)
+ if addedModuleFor[pkg.path] {
+ base.Fatalf("go: %s: looping trying to add package", pkg.stackText())
+ }
+ addedModuleFor[pkg.path] = true
+ if !haveMod[err.Module] {
+ haveMod[err.Module] = true
+ modAddedBy[err.Module] = pkg
+ buildList = append(buildList, err.Module)
+ }
+ continue
}
- if isRoot {
- ld.roots = append(ld.roots, pkg)
+ // Leave other errors for Import or load.Packages to report.
+ }
+ base.ExitIfErrors()
+
+ return modAddedBy
+}
+
+// pkg locates the *loadPkg for path, creating and queuing it for loading if
+// needed, and updates its state to reflect the given flags.
+//
+// The imports of the returned *loadPkg will be loaded asynchronously in the
+// ld.work queue, and its test (if requested) will also be populated once
+// imports have been resolved. When ld.work goes idle, all transitive imports of
+// the requested package (and its test, if requested) will have been loaded.
+func (ld *loader) pkg(path string, flags loadPkgFlags) *loadPkg {
+ if flags.has(pkgImportsLoaded) {
+ panic("internal error: (*loader).pkg called with pkgImportsLoaded flag set")
+ }
+
+ pkg := ld.pkgCache.Do(path, func() interface{} {
+ pkg := &loadPkg{
+ path: path,
}
- ld.work.Add(pkg)
+ ld.applyPkgFlags(pkg, flags)
+
+ ld.work.Add(func() { ld.load(pkg) })
return pkg
}).(*loadPkg)
+
+ ld.applyPkgFlags(pkg, flags)
+ return pkg
}
-// doPkg processes a package on the work queue.
-func (ld *loader) doPkg(item interface{}) {
- // TODO: what about replacements?
- pkg := item.(*loadPkg)
- var imports []string
- if pkg.testOf != nil {
- pkg.dir = pkg.testOf.dir
- pkg.mod = pkg.testOf.mod
- imports = pkg.testOf.testImports
- } else {
- if strings.Contains(pkg.path, "@") {
- // Leave for error during load.
- return
- }
- if build.IsLocalImport(pkg.path) || filepath.IsAbs(pkg.path) {
- // Leave for error during load.
- // (Module mode does not allow local imports.)
- return
- }
+// applyPkgFlags updates pkg.flags to set the given flags and propagate the
+// (transitive) effects of those flags, possibly loading or enqueueing further
+// packages as a result.
+func (ld *loader) applyPkgFlags(pkg *loadPkg, flags loadPkgFlags) {
+ if flags == 0 {
+ return
+ }
- // TODO(matloob): Handle TODO context. This needs to be threaded through Do.
- pkg.mod, pkg.dir, pkg.err = Import(context.TODO(), pkg.path)
- if pkg.dir == "" {
- return
+ if flags.has(pkgInAll) && ld.allPatternIsRoot && !pkg.isTest() {
+ // This package matches a root pattern by virtue of being in "all".
+ flags |= pkgIsRoot
+ }
+
+ old := pkg.flags.update(flags)
+ new := old | flags
+ if new == old || !new.has(pkgImportsLoaded) {
+ // We either didn't change the state of pkg, or we don't know anything about
+ // its dependencies yet. Either way, we can't usefully load its test or
+ // update its dependencies.
+ return
+ }
+
+ if !pkg.isTest() {
+ // Check whether we should add (or update the flags for) a test for pkg.
+ // ld.pkgTest is idempotent and extra invocations are inexpensive,
+ // so it's ok if we call it more than is strictly necessary.
+ wantTest := false
+ switch {
+ case ld.allPatternIsRoot && pkg.mod == Target:
+ // We are loading the "all" pattern, which includes packages imported by
+ // tests in the main module. This package is in the main module, so we
+ // need to identify the imports of its test even if LoadTests is not set.
+ //
+ // (We will filter out the extra tests explicitly in computePatternAll.)
+ wantTest = true
+
+ case ld.allPatternIsRoot && ld.allClosesOverTests && new.has(pkgInAll):
+ // This variant of the "all" pattern includes imports of tests of every
+ // package that is itself in "all", and pkg is in "all", so its test is
+ // also in "all" (as above).
+ wantTest = true
+
+ case LoadTests && new.has(pkgIsRoot):
+ // LoadTest explicitly requests tests of “the root packages”.
+ wantTest = true
}
- var testImports []string
- var err error
- imports, testImports, err = scanDir(pkg.dir, ld.tags)
- if err != nil {
- pkg.err = err
- return
+
+ if wantTest {
+ var testFlags loadPkgFlags
+ if pkg.mod == Target || (ld.allClosesOverTests && new.has(pkgInAll)) {
+ // Tests of packages in the main module are in "all", in the sense that
+ // they cause the packages they import to also be in "all". So are tests
+ // of packages in "all" if "all" closes over test dependencies.
+ testFlags |= pkgInAll
+ }
+ ld.pkgTest(pkg, testFlags)
}
- if pkg.test != nil {
- pkg.testImports = testImports
+ }
+
+ if new.has(pkgInAll) && !old.has(pkgInAll|pkgImportsLoaded) {
+ // We have just marked pkg with pkgInAll, or we have just loaded its
+ // imports, or both. Now is the time to propagate pkgInAll to the imports.
+ for _, dep := range pkg.imports {
+ ld.applyPkgFlags(dep, pkgInAll)
}
}
+}
+
+// load loads an individual package.
+func (ld *loader) load(pkg *loadPkg) {
+ if strings.Contains(pkg.path, "@") {
+ // Leave for error during load.
+ return
+ }
+ if build.IsLocalImport(pkg.path) || filepath.IsAbs(pkg.path) {
+ // Leave for error during load.
+ // (Module mode does not allow local imports.)
+ return
+ }
+
+ pkg.mod, pkg.dir, pkg.err = Import(context.TODO(), pkg.path)
+ if pkg.dir == "" {
+ return
+ }
+ if pkg.mod == Target {
+ // Go ahead and mark pkg as in "all". This provides the invariant that a
+ // package that is *only* imported by other packages in "all" is always
+ // marked as such before loading its imports.
+ //
+ // We don't actually rely on that invariant at the moment, but it may
+ // improve efficiency somewhat and makes the behavior a bit easier to reason
+ // about (by reducing churn on the flag bits of dependencies), and costs
+ // essentially nothing (these atomic flag ops are essentially free compared
+ // to scanning source code for imports).
+ ld.applyPkgFlags(pkg, pkgInAll)
+ }
+
+ imports, testImports, err := scanDir(pkg.dir, ld.tags)
+ if err != nil {
+ pkg.err = err
+ return
+ }
+
+ pkg.inStd = (search.IsStandardImportPath(pkg.path) && search.InDir(pkg.dir, cfg.GOROOTsrc) != "")
- inStd := (search.IsStandardImportPath(pkg.path) && search.InDir(pkg.dir, cfg.GOROOTsrc) != "")
+ pkg.imports = make([]*loadPkg, 0, len(imports))
+ var importFlags loadPkgFlags
+ if pkg.flags.has(pkgInAll) {
+ importFlags = pkgInAll
+ }
for _, path := range imports {
- if inStd {
+ if pkg.inStd {
+ // Imports from packages in "std" should resolve using GOROOT/src/vendor
+ // even when "std" is not the main module.
path = ld.stdVendor(pkg.path, path)
}
- pkg.imports = append(pkg.imports, ld.pkg(path, false))
+ pkg.imports = append(pkg.imports, ld.pkg(path, importFlags))
}
+ pkg.testImports = testImports
- // Now that pkg.dir, pkg.mod, pkg.testImports are set, we can queue pkg.test.
- // TODO: All that's left is creating new imports. Why not just do it now?
- if pkg.test != nil {
- ld.work.Add(pkg.test)
+ ld.applyPkgFlags(pkg, pkgImportsLoaded)
+}
+
+// pkgTest locates the test of pkg, creating it if needed, and updates its state
+// to reflect the given flags.
+//
+// pkgTest requires that the imports of pkg have already been loaded (flagged
+// with pkgImportsLoaded).
+func (ld *loader) pkgTest(pkg *loadPkg, testFlags loadPkgFlags) *loadPkg {
+ if pkg.isTest() {
+ panic("pkgTest called on a test package")
+ }
+
+ createdTest := false
+ pkg.testOnce.Do(func() {
+ pkg.test = &loadPkg{
+ path: pkg.path,
+ testOf: pkg,
+ mod: pkg.mod,
+ dir: pkg.dir,
+ err: pkg.err,
+ inStd: pkg.inStd,
+ }
+ ld.applyPkgFlags(pkg.test, testFlags)
+ createdTest = true
+ })
+
+ test := pkg.test
+ if createdTest {
+ test.imports = make([]*loadPkg, 0, len(pkg.testImports))
+ var importFlags loadPkgFlags
+ if test.flags.has(pkgInAll) {
+ importFlags = pkgInAll
+ }
+ for _, path := range pkg.testImports {
+ if pkg.inStd {
+ path = ld.stdVendor(test.path, path)
+ }
+ test.imports = append(test.imports, ld.pkg(path, importFlags))
+ }
+ pkg.testImports = nil
+ ld.applyPkgFlags(test, pkgImportsLoaded)
+ } else {
+ ld.applyPkgFlags(test, testFlags)
}
+
+ return test
}
// stdVendor returns the canonical import path for the package with the given
@@ -868,30 +1204,13 @@ func (ld *loader) stdVendor(parentPath, path string) string {
// computePatternAll returns the list of packages matching pattern "all",
// starting with a list of the import paths for the packages in the main module.
-func (ld *loader) computePatternAll(paths []string) []string {
- seen := make(map[*loadPkg]bool)
- var all []string
- var walk func(*loadPkg)
- walk = func(pkg *loadPkg) {
- if seen[pkg] {
- return
- }
- seen[pkg] = true
- if pkg.testOf == nil {
+func (ld *loader) computePatternAll() (all []string) {
+ for _, pkg := range ld.pkgs {
+ if pkg.flags.has(pkgInAll) && !pkg.isTest() {
all = append(all, pkg.path)
}
- for _, p := range pkg.imports {
- walk(p)
- }
- if p := pkg.test; p != nil {
- walk(p)
- }
- }
- for _, path := range paths {
- walk(ld.pkg(path, false))
}
sort.Strings(all)
-
return all
}
diff --git a/src/cmd/go/testdata/script/mod_notall.txt b/src/cmd/go/testdata/script/mod_notall.txt
index 72a02485a4..29ca6066fa 100644
--- a/src/cmd/go/testdata/script/mod_notall.txt
+++ b/src/cmd/go/testdata/script/mod_notall.txt
@@ -18,7 +18,7 @@ stdout '^x/otherdep$'
! stdout '^x/fromotherroottest$'
! stdout '^y/fromotherdeptest$'
-# TODO(#40799): cmp go.mod go.mod.orig
+cmp go.mod go.mod.orig
# With -deps -test, test dependencies of other roots should be included,
# but test dependencies of non-roots should not.
@@ -33,7 +33,7 @@ stdout '^x/otherdep$'
stdout '^x/fromotherroottest$'
! stdout '^y/fromotherdeptest$'
-# TODO(#40799): cmp go.mod go.mod.orig
+cmp go.mod go.mod.orig
-- m.go --
package m
--
cgit v1.3
From 26d27f96fec733fe09751b49b47282c9109fb8ad Mon Sep 17 00:00:00 2001
From: "Bryan C. Mills"
Date: Thu, 27 Aug 2020 16:34:59 -0400
Subject: cmd/go/internal/modload: remove (*loader).forceStdVendor
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
forceStdVendor was a special-case mechanism to allow Go contributors
to use vendored dependencies by default when working in GOROOT/src.
As of Go 1.14,¹ the 'go' command uses vendored dependencies by default
within all modules, so the 'std' and 'cmd' modules no longer need to
be special cases, and we can remove this special-case code.
¹ https://golang.org/doc/go1.14#vendor
Updates #33848
Updates #30241
Change-Id: Ib2fb5841c253113b17fa86a086ce85a22ac3d121
Reviewed-on: https://go-review.googlesource.com/c/go/+/251159
Run-TryBot: Bryan C. Mills
TryBot-Result: Gobot Gobot
Reviewed-by: Jay Conrod
Reviewed-by: Michael Matloob
---
src/cmd/go/internal/modload/load.go | 25 +++++------
src/cmd/go/testdata/script/mod_list_std.txt | 64 ++++++++++++++++++---------
src/cmd/go/testdata/script/mod_std_vendor.txt | 6 +--
3 files changed, 57 insertions(+), 38 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index 64ef60230e..8a3af534a5 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -709,8 +709,6 @@ func Lookup(parentPath string, parentIsStd bool, path string) (dir, realPath str
type loader struct {
loaderParams
- forceStdVendor bool // if true, load standard-library dependencies from the vendor subtree
-
work *par.Queue
// reset on each iteration
@@ -850,13 +848,6 @@ func loadFromRoots(params loaderParams) *loader {
work: par.NewQueue(runtime.GOMAXPROCS(0)),
}
- // Inside the "std" and "cmd" modules, we prefer to use the vendor directory
- // unless the command explicitly changes the module graph.
- // TODO(bcmills): Is this still needed now that we have automatic vendoring?
- if !targetInGorootSrc || (cfg.CmdName != "get" && !strings.HasPrefix(cfg.CmdName, "mod ")) {
- ld.forceStdVendor = true
- }
-
var err error
reqs := Reqs()
buildList, err = mvs.BuildList(Target, reqs)
@@ -1120,8 +1111,8 @@ func (ld *loader) load(pkg *loadPkg) {
}
for _, path := range imports {
if pkg.inStd {
- // Imports from packages in "std" should resolve using GOROOT/src/vendor
- // even when "std" is not the main module.
+ // Imports from packages in "std" and "cmd" should resolve using
+ // GOROOT/src/vendor even when "std" is not the main module.
path = ld.stdVendor(pkg.path, path)
}
pkg.imports = append(pkg.imports, ld.pkg(path, importFlags))
@@ -1185,13 +1176,21 @@ func (ld *loader) stdVendor(parentPath, path string) string {
}
if str.HasPathPrefix(parentPath, "cmd") {
- if ld.forceStdVendor || Target.Path != "cmd" {
+ if Target.Path != "cmd" {
vendorPath := pathpkg.Join("cmd", "vendor", path)
if _, err := os.Stat(filepath.Join(cfg.GOROOTsrc, filepath.FromSlash(vendorPath))); err == nil {
return vendorPath
}
}
- } else if ld.forceStdVendor || Target.Path != "std" {
+ } else if Target.Path != "std" || str.HasPathPrefix(parentPath, "vendor") {
+ // If we are outside of the 'std' module, resolve imports from within 'std'
+ // to the vendor directory.
+ //
+ // Do the same for importers beginning with the prefix 'vendor/' even if we
+ // are *inside* of the 'std' module: the 'vendor/' packages that resolve
+ // globally from GOROOT/src/vendor (and are listed as part of 'go list std')
+ // are distinct from the real module dependencies, and cannot import internal
+ // packages from the real module.
vendorPath := pathpkg.Join("vendor", path)
if _, err := os.Stat(filepath.Join(cfg.GOROOTsrc, filepath.FromSlash(vendorPath))); err == nil {
return vendorPath
diff --git a/src/cmd/go/testdata/script/mod_list_std.txt b/src/cmd/go/testdata/script/mod_list_std.txt
index 76a3b00d1c..baf7908ab9 100644
--- a/src/cmd/go/testdata/script/mod_list_std.txt
+++ b/src/cmd/go/testdata/script/mod_list_std.txt
@@ -6,8 +6,13 @@ env GOPROXY=off
# Outside of GOROOT, our vendored packages should be reported as part of the standard library.
go list -f '{{if .Standard}}{{.ImportPath}}{{end}}' std cmd
-stdout ^vendor/golang.org/x/net/http2/hpack
+stdout ^vendor/golang\.org/x/net/http2/hpack
stdout ^cmd/vendor/golang\.org/x/arch/x86/x86asm
+! stdout ^golang\.org/x/
+
+# The dependencies of those packages should also be vendored.
+go list -deps vendor/golang.org/x/crypto/chacha20
+stdout ^vendor/golang\.org/x/crypto/internal/subtle
# cmd/... should match the same packages it used to match in GOPATH mode.
go list cmd/...
@@ -23,40 +28,57 @@ stdout ^bytes$
! stdout ^builtin$
! stdout ^cmd/
! stdout ^vendor/
+! stdout ^golang\.org/x/
+
+# Vendored dependencies should appear with their 'vendor/' paths in std (they're
+# in GOROOT/src, but not in the 'std' module following the usual module-boundary
+# rules).
-# Within the std module, listing ./... should omit the 'std' prefix:
-# the package paths should be the same via ./... or the 'std' meta-pattern.
-# TODO(golang.org/issue/30241): Make that work.
-# Today, they are listed in 'std' but not './...'.
cd $GOROOT/src
-go list ./...
-! stdout ^vendor/golang.org/x # TODO: should be included, or should be omitted from 'std'.
-cp stdout $WORK/listdot.txt
go list std
-stdout ^vendor/golang.org/x # TODO: remove vendor/ prefix
-# TODO: cmp stdout $WORK/listdot.txt
+stdout ^vendor/golang.org/x/net/http2/hpack
+! stdout ^golang\.org/x
+
+# The dependencies of packages with an explicit 'vendor/' prefix should
+# still themselves resolve to vendored packages.
+go list -deps vendor/golang.org/x/crypto/chacha20
+stdout ^vendor/golang.org/x/crypto/internal/subtle
+! stdout ^golang\.org/x
+
+# Within the std module, the dependencies of the non-vendored packages within
+# std should appear to come from modules, but they should be loaded from the
+# vendor directory (just like ordinary vendored module dependencies).
go list all
-stdout ^vendor/golang.org/x # TODO: remove vendor/ prefix.
+stdout ^golang.org/x/
! stdout ^std/
+! stdout ^cmd/
+! stdout ^vendor/
+go list -deps -f '{{if not .Standard}}{{.ImportPath}}{{end}}' std
+! stdout ^vendor/golang.org/x/net/http2/hpack
+stdout ^golang.org/x/net/http2/hpack
-# Within the std module, the vendored dependencies of std should appear
-# to come from the actual modules.
-# TODO(golang.org/issue/30241): Make that work.
-# Today, they still have the vendor/ prefix.
-go list std
-stdout ^vendor/golang.org/x/net/http2/hpack # TODO
-! stdout ^golang.org/x/net/http2/hpack # TODO
+go list -f '{{.Dir}}' golang.org/x/net/http2/hpack
+stdout $GOROOT[/\\]src[/\\]vendor
-go list -deps -f '{{if not .Standard}}{{.ImportPath}}{{end}}' std
-# ! stdout ^vendor/golang.org/x/net/http2/hpack # TODO
-! stdout ^golang.org/x/net/http2/hpack # TODO
+# Within the std module, the packages within the module should omit the 'std/'
+# prefix (they retain their own identities), but should respect normal module
+# boundaries (vendored packages are not included in the module, even though they
+# are included in the 'std' pattern).
+
+go list ./...
+stdout ^bytes$
+! stdout ^builtin$
+! stdout ^cmd/
+! stdout ^vendor/
+! stdout ^golang\.org/x/
# Within std, the vendored dependencies of cmd should still appear to be part of cmd.
+
go list -f '{{if .Standard}}{{.ImportPath}}{{end}}' cmd
stdout ^cmd/vendor/golang\.org/x/arch/x86/x86asm
diff --git a/src/cmd/go/testdata/script/mod_std_vendor.txt b/src/cmd/go/testdata/script/mod_std_vendor.txt
index 5986cff594..fb954d74ed 100644
--- a/src/cmd/go/testdata/script/mod_std_vendor.txt
+++ b/src/cmd/go/testdata/script/mod_std_vendor.txt
@@ -37,12 +37,10 @@ stderr 'use of vendored package'
# When run within the 'std' module, 'go list -test' should report vendored
# transitive dependencies at their original module paths.
-# TODO(golang.org/issue/30241): Make that work.
-# Today, they're standard packages as long as they exist.
cd $GOROOT/src
go list -test -f '{{range .Deps}}{{.}}{{"\n"}}{{end}}' net/http
-stdout ^vendor/golang.org/x/net/http2/hpack # TODO: remove vendor/ prefix
-! stdout ^golang.org/x/net/http2/hpack
+stdout ^golang.org/x/net/http2/hpack
+! stdout ^vendor/golang.org/x/net/http2/hpack
-- go.mod --
module m
--
cgit v1.3
From 015a5a5c5c4b4ce4dce55601032b8e2f5fbcca9a Mon Sep 17 00:00:00 2001
From: "Bryan C. Mills"
Date: Fri, 28 Aug 2020 18:19:22 -0400
Subject: cmd/go/internal/modload: rework import resolution
modload.Import previously performed two otherwise-separable tasks:
1. Identify which module in the build list contains the requested
package.
2. If no such module exists, search available modules to try to find
the missing package.
This change splits those two tasks into two separate unexported
functions, and reports import-resolution errors by attaching them to
the package rather than emitting them directly to stderr. That allows
'list' to report the errors, but 'list -e' to ignore them.
With the two tasks now separate, it will be easier to avoid the
overhead of resolving missing packages during lazy loading if we
discover that some existing dependency needs to be promoted to the top
level (potentially altering the main module's selected versions, and
thus suppling packages that were previously missing).
For #36460
Updates #26909
Change-Id: I32bd853b266d7cd231d1f45f92b0650d95c4bcbd
Reviewed-on: https://go-review.googlesource.com/c/go/+/251445
Run-TryBot: Bryan C. Mills
TryBot-Result: Gobot Gobot
Reviewed-by: Jay Conrod
Reviewed-by: Michael Matloob
---
src/cmd/go/internal/list/list.go | 21 ++++++-
src/cmd/go/internal/modload/import.go | 71 ++++++++++++++--------
src/cmd/go/internal/modload/import_test.go | 44 +++++++++++---
src/cmd/go/internal/modload/load.go | 59 ++++++++++--------
src/cmd/go/testdata/script/list_bad_import.txt | 18 +++---
src/cmd/go/testdata/script/list_test_err.txt | 3 +
src/cmd/go/testdata/script/mod_list_bad_import.txt | 18 +++---
.../testdata/script/mod_missingpkg_prerelease.txt | 4 +-
8 files changed, 158 insertions(+), 80 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go
index 6d81c1cad1..65003dc883 100644
--- a/src/cmd/go/internal/list/list.go
+++ b/src/cmd/go/internal/list/list.go
@@ -545,7 +545,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
// Note that -deps is applied after -test,
// so that you only get descriptions of tests for the things named
// explicitly on the command line, not for all dependencies.
- pkgs = load.PackageList(pkgs)
+ pkgs = loadPackageList(pkgs)
}
// Do we need to run a build to gather information?
@@ -580,7 +580,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
if *listTest {
all := pkgs
if !*listDeps {
- all = load.PackageList(pkgs)
+ all = loadPackageList(pkgs)
}
// Update import paths to distinguish the real package p
// from p recompiled for q.test.
@@ -697,6 +697,23 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
}
}
+// loadPackageList is like load.PackageList, but prints error messages and exits
+// with nonzero status if listE is not set and any package in the expanded list
+// has errors.
+func loadPackageList(roots []*load.Package) []*load.Package {
+ pkgs := load.PackageList(roots)
+
+ if !*listE {
+ for _, pkg := range pkgs {
+ if pkg.Error != nil {
+ base.Errorf("%v", pkg.Error)
+ }
+ }
+ }
+
+ return pkgs
+}
+
// TrackingWriter tracks the last byte written on every write so
// we can avoid printing a newline if one was already written or
// if there is no output at all.
diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go
index 6459e716b7..e04d66c5b1 100644
--- a/src/cmd/go/internal/modload/import.go
+++ b/src/cmd/go/internal/modload/import.go
@@ -26,6 +26,8 @@ import (
"golang.org/x/mod/semver"
)
+var errImportMissing = errors.New("import missing")
+
type ImportMissingError struct {
Path string
Module module.Version
@@ -48,6 +50,11 @@ func (e *ImportMissingError) Error() string {
}
return "cannot find module providing package " + e.Path
}
+
+ if e.newMissingVersion != "" {
+ return fmt.Sprintf("package %s provided by %s at latest version %s but not at required version %s", e.Path, e.Module.Path, e.Module.Version, e.newMissingVersion)
+ }
+
return fmt.Sprintf("missing module for import: %s@%s provides %s", e.Module.Path, e.Module.Version, e.Path)
}
@@ -100,18 +107,20 @@ func (e *AmbiguousImportError) Error() string {
var _ load.ImportPathError = &AmbiguousImportError{}
-// Import finds the module and directory in the build list
-// containing the package with the given import path.
-// The answer must be unique: Import returns an error
-// if multiple modules attempt to provide the same package.
-// Import can return a module with an empty m.Path, for packages in the standard library.
-// Import can return an empty directory string, for fake packages like "C" and "unsafe".
+// importFromBuildList finds the module and directory in the build list
+// containing the package with the given import path. The answer must be unique:
+// importFromBuildList returns an error if multiple modules attempt to provide
+// the same package.
+//
+// importFromBuildList can return a module with an empty m.Path, for packages in
+// the standard library.
+//
+// importFromBuildList can return an empty directory string, for fake packages
+// like "C" and "unsafe".
//
// If the package cannot be found in the current build list,
-// Import returns an ImportMissingError as the error.
-// If Import can identify a module that could be added to supply the package,
-// the ImportMissingError records that module.
-func Import(ctx context.Context, path string) (m module.Version, dir string, err error) {
+// importFromBuildList returns errImportMissing as the error.
+func importFromBuildList(ctx context.Context, path string) (m module.Version, dir string, err error) {
if strings.Contains(path, "@") {
return module.Version{}, "", fmt.Errorf("import path should not have @version")
}
@@ -190,8 +199,14 @@ func Import(ctx context.Context, path string) (m module.Version, dir string, err
return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: dirs, Modules: mods}
}
- // Look up module containing the package, for addition to the build list.
- // Goal is to determine the module, download it to dir, and return m, dir, ErrMissing.
+ return module.Version{}, "", errImportMissing
+}
+
+// queryImport attempts to locate a module that can be added to the current
+// build list to provide the package with the given import path.
+func queryImport(ctx context.Context, path string) (module.Version, error) {
+ pathIsStd := search.IsStandardImportPath(path)
+
if cfg.BuildMod == "readonly" {
var queryErr error
if !pathIsStd {
@@ -201,10 +216,10 @@ func Import(ctx context.Context, path string) (m module.Version, dir string, err
queryErr = fmt.Errorf("import lookup disabled by -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason)
}
}
- return module.Version{}, "", &ImportMissingError{Path: path, QueryErr: queryErr}
+ return module.Version{}, &ImportMissingError{Path: path, QueryErr: queryErr}
}
if modRoot == "" && !allowMissingModuleImports {
- return module.Version{}, "", &ImportMissingError{
+ return module.Version{}, &ImportMissingError{
Path: path,
QueryErr: errors.New("working directory is not part of a module"),
}
@@ -226,7 +241,7 @@ func Import(ctx context.Context, path string) (m module.Version, dir string, err
}
}
- mods = make([]module.Version, 0, len(latest))
+ mods := make([]module.Version, 0, len(latest))
for p, v := range latest {
// If the replacement didn't specify a version, synthesize a
// pseudo-version with an appropriate major version and a timestamp below
@@ -252,19 +267,19 @@ func Import(ctx context.Context, path string) (m module.Version, dir string, err
root, isLocal, err := fetch(ctx, m)
if err != nil {
// Report fetch error as above.
- return module.Version{}, "", err
+ return module.Version{}, err
}
if _, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
- return m, "", err
+ return m, err
} else if ok {
- return m, "", &ImportMissingError{Path: path, Module: m}
+ return m, nil
}
}
if len(mods) > 0 && module.CheckPath(path) != nil {
// The package path is not valid to fetch remotely,
// so it can only exist if in a replaced module,
// and we know from the above loop that it is not.
- return module.Version{}, "", &PackageNotInModuleError{
+ return module.Version{}, &PackageNotInModuleError{
Mod: mods[0],
Query: "latest",
Pattern: path,
@@ -281,7 +296,7 @@ func Import(ctx context.Context, path string) (m module.Version, dir string, err
// QueryPackage cannot possibly find a module containing this package.
//
// Instead of trying QueryPackage, report an ImportMissingError immediately.
- return module.Version{}, "", &ImportMissingError{Path: path}
+ return module.Version{}, &ImportMissingError{Path: path}
}
fmt.Fprintf(os.Stderr, "go: finding module for package %s\n", path)
@@ -291,12 +306,13 @@ func Import(ctx context.Context, path string) (m module.Version, dir string, err
if errors.Is(err, os.ErrNotExist) {
// Return "cannot find module providing package […]" instead of whatever
// low-level error QueryPackage produced.
- return module.Version{}, "", &ImportMissingError{Path: path, QueryErr: err}
+ return module.Version{}, &ImportMissingError{Path: path, QueryErr: err}
} else {
- return module.Version{}, "", err
+ return module.Version{}, err
}
}
- m = candidates[0].Mod
+
+ m := candidates[0].Mod
newMissingVersion := ""
for _, c := range candidates {
cm := c.Mod
@@ -310,13 +326,20 @@ func Import(ctx context.Context, path string) (m module.Version, dir string, err
// version (e.g., v1.0.0) of a module, but we have a newer version
// of the same module in the build list (e.g., v1.0.1-beta), and
// the package is not present there.
+ //
+ // TODO(#41113): This is probably incorrect when there are multiple
+ // candidates, such as when a nested module is split out but only one
+ // half of the split is tagged.
m = cm
newMissingVersion = bm.Version
break
}
}
}
- return m, "", &ImportMissingError{Path: path, Module: m, newMissingVersion: newMissingVersion}
+ if newMissingVersion != "" {
+ return m, &ImportMissingError{Path: path, Module: m, newMissingVersion: newMissingVersion}
+ }
+ return m, nil
}
// maybeInModule reports whether, syntactically,
diff --git a/src/cmd/go/internal/modload/import_test.go b/src/cmd/go/internal/modload/import_test.go
index 47ce89a084..22d5b82e21 100644
--- a/src/cmd/go/internal/modload/import_test.go
+++ b/src/cmd/go/internal/modload/import_test.go
@@ -10,15 +10,20 @@ import (
"regexp"
"strings"
"testing"
+
+ "golang.org/x/mod/module"
)
var importTests = []struct {
path string
+ m module.Version
err string
}{
{
path: "golang.org/x/net/context",
- err: "missing module for import: golang.org/x/net@.* provides golang.org/x/net/context",
+ m: module.Version{
+ Path: "golang.org/x/net",
+ },
},
{
path: "golang.org/x/net",
@@ -26,15 +31,23 @@ var importTests = []struct {
},
{
path: "golang.org/x/text",
- err: "missing module for import: golang.org/x/text@.* provides golang.org/x/text",
+ m: module.Version{
+ Path: "golang.org/x/text",
+ },
},
{
path: "github.com/rsc/quote/buggy",
- err: "missing module for import: github.com/rsc/quote@v1.5.2 provides github.com/rsc/quote/buggy",
+ m: module.Version{
+ Path: "github.com/rsc/quote",
+ Version: "v1.5.2",
+ },
},
{
path: "github.com/rsc/quote",
- err: "missing module for import: github.com/rsc/quote@v1.5.2 provides github.com/rsc/quote",
+ m: module.Version{
+ Path: "github.com/rsc/quote",
+ Version: "v1.5.2",
+ },
},
{
path: "golang.org/x/foo/bar",
@@ -42,7 +55,7 @@ var importTests = []struct {
},
}
-func TestImport(t *testing.T) {
+func TestQueryImport(t *testing.T) {
testenv.MustHaveExternalNetwork(t)
testenv.MustHaveExecPath(t, "git")
defer func(old bool) {
@@ -55,12 +68,23 @@ func TestImport(t *testing.T) {
for _, tt := range importTests {
t.Run(strings.ReplaceAll(tt.path, "/", "_"), func(t *testing.T) {
// Note that there is no build list, so Import should always fail.
- m, dir, err := Import(ctx, tt.path)
- if err == nil {
- t.Fatalf("Import(%q) = %v, %v, nil; expected error", tt.path, m, dir)
+ m, err := queryImport(ctx, tt.path)
+
+ if tt.err == "" {
+ if err != nil {
+ t.Fatalf("queryImport(_, %q): %v", tt.path, err)
+ }
+ } else {
+ if err == nil {
+ t.Fatalf("queryImport(_, %q) = %v, nil; expected error", tt.path, m)
+ }
+ if !regexp.MustCompile(tt.err).MatchString(err.Error()) {
+ t.Fatalf("queryImport(_, %q): error %q, want error matching %#q", tt.path, err, tt.err)
+ }
}
- if !regexp.MustCompile(tt.err).MatchString(err.Error()) {
- t.Fatalf("Import(%q): error %q, want error matching %#q", tt.path, err, tt.err)
+
+ if m.Path != tt.m.Path || (tt.m.Version != "" && m.Version != tt.m.Version) {
+ t.Errorf("queryImport(_, %q) = %v, _; want %v", tt.path, m, tt.m)
}
})
}
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index 8a3af534a5..2096dfb636 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -881,7 +881,7 @@ func loadFromRoots(params loaderParams) *loader {
ld.buildStacks()
- modAddedBy := resolveMissingImports(addedModuleFor, ld.pkgs)
+ modAddedBy := ld.resolveMissingImports(addedModuleFor)
if len(modAddedBy) == 0 {
break
}
@@ -937,38 +937,45 @@ func loadFromRoots(params loaderParams) *loader {
// The newly-resolved packages are added to the addedModuleFor map, and
// resolveMissingImports returns a map from each newly-added module version to
// the first package for which that module was added.
-func resolveMissingImports(addedModuleFor map[string]bool, pkgs []*loadPkg) (modAddedBy map[module.Version]*loadPkg) {
- haveMod := make(map[module.Version]bool)
- for _, m := range buildList {
- haveMod[m] = true
- }
-
- modAddedBy = make(map[module.Version]*loadPkg)
- for _, pkg := range pkgs {
+func (ld *loader) resolveMissingImports(addedModuleFor map[string]bool) (modAddedBy map[module.Version]*loadPkg) {
+ var needPkgs []*loadPkg
+ for _, pkg := range ld.pkgs {
if pkg.isTest() {
// If we are missing a test, we are also missing its non-test version, and
// we should only add the missing import once.
continue
}
- if err, ok := pkg.err.(*ImportMissingError); ok && err.Module.Path != "" {
- if err.newMissingVersion != "" {
- base.Fatalf("go: %s: package provided by %s at latest version %s but not at required version %s", pkg.stackText(), err.Module.Path, err.Module.Version, err.newMissingVersion)
- }
- fmt.Fprintf(os.Stderr, "go: found %s in %s %s\n", pkg.path, err.Module.Path, err.Module.Version)
- if addedModuleFor[pkg.path] {
- base.Fatalf("go: %s: looping trying to add package", pkg.stackText())
- }
- addedModuleFor[pkg.path] = true
- if !haveMod[err.Module] {
- haveMod[err.Module] = true
- modAddedBy[err.Module] = pkg
- buildList = append(buildList, err.Module)
- }
+ if pkg.err != errImportMissing {
+ // Leave other errors for Import or load.Packages to report.
continue
}
- // Leave other errors for Import or load.Packages to report.
+
+ needPkgs = append(needPkgs, pkg)
+
+ pkg := pkg
+ ld.work.Add(func() {
+ pkg.mod, pkg.err = queryImport(context.TODO(), pkg.path)
+ })
+ }
+ <-ld.work.Idle()
+
+ modAddedBy = map[module.Version]*loadPkg{}
+ for _, pkg := range needPkgs {
+ if pkg.err != nil {
+ continue
+ }
+
+ fmt.Fprintf(os.Stderr, "go: found %s in %s %s\n", pkg.path, pkg.mod.Path, pkg.mod.Version)
+ if addedModuleFor[pkg.path] {
+ // TODO(bcmills): This should only be an error if pkg.mod is the same
+ // version we already tried to add previously.
+ base.Fatalf("go: %s: looping trying to add package", pkg.stackText())
+ }
+ if modAddedBy[pkg.mod] == nil {
+ modAddedBy[pkg.mod] = pkg
+ buildList = append(buildList, pkg.mod)
+ }
}
- base.ExitIfErrors()
return modAddedBy
}
@@ -1079,7 +1086,7 @@ func (ld *loader) load(pkg *loadPkg) {
return
}
- pkg.mod, pkg.dir, pkg.err = Import(context.TODO(), pkg.path)
+ pkg.mod, pkg.dir, pkg.err = importFromBuildList(context.TODO(), pkg.path)
if pkg.dir == "" {
return
}
diff --git a/src/cmd/go/testdata/script/list_bad_import.txt b/src/cmd/go/testdata/script/list_bad_import.txt
index b8f9d586f3..dbec35069c 100644
--- a/src/cmd/go/testdata/script/list_bad_import.txt
+++ b/src/cmd/go/testdata/script/list_bad_import.txt
@@ -15,10 +15,11 @@ stdout 'incomplete'
stdout 'bad dep: .*example.com[/\\]notfound'
# Listing with -deps should also fail.
-# BUG: Today, it does not.
-# ! go list -deps example.com/direct
-# stderr example.com[/\\]notfound
-go list -deps example.com/direct
+! go list -deps example.com/direct
+stderr example.com[/\\]notfound
+
+# But -e -deps should succeed.
+go list -e -deps example.com/direct
stdout example.com/notfound
@@ -31,10 +32,11 @@ stdout incomplete
stdout 'bad dep: .*example.com[/\\]notfound'
# Again, -deps should fail.
-# BUG: Again, it does not.
-# ! go list -deps example.com/indirect
-# stderr example.com[/\\]notfound
-go list -deps example.com/indirect
+! go list -deps example.com/indirect
+stderr example.com[/\\]notfound
+
+# But -deps -e should succeed.
+go list -e -deps example.com/indirect
stdout example.com/notfound
diff --git a/src/cmd/go/testdata/script/list_test_err.txt b/src/cmd/go/testdata/script/list_test_err.txt
index a174b5e9ad..c6f1ecf400 100644
--- a/src/cmd/go/testdata/script/list_test_err.txt
+++ b/src/cmd/go/testdata/script/list_test_err.txt
@@ -22,6 +22,9 @@ go list -e -test -deps -f '{{.ImportPath}} {{.Error | printf "%q"}}' syntaxerr
stdout 'pkgdep '
stdout 'testdep_a '
stdout 'testdep_b '
+stdout 'syntaxerr '
+stdout 'syntaxerr \[syntaxerr.test\] '
+stdout 'syntaxerr_test \[syntaxerr.test\] '
stdout 'syntaxerr\.test "[^"]*expected declaration'
! stderr 'expected declaration'
diff --git a/src/cmd/go/testdata/script/mod_list_bad_import.txt b/src/cmd/go/testdata/script/mod_list_bad_import.txt
index 8a66e0b72a..b3e2fff67d 100644
--- a/src/cmd/go/testdata/script/mod_list_bad_import.txt
+++ b/src/cmd/go/testdata/script/mod_list_bad_import.txt
@@ -12,10 +12,11 @@ stdout 'incomplete'
stdout 'bad dep: .*example.com/notfound'
# Listing with -deps should also fail.
-# BUG: Today, it does not.
-# ! go list -deps example.com/direct
-# stderr example.com/notfound
-go list -deps example.com/direct
+! go list -deps example.com/direct
+stderr example.com/notfound
+
+# But -e -deps should succeed.
+go list -e -deps example.com/direct
stdout example.com/notfound
@@ -28,10 +29,11 @@ stdout incomplete
stdout 'bad dep: .*example.com/notfound'
# Again, -deps should fail.
-# BUG: Again, it does not.
-# ! go list -deps example.com/indirect
-# stderr example.com/notfound
-go list -deps example.com/indirect
+! go list -deps example.com/indirect
+stderr example.com/notfound
+
+# But -e -deps should succeed.
+go list -e -deps example.com/indirect
stdout example.com/notfound
diff --git a/src/cmd/go/testdata/script/mod_missingpkg_prerelease.txt b/src/cmd/go/testdata/script/mod_missingpkg_prerelease.txt
index 319ff85587..1ba8d3d22a 100644
--- a/src/cmd/go/testdata/script/mod_missingpkg_prerelease.txt
+++ b/src/cmd/go/testdata/script/mod_missingpkg_prerelease.txt
@@ -1,7 +1,7 @@
env GO111MODULE=on
-! go list use.go
-stderr 'example.com/missingpkg/deprecated: package provided by example.com/missingpkg at latest version v1.0.0 but not at required version v1.0.1-beta'
+! go list -deps use.go
+stderr '^use.go:4:2: package example.com/missingpkg/deprecated provided by example.com/missingpkg at latest version v1.0.0 but not at required version v1.0.1-beta$'
-- go.mod --
module m
--
cgit v1.3
From cd91ab5d9601c975286f1ac83cd289e34aa117f8 Mon Sep 17 00:00:00 2001
From: "Bryan C. Mills"
Date: Fri, 28 Aug 2020 21:32:05 -0400
Subject: cmd/go/internal/modload: fix spurious import resolution error
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Due to a bug in CL 173017, if QueryPackages found multiple candidates
for the given package and *at least* one of those candidates was not
available to add, we would reject *all* such candidates — even those
that were still viable.
Now, we return the first viable candidate, and only return an error if
*no* candidate is viable given the current build list.
Fixes #41113
Change-Id: Idb2e77244be7c0f5dd511efb142c3059925d7336
Reviewed-on: https://go-review.googlesource.com/c/go/+/251446
Run-TryBot: Bryan C. Mills
TryBot-Result: Gobot Gobot
Reviewed-by: Jay Conrod
Reviewed-by: Michael Matloob
---
src/cmd/go/internal/modload/import.go | 26 ++++++++++----------
...xample.com_split-incompatible_subpkg_v0.1.0.txt | 14 +++++++++++
....com_split-incompatible_v2.0.0+incompatible.txt | 10 ++++++++
..._split-incompatible_v2.1.0-pre+incompatible.txt | 10 ++++++++
.../go/testdata/script/mod_import_issue41113.txt | 28 ++++++++++++++++++++++
5 files changed, 76 insertions(+), 12 deletions(-)
create mode 100644 src/cmd/go/testdata/mod/example.com_split-incompatible_subpkg_v0.1.0.txt
create mode 100644 src/cmd/go/testdata/mod/example.com_split-incompatible_v2.0.0+incompatible.txt
create mode 100644 src/cmd/go/testdata/mod/example.com_split-incompatible_v2.1.0-pre+incompatible.txt
create mode 100644 src/cmd/go/testdata/script/mod_import_issue41113.txt
(limited to 'src/cmd')
diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go
index e04d66c5b1..c625184b8b 100644
--- a/src/cmd/go/internal/modload/import.go
+++ b/src/cmd/go/internal/modload/import.go
@@ -312,10 +312,10 @@ func queryImport(ctx context.Context, path string) (module.Version, error) {
}
}
- m := candidates[0].Mod
- newMissingVersion := ""
- for _, c := range candidates {
+ candidate0MissingVersion := ""
+ for i, c := range candidates {
cm := c.Mod
+ canAdd := true
for _, bm := range buildList {
if bm.Path == cm.Path && semver.Compare(bm.Version, cm.Version) > 0 {
// QueryPackage proposed that we add module cm to provide the package,
@@ -326,20 +326,22 @@ func queryImport(ctx context.Context, path string) (module.Version, error) {
// version (e.g., v1.0.0) of a module, but we have a newer version
// of the same module in the build list (e.g., v1.0.1-beta), and
// the package is not present there.
- //
- // TODO(#41113): This is probably incorrect when there are multiple
- // candidates, such as when a nested module is split out but only one
- // half of the split is tagged.
- m = cm
- newMissingVersion = bm.Version
+ canAdd = false
+ if i == 0 {
+ candidate0MissingVersion = bm.Version
+ }
break
}
}
+ if canAdd {
+ return cm, nil
+ }
}
- if newMissingVersion != "" {
- return m, &ImportMissingError{Path: path, Module: m, newMissingVersion: newMissingVersion}
+ return module.Version{}, &ImportMissingError{
+ Path: path,
+ Module: candidates[0].Mod,
+ newMissingVersion: candidate0MissingVersion,
}
- return m, nil
}
// maybeInModule reports whether, syntactically,
diff --git a/src/cmd/go/testdata/mod/example.com_split-incompatible_subpkg_v0.1.0.txt b/src/cmd/go/testdata/mod/example.com_split-incompatible_subpkg_v0.1.0.txt
new file mode 100644
index 0000000000..8f9e49176c
--- /dev/null
+++ b/src/cmd/go/testdata/mod/example.com_split-incompatible_subpkg_v0.1.0.txt
@@ -0,0 +1,14 @@
+Written by hand.
+Test case for getting a package that has been moved to a nested module,
+with a +incompatible verison (and thus no go.mod file) at the root module.
+
+-- .mod --
+module example.com/split-incompatible/subpkg
+-- .info --
+{"Version": "v0.1.0"}
+-- go.mod --
+module example.com/split-incompatible/subpkg
+
+go 1.16
+-- subpkg.go --
+package subpkg
diff --git a/src/cmd/go/testdata/mod/example.com_split-incompatible_v2.0.0+incompatible.txt b/src/cmd/go/testdata/mod/example.com_split-incompatible_v2.0.0+incompatible.txt
new file mode 100644
index 0000000000..35c3f27710
--- /dev/null
+++ b/src/cmd/go/testdata/mod/example.com_split-incompatible_v2.0.0+incompatible.txt
@@ -0,0 +1,10 @@
+Written by hand.
+Test case for getting a package that has been moved to a nested module,
+with a +incompatible verison (and thus no go.mod file) at the root module.
+
+-- .mod --
+module example.com/split-incompatible
+-- .info --
+{"Version": "v2.0.0+incompatible"}
+-- subpkg/subpkg.go --
+package subpkg
diff --git a/src/cmd/go/testdata/mod/example.com_split-incompatible_v2.1.0-pre+incompatible.txt b/src/cmd/go/testdata/mod/example.com_split-incompatible_v2.1.0-pre+incompatible.txt
new file mode 100644
index 0000000000..917fc0f559
--- /dev/null
+++ b/src/cmd/go/testdata/mod/example.com_split-incompatible_v2.1.0-pre+incompatible.txt
@@ -0,0 +1,10 @@
+Written by hand.
+Test case for getting a package that has been moved to a nested module,
+with a +incompatible verison (and thus no go.mod file) at the root module.
+
+-- .mod --
+module example.com/split-incompatible
+-- .info --
+{"Version": "v2.1.0-pre+incompatible"}
+-- README.txt --
+subpkg has moved to module example.com/split-incompatible/subpkg
diff --git a/src/cmd/go/testdata/script/mod_import_issue41113.txt b/src/cmd/go/testdata/script/mod_import_issue41113.txt
new file mode 100644
index 0000000000..e98ac63d48
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_import_issue41113.txt
@@ -0,0 +1,28 @@
+# Regression test for https://golang.org/issue/41113.
+#
+# When resolving a missing import path, the inability to add the package from
+# one module path should not interfere with adding a nested path.
+
+# Initially, our module depends on split-incompatible v2.1.0-pre+incompatible,
+# from which an imported package has been removed (and relocated to the nested
+# split-incompatible/subpkg module). modload.QueryPackage will suggest
+# split-incompatible v2.0.0+incompatible, which we cannot use (because it would
+# be an implicit downgrade), and split-incompatible/subpkg v0.1.0, which we
+# *should* use.
+
+go mod tidy
+
+go list -m all
+stdout '^example.com/split-incompatible/subpkg v0\.1\.0$'
+! stdout '^example.com/split-incompatible .*'
+
+-- go.mod --
+module golang.org/issue/41113
+
+go 1.16
+
+require example.com/split-incompatible v2.1.0-pre+incompatible
+-- x.go --
+package issue41113
+
+import _ "example.com/split-incompatible/subpkg"
--
cgit v1.3
From b4944ef310ed43fad53c6128344e4bed2b346c88 Mon Sep 17 00:00:00 2001
From: Tzu-Chiao Yeh
Date: Sun, 6 Sep 2020 09:43:34 +0800
Subject: cmd: update golang.org/x/tools to v0.0.0-20200901153117-6e59e24738da
Includes the latest fix on vet to warn unused context.WithValue result.
Fixes #41149
Change-Id: I06c204f40ef12b0f62f59b1bbdf1fe06ccd6565d
Reviewed-on: https://go-review.googlesource.com/c/go/+/252941
Run-TryBot: Emmanuel Odeke
TryBot-Result: Gobot Gobot
Reviewed-by: Bryan C. Mills
---
src/cmd/go.mod | 2 +-
src/cmd/go.sum | 15 +-
.../go/analysis/passes/structtag/structtag.go | 6 +-
.../go/analysis/passes/unmarshal/unmarshal.go | 7 +-
.../analysis/passes/unusedresult/unusedresult.go | 2 +-
.../x/tools/internal/analysisinternal/analysis.go | 343 +++++++++++++++++-
.../golang.org/x/tools/internal/lsp/fuzzy/input.go | 168 +++++++++
.../x/tools/internal/lsp/fuzzy/matcher.go | 398 +++++++++++++++++++++
src/cmd/vendor/modules.txt | 3 +-
9 files changed, 912 insertions(+), 32 deletions(-)
create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/input.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/matcher.go
(limited to 'src/cmd')
diff --git a/src/cmd/go.mod b/src/cmd/go.mod
index 68ce1705e4..0952dbb84c 100644
--- a/src/cmd/go.mod
+++ b/src/cmd/go.mod
@@ -9,6 +9,6 @@ require (
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 // indirect
- golang.org/x/tools v0.0.0-20200616133436-c1934b75d054
+ golang.org/x/tools v0.0.0-20200901153117-6e59e24738da
golang.org/x/xerrors v0.0.0-20200806184451-1a77d5e9f316 // indirect
)
diff --git a/src/cmd/go.sum b/src/cmd/go.sum
index cb64a5d475..adbc5a96ac 100644
--- a/src/cmd/go.sum
+++ b/src/cmd/go.sum
@@ -6,33 +6,34 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200414190113-039b1ae3a340 h1:S1+yTUaFPXuDZnPDbO+TrDFIjPzQraYH8/CwSlu9Fac=
github.com/ianlancetaylor/demangle v0.0.0-20200414190113-039b1ae3a340/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/arch v0.0.0-20200511175325-f7c78586839d h1:YvwchuJby5xEAPdBGmdAVSiVME50C+RJfJJwJJsGEV8=
golang.org/x/arch v0.0.0-20200511175325-f7c78586839d/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449 h1:xUIPaMhvROX9dhPvRCenIJtU78+lbEenGbgqB5hfHCQ=
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20200616133436-c1934b75d054 h1:HHeAlu5H9b71C+Fx0K+1dGgVFN1DM1/wz4aoGOA5qS8=
-golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200901153117-6e59e24738da h1:8nFbt74voFOsM+Hb5XtF+1SNbbf3dzikH5osZO1hyyo=
+golang.org/x/tools v0.0.0-20200901153117-6e59e24738da/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200806184451-1a77d5e9f316 h1:Jhw4VC65LaKnpq9FvcK+a8ZzrFm3D+UygvMMrhkOw70=
golang.org/x/xerrors v0.0.0-20200806184451-1a77d5e9f316/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go
index e09160379f..f0b15051c5 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go
@@ -116,7 +116,11 @@ func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, s
}
for _, enc := range [...]string{"json", "xml"} {
- if reflect.StructTag(tag).Get(enc) != "" {
+ switch reflect.StructTag(tag).Get(enc) {
+ // Ignore warning if the field not exported and the tag is marked as
+ // ignored.
+ case "", "-":
+ default:
pass.Reportf(field.Pos(), "struct field %s has %s tag but is not exported", field.Name(), enc)
return
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go
index f9cc993cbb..92b37caff9 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go
@@ -30,7 +30,7 @@ var Analyzer = &analysis.Analyzer{
func run(pass *analysis.Pass) (interface{}, error) {
switch pass.Pkg.Path() {
- case "encoding/gob", "encoding/json", "encoding/xml":
+ case "encoding/gob", "encoding/json", "encoding/xml", "encoding/asn1":
// These packages know how to use their own APIs.
// Sometimes they are testing what happens to incorrect programs.
return nil, nil
@@ -53,9 +53,10 @@ func run(pass *analysis.Pass) (interface{}, error) {
recv := fn.Type().(*types.Signature).Recv()
if fn.Name() == "Unmarshal" && recv == nil {
// "encoding/json".Unmarshal
- // "encoding/xml".Unmarshal
+ // "encoding/xml".Unmarshal
+ // "encoding/asn1".Unmarshal
switch fn.Pkg().Path() {
- case "encoding/json", "encoding/xml":
+ case "encoding/json", "encoding/xml", "encoding/asn1":
argidx = 1 // func([]byte, interface{})
}
} else if fn.Name() == "Decode" && recv != nil {
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go
index 76d4ab2382..bececee7e9 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go
@@ -44,7 +44,7 @@ var funcs, stringMethods stringSetFlag
func init() {
// TODO(adonovan): provide a comment syntax to allow users to
// add their functions to this set using facts.
- funcs.Set("errors.New,fmt.Errorf,fmt.Sprintf,fmt.Sprint,sort.Reverse")
+ funcs.Set("errors.New,fmt.Errorf,fmt.Sprintf,fmt.Sprint,sort.Reverse,context.WithValue,context.WithCancel,context.WithDeadline,context.WithTimeout")
Analyzer.Flags.Var(&funcs, "funcs",
"comma-separated list of functions whose results must be used")
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go b/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go
index 26586810c7..01f6e829f7 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go
@@ -14,6 +14,12 @@ import (
"strings"
"golang.org/x/tools/go/ast/astutil"
+ "golang.org/x/tools/internal/lsp/fuzzy"
+)
+
+var (
+ GetTypeErrors func(p interface{}) []types.Error
+ SetTypeErrors func(p interface{}, errors []types.Error)
)
func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos {
@@ -45,32 +51,34 @@ func ZeroValue(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.T
default:
panic("unknown basic type")
}
- case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice:
+ case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice, *types.Array:
return ast.NewIdent("nil")
case *types.Struct:
- texpr := typeExpr(fset, f, pkg, typ) // typ because we want the name here.
+ texpr := TypeExpr(fset, f, pkg, typ) // typ because we want the name here.
if texpr == nil {
return nil
}
return &ast.CompositeLit{
Type: texpr,
}
- case *types.Array:
- texpr := typeExpr(fset, f, pkg, u.Elem())
- if texpr == nil {
- return nil
- }
- return &ast.CompositeLit{
- Type: &ast.ArrayType{
- Elt: texpr,
- Len: &ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%v", u.Len())},
- },
- }
}
return nil
}
-func typeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
+// IsZeroValue checks whether the given expression is a 'zero value' (as determined by output of
+// analysisinternal.ZeroValue)
+func IsZeroValue(expr ast.Expr) bool {
+ switch e := expr.(type) {
+ case *ast.BasicLit:
+ return e.Value == "0" || e.Value == `""`
+ case *ast.Ident:
+ return e.Name == "nil" || e.Name == "false"
+ default:
+ return false
+ }
+}
+
+func TypeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
switch t := typ.(type) {
case *types.Basic:
switch t.Kind() {
@@ -79,7 +87,96 @@ func typeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Ty
default:
return ast.NewIdent(t.Name())
}
+ case *types.Pointer:
+ x := TypeExpr(fset, f, pkg, t.Elem())
+ if x == nil {
+ return nil
+ }
+ return &ast.UnaryExpr{
+ Op: token.MUL,
+ X: x,
+ }
+ case *types.Array:
+ elt := TypeExpr(fset, f, pkg, t.Elem())
+ if elt == nil {
+ return nil
+ }
+ return &ast.ArrayType{
+ Len: &ast.BasicLit{
+ Kind: token.INT,
+ Value: fmt.Sprintf("%d", t.Len()),
+ },
+ Elt: elt,
+ }
+ case *types.Slice:
+ elt := TypeExpr(fset, f, pkg, t.Elem())
+ if elt == nil {
+ return nil
+ }
+ return &ast.ArrayType{
+ Elt: elt,
+ }
+ case *types.Map:
+ key := TypeExpr(fset, f, pkg, t.Key())
+ value := TypeExpr(fset, f, pkg, t.Elem())
+ if key == nil || value == nil {
+ return nil
+ }
+ return &ast.MapType{
+ Key: key,
+ Value: value,
+ }
+ case *types.Chan:
+ dir := ast.ChanDir(t.Dir())
+ if t.Dir() == types.SendRecv {
+ dir = ast.SEND | ast.RECV
+ }
+ value := TypeExpr(fset, f, pkg, t.Elem())
+ if value == nil {
+ return nil
+ }
+ return &ast.ChanType{
+ Dir: dir,
+ Value: value,
+ }
+ case *types.Signature:
+ var params []*ast.Field
+ for i := 0; i < t.Params().Len(); i++ {
+ p := TypeExpr(fset, f, pkg, t.Params().At(i).Type())
+ if p == nil {
+ return nil
+ }
+ params = append(params, &ast.Field{
+ Type: p,
+ Names: []*ast.Ident{
+ {
+ Name: t.Params().At(i).Name(),
+ },
+ },
+ })
+ }
+ var returns []*ast.Field
+ for i := 0; i < t.Results().Len(); i++ {
+ r := TypeExpr(fset, f, pkg, t.Results().At(i).Type())
+ if r == nil {
+ return nil
+ }
+ returns = append(returns, &ast.Field{
+ Type: r,
+ })
+ }
+ return &ast.FuncType{
+ Params: &ast.FieldList{
+ List: params,
+ },
+ Results: &ast.FieldList{
+ List: returns,
+ },
+ }
case *types.Named:
+ if t.Obj().Pkg() == nil {
+ return ast.NewIdent(t.Obj().Name())
+ }
if t.Obj().Pkg() == pkg {
return ast.NewIdent(t.Obj().Name())
}
@@ -101,14 +198,15 @@ func typeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Ty
X: ast.NewIdent(pkgName),
Sel: ast.NewIdent(t.Obj().Name()),
}
+ case *types.Struct:
+ return ast.NewIdent(t.String())
+ case *types.Interface:
+ return ast.NewIdent(t.String())
default:
- return nil // TODO: anonymous structs, but who does that
+ return nil
}
}
-var GetTypeErrors = func(p interface{}) []types.Error { return nil }
-var SetTypeErrors = func(p interface{}, errors []types.Error) {}
-
type TypeErrorPass string
const (
@@ -116,3 +214,212 @@ const (
NoResultValues TypeErrorPass = "noresultvalues"
UndeclaredName TypeErrorPass = "undeclaredname"
)
+
+// StmtToInsertVarBefore returns the ast.Stmt before which we can safely insert a new variable.
+// Some examples:
+//
+// Basic Example:
+// z := 1
+// y := z + x
+// If x is undeclared, then this function would return `y := z + x`, so that we
+// can insert `x := ` on the line before `y := z + x`.
+//
+// If stmt example:
+// if z == 1 {
+// } else if z == y {}
+// If y is undeclared, then this function would return `if z == 1 {`, because we cannot
+// insert a statement between an if and an else if statement. As a result, we need to find
+// the top of the if chain to insert `y := ` before.
+func StmtToInsertVarBefore(path []ast.Node) ast.Stmt {
+ enclosingIndex := -1
+ for i, p := range path {
+ if _, ok := p.(ast.Stmt); ok {
+ enclosingIndex = i
+ break
+ }
+ }
+ if enclosingIndex == -1 {
+ return nil
+ }
+ enclosingStmt := path[enclosingIndex]
+ switch enclosingStmt.(type) {
+ case *ast.IfStmt:
+ // The enclosingStmt is inside of the if declaration,
+ // We need to check if we are in an else-if stmt and
+ // get the base if statement.
+ return baseIfStmt(path, enclosingIndex)
+ case *ast.CaseClause:
+ // Get the enclosing switch stmt if the enclosingStmt is
+ // inside of the case statement.
+ for i := enclosingIndex + 1; i < len(path); i++ {
+ if node, ok := path[i].(*ast.SwitchStmt); ok {
+ return node
+ } else if node, ok := path[i].(*ast.TypeSwitchStmt); ok {
+ return node
+ }
+ }
+ }
+ if len(path) <= enclosingIndex+1 {
+ return enclosingStmt.(ast.Stmt)
+ }
+ // Check if the enclosing statement is inside another node.
+ switch expr := path[enclosingIndex+1].(type) {
+ case *ast.IfStmt:
+ // Get the base if statement.
+ return baseIfStmt(path, enclosingIndex+1)
+ case *ast.ForStmt:
+ if expr.Init == enclosingStmt || expr.Post == enclosingStmt {
+ return expr
+ }
+ }
+ return enclosingStmt.(ast.Stmt)
+}
+
+// baseIfStmt walks up the if/else-if chain until we get to
+// the top of the current if chain.
+func baseIfStmt(path []ast.Node, index int) ast.Stmt {
+ stmt := path[index]
+ for i := index + 1; i < len(path); i++ {
+ if node, ok := path[i].(*ast.IfStmt); ok && node.Else == stmt {
+ stmt = node
+ continue
+ }
+ break
+ }
+ return stmt.(ast.Stmt)
+}
+
+// WalkASTWithParent walks the AST rooted at n. The semantics are
+// similar to ast.Inspect except it does not call f(nil).
+func WalkASTWithParent(n ast.Node, f func(n ast.Node, parent ast.Node) bool) {
+ var ancestors []ast.Node
+ ast.Inspect(n, func(n ast.Node) (recurse bool) {
+ if n == nil {
+ ancestors = ancestors[:len(ancestors)-1]
+ return false
+ }
+
+ var parent ast.Node
+ if len(ancestors) > 0 {
+ parent = ancestors[len(ancestors)-1]
+ }
+ ancestors = append(ancestors, n)
+ return f(n, parent)
+ })
+}
+
+// FindMatchingIdents finds all identifiers in 'node' that match any of the given types.
+// 'pos' represents the position at which the identifiers may be inserted. 'pos' must be within
+// the scope of each of identifier we select. Otherwise, we will insert a variable at 'pos' that
+// is unrecognized.
+func FindMatchingIdents(typs []types.Type, node ast.Node, pos token.Pos, info *types.Info, pkg *types.Package) map[types.Type][]*ast.Ident {
+ matches := map[types.Type][]*ast.Ident{}
+ // Initialize matches to contain the variable types we are searching for.
+ for _, typ := range typs {
+ if typ == nil {
+ continue
+ }
+ matches[typ] = []*ast.Ident{}
+ }
+ seen := map[types.Object]struct{}{}
+ ast.Inspect(node, func(n ast.Node) bool {
+ if n == nil {
+ return false
+ }
+ // Prevent circular definitions. If 'pos' is within an assignment statement, do not
+ // allow any identifiers in that assignment statement to be selected. Otherwise,
+ // we could do the following, where 'x' satisfies the type of 'f0':
+ //
+ // x := fakeStruct{f0: x}
+ //
+ assignment, ok := n.(*ast.AssignStmt)
+ if ok && pos > assignment.Pos() && pos <= assignment.End() {
+ return false
+ }
+ if n.End() > pos {
+ return n.Pos() <= pos
+ }
+ ident, ok := n.(*ast.Ident)
+ if !ok || ident.Name == "_" {
+ return true
+ }
+ obj := info.Defs[ident]
+ if obj == nil || obj.Type() == nil {
+ return true
+ }
+ if _, ok := obj.(*types.TypeName); ok {
+ return true
+ }
+ // Prevent duplicates in matches' values.
+ if _, ok = seen[obj]; ok {
+ return true
+ }
+ seen[obj] = struct{}{}
+ // Find the scope for the given position. Then, check whether the object
+ // exists within the scope.
+ innerScope := pkg.Scope().Innermost(pos)
+ if innerScope == nil {
+ return true
+ }
+ _, foundObj := innerScope.LookupParent(ident.Name, pos)
+ if foundObj != obj {
+ return true
+ }
+ // The object must match one of the types that we are searching for.
+ if idents, ok := matches[obj.Type()]; ok {
+ matches[obj.Type()] = append(idents, ast.NewIdent(ident.Name))
+ }
+ // If the object type does not exactly match any of the target types, greedily
+ // find the first target type that the object type can satisfy.
+ for typ := range matches {
+ if obj.Type() == typ {
+ continue
+ }
+ if equivalentTypes(obj.Type(), typ) {
+ matches[typ] = append(matches[typ], ast.NewIdent(ident.Name))
+ }
+ }
+ return true
+ })
+ return matches
+}
+
+func equivalentTypes(want, got types.Type) bool {
+ if want == got || types.Identical(want, got) {
+ return true
+ }
+ // Code segment to help check for untyped equality from (golang/go#32146).
+ if rhs, ok := want.(*types.Basic); ok && rhs.Info()&types.IsUntyped > 0 {
+ if lhs, ok := got.Underlying().(*types.Basic); ok {
+ return rhs.Info()&types.IsConstType == lhs.Info()&types.IsConstType
+ }
+ }
+ return types.AssignableTo(want, got)
+}
+
+// FindBestMatch employs fuzzy matching to evaluate the similarity of each given identifier to the
+// given pattern. We return the identifier whose name is most similar to the pattern.
+func FindBestMatch(pattern string, idents []*ast.Ident) ast.Expr {
+ fuzz := fuzzy.NewMatcher(pattern)
+ var bestFuzz ast.Expr
+ highScore := float32(0) // minimum score is 0 (no match)
+ for _, ident := range idents {
+ // TODO: Improve scoring algorithm.
+ score := fuzz.Score(ident.Name)
+ if score > highScore {
+ highScore = score
+ bestFuzz = ident
+ } else if score == 0 {
+ // Order matters in the fuzzy matching algorithm. If we find no match
+ // when matching the target to the identifier, try matching the identifier
+ // to the target.
+ revFuzz := fuzzy.NewMatcher(ident.Name)
+ revScore := revFuzz.Score(pattern)
+ if revScore > highScore {
+ highScore = revScore
+ bestFuzz = ident
+ }
+ }
+ }
+ return bestFuzz
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/input.go b/src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/input.go
new file mode 100644
index 0000000000..ac377035ec
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/input.go
@@ -0,0 +1,168 @@
+// 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.
+
+package fuzzy
+
+import (
+ "unicode"
+)
+
+// RuneRole specifies the role of a rune in the context of an input.
+type RuneRole byte
+
+const (
+ // RNone specifies a rune without any role in the input (i.e., whitespace/non-ASCII).
+ RNone RuneRole = iota
+ // RSep specifies a rune with the role of segment separator.
+ RSep
+ // RTail specifies a rune which is a lower-case tail in a word in the input.
+ RTail
+ // RUCTail specifies a rune which is an upper-case tail in a word in the input.
+ RUCTail
+ // RHead specifies a rune which is the first character in a word in the input.
+ RHead
+)
+
+// RuneRoles detects the roles of each byte rune in an input string and stores it in the output
+// slice. The rune role depends on the input type. Stops when it parsed all the runes in the string
+// or when it filled the output. If output is nil, then it gets created.
+func RuneRoles(str string, reuse []RuneRole) []RuneRole {
+ var output []RuneRole
+ if cap(reuse) < len(str) {
+ output = make([]RuneRole, 0, len(str))
+ } else {
+ output = reuse[:0]
+ }
+
+ prev, prev2 := rtNone, rtNone
+ for i := 0; i < len(str); i++ {
+ r := rune(str[i])
+
+ role := RNone
+
+ curr := rtLower
+ if str[i] <= unicode.MaxASCII {
+ curr = runeType(rt[str[i]] - '0')
+ }
+
+ if curr == rtLower {
+ if prev == rtNone || prev == rtPunct {
+ role = RHead
+ } else {
+ role = RTail
+ }
+ } else if curr == rtUpper {
+ role = RHead
+
+ if prev == rtUpper {
+ // This and previous characters are both upper case.
+
+ if i+1 == len(str) {
+ // This is last character, previous was also uppercase -> this is UCTail
+ // i.e., (current char is C): aBC / BC / ABC
+ role = RUCTail
+ }
+ }
+ } else if curr == rtPunct {
+ switch r {
+ case '.', ':':
+ role = RSep
+ }
+ }
+ if curr != rtLower {
+ if i > 1 && output[i-1] == RHead && prev2 == rtUpper && (output[i-2] == RHead || output[i-2] == RUCTail) {
+ // The previous two characters were uppercase. The current one is not a lower case, so the
+ // previous one can't be a HEAD. Make it a UCTail.
+ // i.e., (last char is current char - B must be a UCTail): ABC / ZABC / AB.
+ output[i-1] = RUCTail
+ }
+ }
+
+ output = append(output, role)
+ prev2 = prev
+ prev = curr
+ }
+ return output
+}
+
+type runeType byte
+
+const (
+ rtNone runeType = iota
+ rtPunct
+ rtLower
+ rtUpper
+)
+
+const rt = "00000000000000000000000000000000000000000000001122222222221000000333333333333333333333333330000002222222222222222222222222200000"
+
+// LastSegment returns the substring representing the last segment from the input, where each
+// byte has an associated RuneRole in the roles slice. This makes sense only for inputs of Symbol
+// or Filename type.
+func LastSegment(input string, roles []RuneRole) string {
+ // Exclude ending separators.
+ end := len(input) - 1
+ for end >= 0 && roles[end] == RSep {
+ end--
+ }
+ if end < 0 {
+ return ""
+ }
+
+ start := end - 1
+ for start >= 0 && roles[start] != RSep {
+ start--
+ }
+
+ return input[start+1 : end+1]
+}
+
+// ToLower transforms the input string to lower case, which is stored in the output byte slice.
+// The lower casing considers only ASCII values - non ASCII values are left unmodified.
+// Stops when parsed all input or when it filled the output slice. If output is nil, then it gets
+// created.
+func ToLower(input string, reuse []byte) []byte {
+ output := reuse
+ if cap(reuse) < len(input) {
+ output = make([]byte, len(input))
+ }
+
+ for i := 0; i < len(input); i++ {
+ r := rune(input[i])
+ if r <= unicode.MaxASCII {
+ if 'A' <= r && r <= 'Z' {
+ r += 'a' - 'A'
+ }
+ }
+ output[i] = byte(r)
+ }
+ return output[:len(input)]
+}
+
+// WordConsumer defines a consumer for a word delimited by the [start,end) byte offsets in an input
+// (start is inclusive, end is exclusive).
+type WordConsumer func(start, end int)
+
+// Words find word delimiters in an input based on its bytes' mappings to rune roles. The offset
+// delimiters for each word are fed to the provided consumer function.
+func Words(roles []RuneRole, consume WordConsumer) {
+ var wordStart int
+ for i, r := range roles {
+ switch r {
+ case RUCTail, RTail:
+ case RHead, RNone, RSep:
+ if i != wordStart {
+ consume(wordStart, i)
+ }
+ wordStart = i
+ if r != RHead {
+ // Skip this character.
+ wordStart = i + 1
+ }
+ }
+ }
+ if wordStart != len(roles) {
+ consume(wordStart, len(roles))
+ }
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/matcher.go b/src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/matcher.go
new file mode 100644
index 0000000000..16a643097d
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/lsp/fuzzy/matcher.go
@@ -0,0 +1,398 @@
+// 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.
+
+// Package fuzzy implements a fuzzy matching algorithm.
+package fuzzy
+
+import (
+ "bytes"
+ "fmt"
+)
+
+const (
+ // MaxInputSize is the maximum size of the input scored against the fuzzy matcher. Longer inputs
+ // will be truncated to this size.
+ MaxInputSize = 127
+ // MaxPatternSize is the maximum size of the pattern used to construct the fuzzy matcher. Longer
+ // inputs are truncated to this size.
+ MaxPatternSize = 63
+)
+
+type scoreVal int
+
+func (s scoreVal) val() int {
+ return int(s) >> 1
+}
+
+func (s scoreVal) prevK() int {
+ return int(s) & 1
+}
+
+func score(val int, prevK int /*0 or 1*/) scoreVal {
+ return scoreVal(val<<1 + prevK)
+}
+
+// Matcher implements a fuzzy matching algorithm for scoring candidates against a pattern.
+// The matcher does not support parallel usage.
+type Matcher struct {
+ pattern string
+ patternLower []byte // lower-case version of the pattern
+ patternShort []byte // first characters of the pattern
+ caseSensitive bool // set if the pattern is mix-cased
+
+ patternRoles []RuneRole // the role of each character in the pattern
+ roles []RuneRole // the role of each character in the tested string
+
+ scores [MaxInputSize + 1][MaxPatternSize + 1][2]scoreVal
+
+ scoreScale float32
+
+ lastCandidateLen int // in bytes
+ lastCandidateMatched bool
+
+ // Here we save the last candidate in lower-case. This is basically a byte slice we reuse for
+ // performance reasons, so the slice is not reallocated for every candidate.
+ lowerBuf [MaxInputSize]byte
+ rolesBuf [MaxInputSize]RuneRole
+}
+
+func (m *Matcher) bestK(i, j int) int {
+ if m.scores[i][j][0].val() < m.scores[i][j][1].val() {
+ return 1
+ }
+ return 0
+}
+
+// NewMatcher returns a new fuzzy matcher for scoring candidates against the provided pattern.
+func NewMatcher(pattern string) *Matcher {
+ if len(pattern) > MaxPatternSize {
+ pattern = pattern[:MaxPatternSize]
+ }
+
+ m := &Matcher{
+ pattern: pattern,
+ patternLower: ToLower(pattern, nil),
+ }
+
+ for i, c := range m.patternLower {
+ if pattern[i] != c {
+ m.caseSensitive = true
+ break
+ }
+ }
+
+ if len(pattern) > 3 {
+ m.patternShort = m.patternLower[:3]
+ } else {
+ m.patternShort = m.patternLower
+ }
+
+ m.patternRoles = RuneRoles(pattern, nil)
+
+ if len(pattern) > 0 {
+ maxCharScore := 4
+ m.scoreScale = 1 / float32(maxCharScore*len(pattern))
+ }
+
+ return m
+}
+
+// Score returns the score returned by matching the candidate to the pattern.
+// This is not designed for parallel use. Multiple candidates must be scored sequentially.
+// Returns a score between 0 and 1 (0 - no match, 1 - perfect match).
+func (m *Matcher) Score(candidate string) float32 {
+ if len(candidate) > MaxInputSize {
+ candidate = candidate[:MaxInputSize]
+ }
+ lower := ToLower(candidate, m.lowerBuf[:])
+ m.lastCandidateLen = len(candidate)
+
+ if len(m.pattern) == 0 {
+ // Empty patterns perfectly match candidates.
+ return 1
+ }
+
+ if m.match(candidate, lower) {
+ sc := m.computeScore(candidate, lower)
+ if sc > minScore/2 && !m.poorMatch() {
+ m.lastCandidateMatched = true
+ if len(m.pattern) == len(candidate) {
+ // Perfect match.
+ return 1
+ }
+
+ if sc < 0 {
+ sc = 0
+ }
+ normalizedScore := float32(sc) * m.scoreScale
+ if normalizedScore > 1 {
+ normalizedScore = 1
+ }
+
+ return normalizedScore
+ }
+ }
+
+ m.lastCandidateMatched = false
+ return 0
+}
+
+const minScore = -10000
+
+// MatchedRanges returns matches ranges for the last scored string as a flattened array of
+// [begin, end) byte offset pairs.
+func (m *Matcher) MatchedRanges() []int {
+ if len(m.pattern) == 0 || !m.lastCandidateMatched {
+ return nil
+ }
+ i, j := m.lastCandidateLen, len(m.pattern)
+ if m.scores[i][j][0].val() < minScore/2 && m.scores[i][j][1].val() < minScore/2 {
+ return nil
+ }
+
+ var ret []int
+ k := m.bestK(i, j)
+ for i > 0 {
+ take := (k == 1)
+ k = m.scores[i][j][k].prevK()
+ if take {
+ if len(ret) == 0 || ret[len(ret)-1] != i {
+ ret = append(ret, i)
+ ret = append(ret, i-1)
+ } else {
+ ret[len(ret)-1] = i - 1
+ }
+ j--
+ }
+ i--
+ }
+ // Reverse slice.
+ for i := 0; i < len(ret)/2; i++ {
+ ret[i], ret[len(ret)-1-i] = ret[len(ret)-1-i], ret[i]
+ }
+ return ret
+}
+
+func (m *Matcher) match(candidate string, candidateLower []byte) bool {
+ i, j := 0, 0
+ for ; i < len(candidateLower) && j < len(m.patternLower); i++ {
+ if candidateLower[i] == m.patternLower[j] {
+ j++
+ }
+ }
+ if j != len(m.patternLower) {
+ return false
+ }
+
+ // The input passes the simple test against pattern, so it is time to classify its characters.
+ // Character roles are used below to find the last segment.
+ m.roles = RuneRoles(candidate, m.rolesBuf[:])
+
+ return true
+}
+
+func (m *Matcher) computeScore(candidate string, candidateLower []byte) int {
+ pattLen, candLen := len(m.pattern), len(candidate)
+
+ for j := 0; j <= len(m.pattern); j++ {
+ m.scores[0][j][0] = minScore << 1
+ m.scores[0][j][1] = minScore << 1
+ }
+ m.scores[0][0][0] = score(0, 0) // Start with 0.
+
+ segmentsLeft, lastSegStart := 1, 0
+ for i := 0; i < candLen; i++ {
+ if m.roles[i] == RSep {
+ segmentsLeft++
+ lastSegStart = i + 1
+ }
+ }
+
+ // A per-character bonus for a consecutive match.
+ consecutiveBonus := 2
+ wordIdx := 0 // Word count within segment.
+ for i := 1; i <= candLen; i++ {
+
+ role := m.roles[i-1]
+ isHead := role == RHead
+
+ if isHead {
+ wordIdx++
+ } else if role == RSep && segmentsLeft > 1 {
+ wordIdx = 0
+ segmentsLeft--
+ }
+
+ var skipPenalty int
+ if i == 1 || (i-1) == lastSegStart {
+ // Skipping the start of first or last segment.
+ skipPenalty++
+ }
+
+ for j := 0; j <= pattLen; j++ {
+ // By default, we don't have a match. Fill in the skip data.
+ m.scores[i][j][1] = minScore << 1
+
+ // Compute the skip score.
+ k := 0
+ if m.scores[i-1][j][0].val() < m.scores[i-1][j][1].val() {
+ k = 1
+ }
+
+ skipScore := m.scores[i-1][j][k].val()
+ // Do not penalize missing characters after the last matched segment.
+ if j != pattLen {
+ skipScore -= skipPenalty
+ }
+ m.scores[i][j][0] = score(skipScore, k)
+
+ if j == 0 || candidateLower[i-1] != m.patternLower[j-1] {
+ // Not a match.
+ continue
+ }
+ pRole := m.patternRoles[j-1]
+
+ if role == RTail && pRole == RHead {
+ if j > 1 {
+ // Not a match: a head in the pattern matches a tail character in the candidate.
+ continue
+ }
+ // Special treatment for the first character of the pattern. We allow
+ // matches in the middle of a word if they are long enough, at least
+ // min(3, pattern.length) characters.
+ if !bytes.HasPrefix(candidateLower[i-1:], m.patternShort) {
+ continue
+ }
+ }
+
+ // Compute the char score.
+ var charScore int
+ // Bonus 1: the char is in the candidate's last segment.
+ if segmentsLeft <= 1 {
+ charScore++
+ }
+ // Bonus 2: Case match or a Head in the pattern aligns with one in the word.
+ // Single-case patterns lack segmentation signals and we assume any character
+ // can be a head of a segment.
+ if candidate[i-1] == m.pattern[j-1] || role == RHead && (!m.caseSensitive || pRole == RHead) {
+ charScore++
+ }
+
+ // Penalty 1: pattern char is Head, candidate char is Tail.
+ if role == RTail && pRole == RHead {
+ charScore--
+ }
+ // Penalty 2: first pattern character matched in the middle of a word.
+ if j == 1 && role == RTail {
+ charScore -= 4
+ }
+
+ // Third dimension encodes whether there is a gap between the previous match and the current
+ // one.
+ for k := 0; k < 2; k++ {
+ sc := m.scores[i-1][j-1][k].val() + charScore
+
+ isConsecutive := k == 1 || i-1 == 0 || i-1 == lastSegStart
+ if isConsecutive {
+ // Bonus 3: a consecutive match. First character match also gets a bonus to
+ // ensure prefix final match score normalizes to 1.0.
+ // Logically, this is a part of charScore, but we have to compute it here because it
+ // only applies for consecutive matches (k == 1).
+ sc += consecutiveBonus
+ }
+ if k == 0 {
+ // Penalty 3: Matching inside a segment (and previous char wasn't matched). Penalize for the lack
+ // of alignment.
+ if role == RTail || role == RUCTail {
+ sc -= 3
+ }
+ }
+
+ if sc > m.scores[i][j][1].val() {
+ m.scores[i][j][1] = score(sc, k)
+ }
+ }
+ }
+ }
+
+ result := m.scores[len(candidate)][len(m.pattern)][m.bestK(len(candidate), len(m.pattern))].val()
+
+ return result
+}
+
+// ScoreTable returns the score table computed for the provided candidate. Used only for debugging.
+func (m *Matcher) ScoreTable(candidate string) string {
+ var buf bytes.Buffer
+
+ var line1, line2, separator bytes.Buffer
+ line1.WriteString("\t")
+ line2.WriteString("\t")
+ for j := 0; j < len(m.pattern); j++ {
+ line1.WriteString(fmt.Sprintf("%c\t\t", m.pattern[j]))
+ separator.WriteString("----------------")
+ }
+
+ buf.WriteString(line1.String())
+ buf.WriteString("\n")
+ buf.WriteString(separator.String())
+ buf.WriteString("\n")
+
+ for i := 1; i <= len(candidate); i++ {
+ line1.Reset()
+ line2.Reset()
+
+ line1.WriteString(fmt.Sprintf("%c\t", candidate[i-1]))
+ line2.WriteString("\t")
+
+ for j := 1; j <= len(m.pattern); j++ {
+ line1.WriteString(fmt.Sprintf("M%6d(%c)\t", m.scores[i][j][0].val(), dir(m.scores[i][j][0].prevK())))
+ line2.WriteString(fmt.Sprintf("H%6d(%c)\t", m.scores[i][j][1].val(), dir(m.scores[i][j][1].prevK())))
+ }
+ buf.WriteString(line1.String())
+ buf.WriteString("\n")
+ buf.WriteString(line2.String())
+ buf.WriteString("\n")
+ buf.WriteString(separator.String())
+ buf.WriteString("\n")
+ }
+
+ return buf.String()
+}
+
+func dir(prevK int) rune {
+ if prevK == 0 {
+ return 'M'
+ }
+ return 'H'
+}
+
+func (m *Matcher) poorMatch() bool {
+ if len(m.pattern) < 2 {
+ return false
+ }
+
+ i, j := m.lastCandidateLen, len(m.pattern)
+ k := m.bestK(i, j)
+
+ var counter, len int
+ for i > 0 {
+ take := (k == 1)
+ k = m.scores[i][j][k].prevK()
+ if take {
+ len++
+ if k == 0 && len < 3 && m.roles[i-1] == RTail {
+ // Short match in the middle of a word
+ counter++
+ if counter > 1 {
+ return true
+ }
+ }
+ j--
+ } else {
+ len = 0
+ }
+ i--
+ }
+ return false
+}
diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt
index c0c008e038..c827365400 100644
--- a/src/cmd/vendor/modules.txt
+++ b/src/cmd/vendor/modules.txt
@@ -45,7 +45,7 @@ golang.org/x/mod/zip
golang.org/x/sys/internal/unsafeheader
golang.org/x/sys/unix
golang.org/x/sys/windows
-# golang.org/x/tools v0.0.0-20200616133436-c1934b75d054
+# golang.org/x/tools v0.0.0-20200901153117-6e59e24738da
## explicit
golang.org/x/tools/go/analysis
golang.org/x/tools/go/analysis/internal/analysisflags
@@ -84,6 +84,7 @@ golang.org/x/tools/go/cfg
golang.org/x/tools/go/types/objectpath
golang.org/x/tools/go/types/typeutil
golang.org/x/tools/internal/analysisinternal
+golang.org/x/tools/internal/lsp/fuzzy
# golang.org/x/xerrors v0.0.0-20200806184451-1a77d5e9f316
## explicit
golang.org/x/xerrors
--
cgit v1.3
From d27ebc7b8630993269c36e7728a7f30543ffa048 Mon Sep 17 00:00:00 2001
From: "Bryan C. Mills"
Date: Fri, 13 Mar 2020 16:32:37 -0400
Subject: cmd/go/internal/modload: implement the "all" pattern for lazy loading
The new semantics of the "all" package pattern can be implemented
without actually changing module loading per se. This change
implements those semantics, so that the change can be decoupled from
the changes to the module requirement graph.
For #36460
Change-Id: I0ee8b17afa8b728dc470a42a540fcc01764a4442
Reviewed-on: https://go-review.googlesource.com/c/go/+/240623
Run-TryBot: Bryan C. Mills
TryBot-Result: Gobot Gobot
Reviewed-by: Jay Conrod
Reviewed-by: Michael Matloob
---
doc/go1.16.html | 14 ++++-
src/cmd/go/internal/modcmd/tidy.go | 12 ++++
src/cmd/go/internal/modcmd/why.go | 2 +
src/cmd/go/internal/modload/load.go | 16 ++++--
src/cmd/go/internal/modload/modfile.go | 22 ++++++++
src/cmd/go/testdata/script/mod_all.txt | 100 +++++++++++++++++++++++++++++++--
6 files changed, 154 insertions(+), 12 deletions(-)
(limited to 'src/cmd')
diff --git a/doc/go1.16.html b/doc/go1.16.html
index 0ffaecc5a9..95e63d0d5a 100644
--- a/doc/go1.16.html
+++ b/doc/go1.16.html
@@ -52,7 +52,7 @@ Do not send CLs removing the interior tags from such phrases.
TODO: write and link to tutorial or blog post
-
+
When using go test, a test that
calls os.Exit(0) during execution of a test function
will now be considered to fail.
@@ -62,6 +62,18 @@ Do not send CLs removing the interior tags from such phrases.
that is still considered to be a passing test.
+The all pattern
+
+
+ When the main module's go.mod file
+ declares go 1.16 or higher, the all
+ package pattern now matches only those packages that are transitively imported
+ by a package or test found in the main module. (Packages imported by tests
+ of packages imported by the main module are no longer included.) This is
+ the same set of packages retained
+ by go mod vendor since Go 1.11.
+
+
TODO
diff --git a/src/cmd/go/internal/modcmd/tidy.go b/src/cmd/go/internal/modcmd/tidy.go
index c7c53d7c0c..4dcb62e02f 100644
--- a/src/cmd/go/internal/modcmd/tidy.go
+++ b/src/cmd/go/internal/modcmd/tidy.go
@@ -40,6 +40,18 @@ func runTidy(ctx context.Context, cmd *base.Command, args []string) {
base.Fatalf("go mod tidy: no arguments allowed")
}
+ // Tidy aims to make 'go test' reproducible for any package in 'all', so we
+ // need to include test dependencies. For modules that specify go 1.15 or
+ // earlier this is a no-op (because 'all' saturates transitive test
+ // dependencies).
+ //
+ // However, with lazy loading (go 1.16+) 'all' includes only the packages that
+ // are transitively imported by the main module, not the test dependencies of
+ // those packages. In order to make 'go test' reproducible for the packages
+ // that are in 'all' but outside of the main module, we must explicitly
+ // request that their test dependencies be included.
+ modload.LoadTests = true
+
modload.LoadALL(ctx)
modload.TidyBuildList()
modload.TrimGoSum()
diff --git a/src/cmd/go/internal/modcmd/why.go b/src/cmd/go/internal/modcmd/why.go
index b16887d318..30b15fc153 100644
--- a/src/cmd/go/internal/modcmd/why.go
+++ b/src/cmd/go/internal/modcmd/why.go
@@ -65,6 +65,8 @@ func runWhy(ctx context.Context, cmd *base.Command, args []string) {
loadALL := modload.LoadALL
if *whyVendor {
loadALL = modload.LoadVendor
+ } else {
+ modload.LoadTests = true
}
if *whyM {
listU := false
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index 2096dfb636..9cedc219b6 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -231,7 +231,7 @@ func ImportPathsQuiet(ctx context.Context, patterns []string, tags map[string]bo
loaded = loadFromRoots(loaderParams{
tags: tags,
allPatternIsRoot: allPatternIsRoot,
- allClosesOverTests: true, // until lazy loading in Go 1.16+
+ allClosesOverTests: index.allPatternClosesOverTests(),
listRoots: func() (roots []string) {
updateMatches(nil)
@@ -450,7 +450,7 @@ func ImportFromFiles(ctx context.Context, gofiles []string) {
roots = append(roots, testImports...)
return roots
},
- allClosesOverTests: true, // until lazy loading.
+ allClosesOverTests: index.allPatternClosesOverTests(),
})
WriteGoMod()
}
@@ -501,7 +501,7 @@ func ReloadBuildList() []module.Version {
loaded = loadFromRoots(loaderParams{
tags: imports.Tags(),
listRoots: func() []string { return nil },
- allClosesOverTests: true, // until lazy loading, but doesn't matter because the root list is empty.
+ allClosesOverTests: index.allPatternClosesOverTests(), // but doesn't matter because the root list is empty.
})
return buildList
}
@@ -512,9 +512,13 @@ func ReloadBuildList() []module.Version {
// It adds modules to the build list as needed to satisfy new imports.
// This set is useful for deciding whether a particular import is needed
// anywhere in a module.
+//
+// In modules that specify "go 1.16" or higher, ALL follows only one layer of
+// test dependencies. In "go 1.15" or lower, ALL follows the imports of tests of
+// dependencies of tests.
func LoadALL(ctx context.Context) []string {
InitMod(ctx)
- return loadAll(ctx, true)
+ return loadAll(ctx, index.allPatternClosesOverTests())
}
// LoadVendor is like LoadALL but only follows test dependencies
@@ -523,7 +527,9 @@ func LoadALL(ctx context.Context) []string {
// This set is useful for identifying the which packages to include in a vendor directory.
func LoadVendor(ctx context.Context) []string {
InitMod(ctx)
- return loadAll(ctx, false)
+ // 'go mod vendor' has never followed test dependencies since Go 1.11.
+ const closeOverTests = false
+ return loadAll(ctx, closeOverTests)
}
func loadAll(ctx context.Context, closeOverTests bool) []string {
diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go
index a45c4a63be..18dd293ac9 100644
--- a/src/cmd/go/internal/modload/modfile.go
+++ b/src/cmd/go/internal/modload/modfile.go
@@ -25,6 +25,11 @@ import (
"golang.org/x/mod/semver"
)
+// lazyLoadingVersion is the Go version (plus leading "v") at which lazy module
+// loading takes effect.
+const lazyLoadingVersionV = "v1.16"
+const go116EnableLazyLoading = true
+
var modFile *modfile.File
// A modFileIndex is an index of data corresponding to a modFile
@@ -249,6 +254,23 @@ func indexModFile(data []byte, modFile *modfile.File, needsFix bool) *modFileInd
return i
}
+// allPatternClosesOverTests reports whether the "all" pattern includes
+// dependencies of tests outside the main module (as in Go 1.11–1.15).
+// (Otherwise — as in Go 1.16+ — the "all" pattern includes only the packages
+// transitively *imported by* the packages and tests in the main module.)
+func (i *modFileIndex) allPatternClosesOverTests() bool {
+ if !go116EnableLazyLoading {
+ return true
+ }
+ if i != nil && semver.Compare(i.goVersionV, lazyLoadingVersionV) < 0 {
+ // The module explicitly predates the change in "all" for lazy loading, so
+ // continue to use the older interpretation. (If i == nil, we not in any
+ // module at all and should use the latest semantics.)
+ return true
+ }
+ return false
+}
+
// modFileIsDirty reports whether the go.mod file differs meaningfully
// from what was indexed.
// If modFile has been changed (even cosmetically) since it was first read,
diff --git a/src/cmd/go/testdata/script/mod_all.txt b/src/cmd/go/testdata/script/mod_all.txt
index 9f4b0a4e4d..aac66292d6 100644
--- a/src/cmd/go/testdata/script/mod_all.txt
+++ b/src/cmd/go/testdata/script/mod_all.txt
@@ -187,17 +187,105 @@ stdout '^example.com/main_test \[example.com/main.test\]$'
stdout '^example.com/main/testonly.test$'
stdout '^example.com/main/testonly_test \[example.com/main/testonly.test\]$'
-# TODO(#36460):
+rm vendor
+
+# Convert all modules to go 1.16 to enable lazy loading.
+go mod edit -go=1.16 a/go.mod
+go mod edit -go=1.16 b/go.mod
+go mod edit -go=1.16 c/go.mod
+go mod edit -go=1.16 d/go.mod
+go mod edit -go=1.16 q/go.mod
+go mod edit -go=1.16 r/go.mod
+go mod edit -go=1.16 s/go.mod
+go mod edit -go=1.16 t/go.mod
+go mod edit -go=1.16 u/go.mod
+go mod edit -go=1.16 w/go.mod
+go mod edit -go=1.16 x/go.mod
+go mod edit -go=1.16
+
+# With lazy loading, 'go list all' with neither -mod=vendor nor -test should
+# match -mod=vendor without -test in 1.15.
-# With lazy loading, 'go list all' without -mod=vendor should match
-# 'go mod vendor'.
+go list -f $PKGFMT all
+stdout -count=8 '^.'
+stdout '^example.com/a$'
+stdout '^example.com/b$'
+stdout '^example.com/main$'
+stdout '^example.com/main/testonly$'
+stdout '^example.com/q$'
+stdout '^example.com/r$'
+stdout '^example.com/t$'
+stdout '^example.com/u$'
-# 'go list -test all' should expand that to cover test dependencies
-# of packages imported by the main module.
+# 'go list -test all' should expand that to include the test variants of the
+# packages in 'all', but not the dependencies of outside tests.
-# 'go list -m all' should cover the packages in 'go list -test all'.
+go list -test -f $PKGFMT all
+stdout -count=25 '^.'
+stdout '^example.com/a$'
+stdout '^example.com/b$'
+stdout '^example.com/main$'
+stdout '^example.com/main/testonly$'
+stdout '^example.com/q$'
+stdout '^example.com/r$'
+stdout '^example.com/t$'
+stdout '^example.com/u$'
+stdout '^example.com/a.test$'
+stdout '^example.com/a_test \[example.com/a.test\]$'
+stdout '^example.com/b.test$'
+stdout '^example.com/b_test \[example.com/b.test\]$'
+stdout '^example.com/main.test$'
+stdout '^example.com/main \[example.com/main.test\]$'
+stdout '^example.com/main_test \[example.com/main.test\]$'
+stdout '^example.com/main/testonly.test$'
+stdout '^example.com/main/testonly_test \[example.com/main/testonly.test\]$'
+stdout '^example.com/q.test$'
+stdout '^example.com/q_test \[example.com/q.test\]$'
+stdout '^example.com/r.test$'
+stdout '^example.com/r_test \[example.com/r.test\]$'
+stdout '^example.com/t.test$'
+stdout '^example.com/t_test \[example.com/t.test\]$'
+stdout '^example.com/u.test$'
+stdout '^example.com/u_test \[example.com/u.test\]$'
+
+# 'go list -test -deps all' should include the dependencies of those tests,
+# but not the tests of the dependencies of outside tests.
+
+go list -test -deps -f $PKGFMT all
+stdout -count=28 '^.'
+stdout '^example.com/a$'
+stdout '^example.com/b$'
+stdout '^example.com/c$'
+stdout '^example.com/main$'
+stdout '^example.com/main/testonly$'
+stdout '^example.com/q$'
+stdout '^example.com/r$'
+stdout '^example.com/s$'
+stdout '^example.com/t$'
+stdout '^example.com/u$'
+stdout '^example.com/w$'
+stdout '^example.com/a.test$'
+stdout '^example.com/a_test \[example.com/a.test\]$'
+stdout '^example.com/b.test$'
+stdout '^example.com/b_test \[example.com/b.test\]$'
+stdout '^example.com/main.test$'
+stdout '^example.com/main \[example.com/main.test\]$'
+stdout '^example.com/main_test \[example.com/main.test\]$'
+stdout '^example.com/main/testonly.test$'
+stdout '^example.com/main/testonly_test \[example.com/main/testonly.test\]$'
+stdout '^example.com/q.test$'
+stdout '^example.com/q_test \[example.com/q.test\]$'
+stdout '^example.com/r.test$'
+stdout '^example.com/r_test \[example.com/r.test\]$'
+stdout '^example.com/t.test$'
+stdout '^example.com/t_test \[example.com/t.test\]$'
+stdout '^example.com/u.test$'
+stdout '^example.com/u_test \[example.com/u.test\]$'
+# TODO(#36460):
+# 'go list -m all' should exactly cover the packages in 'go list -test all'.
+
-- go.mod --
module example.com/main
--
cgit v1.3
From 363fb4bcc814069e3d80e22bd022599179ec1c62 Mon Sep 17 00:00:00 2001
From: "Bryan C. Mills"
Date: Wed, 1 Jul 2020 22:38:45 -0400
Subject: cmd/go/internal/modload: consolidate buildList and associated
functions into one file
Change-Id: I310c37c7f0ce5581f07cf6e27d1f6361d03b92ef
Reviewed-on: https://go-review.googlesource.com/c/go/+/244077
Run-TryBot: Bryan C. Mills
TryBot-Result: Gobot Gobot
Reviewed-by: Michael Matloob
Reviewed-by: Jay Conrod
---
src/cmd/go/internal/modget/get.go | 4 ++
src/cmd/go/internal/modload/buildlist.go | 117 +++++++++++++++++++++++++++++++
src/cmd/go/internal/modload/load.go | 103 ---------------------------
3 files changed, 121 insertions(+), 103 deletions(-)
create mode 100644 src/cmd/go/internal/modload/buildlist.go
(limited to 'src/cmd')
diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go
index 4ca7f5b529..126b1f4bd4 100644
--- a/src/cmd/go/internal/modget/get.go
+++ b/src/cmd/go/internal/modget/get.go
@@ -628,6 +628,10 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
if err != nil {
base.Fatalf("go: %v", err)
}
+
+ // TODO(bcmills) What should happen here under lazy loading?
+ // Downgrading may intentionally violate the lazy-loading invariants.
+
modload.SetBuildList(buildList)
modload.ReloadBuildList() // note: does not update go.mod
base.ExitIfErrors()
diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go
new file mode 100644
index 0000000000..2302b044e8
--- /dev/null
+++ b/src/cmd/go/internal/modload/buildlist.go
@@ -0,0 +1,117 @@
+// Copyright 2018 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.
+
+package modload
+
+import (
+ "cmd/go/internal/base"
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/imports"
+ "cmd/go/internal/mvs"
+ "context"
+ "fmt"
+ "os"
+
+ "golang.org/x/mod/module"
+)
+
+// buildList is the list of modules to use for building packages.
+// It is initialized by calling ImportPaths, ImportFromFiles,
+// LoadALL, or LoadBuildList, each of which uses loaded.load.
+//
+// Ideally, exactly ONE of those functions would be called,
+// and exactly once. Most of the time, that's true.
+// During "go get" it may not be. TODO(rsc): Figure out if
+// that restriction can be established, or else document why not.
+//
+var buildList []module.Version
+
+// LoadBuildList loads and returns the build list from go.mod.
+// The loading of the build list happens automatically in ImportPaths:
+// LoadBuildList need only be called if ImportPaths is not
+// (typically in commands that care about the module but
+// no particular package).
+func LoadBuildList(ctx context.Context) []module.Version {
+ InitMod(ctx)
+ ReloadBuildList()
+ WriteGoMod()
+ return buildList
+}
+
+// ReloadBuildList resets the state of loaded packages, then loads and returns
+// the build list set in SetBuildList.
+func ReloadBuildList() []module.Version {
+ loaded = loadFromRoots(loaderParams{
+ tags: imports.Tags(),
+ listRoots: func() []string { return nil },
+ allClosesOverTests: index.allPatternClosesOverTests(), // but doesn't matter because the root list is empty.
+ })
+ return buildList
+}
+
+// BuildList returns the module build list,
+// typically constructed by a previous call to
+// LoadBuildList or ImportPaths.
+// The caller must not modify the returned list.
+func BuildList() []module.Version {
+ return buildList
+}
+
+// SetBuildList sets the module build list.
+// The caller is responsible for ensuring that the list is valid.
+// SetBuildList does not retain a reference to the original list.
+func SetBuildList(list []module.Version) {
+ buildList = append([]module.Version{}, list...)
+}
+
+// TidyBuildList trims the build list to the minimal requirements needed to
+// retain the same versions of all packages from the preceding Load* or
+// ImportPaths* call.
+func TidyBuildList() {
+ used := map[module.Version]bool{Target: true}
+ for _, pkg := range loaded.pkgs {
+ used[pkg.mod] = true
+ }
+
+ keep := []module.Version{Target}
+ var direct []string
+ for _, m := range buildList[1:] {
+ if used[m] {
+ keep = append(keep, m)
+ if loaded.direct[m.Path] {
+ direct = append(direct, m.Path)
+ }
+ } else if cfg.BuildV {
+ if _, ok := index.require[m]; ok {
+ fmt.Fprintf(os.Stderr, "unused %s\n", m.Path)
+ }
+ }
+ }
+
+ min, err := mvs.Req(Target, direct, &mvsReqs{buildList: keep})
+ if err != nil {
+ base.Fatalf("go: %v", err)
+ }
+ buildList = append([]module.Version{Target}, min...)
+}
+
+// checkMultiplePaths verifies that a given module path is used as itself
+// or as a replacement for another module, but not both at the same time.
+//
+// (See https://golang.org/issue/26607 and https://golang.org/issue/34650.)
+func checkMultiplePaths() {
+ firstPath := make(map[module.Version]string, len(buildList))
+ for _, mod := range buildList {
+ src := mod
+ if rep := Replacement(mod); rep.Path != "" {
+ src = rep
+ }
+ if prev, ok := firstPath[src]; !ok {
+ firstPath[src] = mod.Path
+ } else if prev != mod.Path {
+ base.Errorf("go: %s@%s used for two different module paths (%s and %s)", src.Path, src.Version, prev, mod.Path)
+ }
+ }
+ base.ExitIfErrors()
+}
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index 9cedc219b6..6050646594 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -118,22 +118,10 @@ import (
"cmd/go/internal/par"
"cmd/go/internal/search"
"cmd/go/internal/str"
- "cmd/go/internal/trace"
"golang.org/x/mod/module"
)
-// buildList is the list of modules to use for building packages.
-// It is initialized by calling ImportPaths, ImportFromFiles,
-// LoadALL, or LoadBuildList, each of which uses loaded.load.
-//
-// Ideally, exactly ONE of those functions would be called,
-// and exactly once. Most of the time, that's true.
-// During "go get" it may not be. TODO(rsc): Figure out if
-// that restriction can be established, or else document why not.
-//
-var buildList []module.Version
-
// loaded is the most recently-used package loader.
// It holds details about individual packages.
var loaded *loader
@@ -250,26 +238,6 @@ func ImportPathsQuiet(ctx context.Context, patterns []string, tags map[string]bo
return matches
}
-// checkMultiplePaths verifies that a given module path is used as itself
-// or as a replacement for another module, but not both at the same time.
-//
-// (See https://golang.org/issue/26607 and https://golang.org/issue/34650.)
-func checkMultiplePaths() {
- firstPath := make(map[module.Version]string, len(buildList))
- for _, mod := range buildList {
- src := mod
- if rep := Replacement(mod); rep.Path != "" {
- src = rep
- }
- if prev, ok := firstPath[src]; !ok {
- firstPath[src] = mod.Path
- } else if prev != mod.Path {
- base.Errorf("go: %s@%s used for two different module paths (%s and %s)", src.Path, src.Version, prev, mod.Path)
- }
- }
- base.ExitIfErrors()
-}
-
// matchLocalDirs is like m.MatchDirs, but tries to avoid scanning directories
// outside of the standard library and active modules.
func matchLocalDirs(m *search.Match) {
@@ -481,31 +449,6 @@ func DirImportPath(dir string) string {
return "."
}
-// LoadBuildList loads and returns the build list from go.mod.
-// The loading of the build list happens automatically in ImportPaths:
-// LoadBuildList need only be called if ImportPaths is not
-// (typically in commands that care about the module but
-// no particular package).
-func LoadBuildList(ctx context.Context) []module.Version {
- ctx, span := trace.StartSpan(ctx, "LoadBuildList")
- defer span.Done()
- InitMod(ctx)
- ReloadBuildList()
- WriteGoMod()
- return buildList
-}
-
-// ReloadBuildList resets the state of loaded packages, then loads and returns
-// the build list set in SetBuildList.
-func ReloadBuildList() []module.Version {
- loaded = loadFromRoots(loaderParams{
- tags: imports.Tags(),
- listRoots: func() []string { return nil },
- allClosesOverTests: index.allPatternClosesOverTests(), // but doesn't matter because the root list is empty.
- })
- return buildList
-}
-
// LoadALL returns the set of all packages in the current module
// and their dependencies in any other modules, without filtering
// due to build tags, except "+build ignore".
@@ -571,52 +514,6 @@ func TargetPackages(ctx context.Context, pattern string) *search.Match {
return m
}
-// BuildList returns the module build list,
-// typically constructed by a previous call to
-// LoadBuildList or ImportPaths.
-// The caller must not modify the returned list.
-func BuildList() []module.Version {
- return buildList
-}
-
-// SetBuildList sets the module build list.
-// The caller is responsible for ensuring that the list is valid.
-// SetBuildList does not retain a reference to the original list.
-func SetBuildList(list []module.Version) {
- buildList = append([]module.Version{}, list...)
-}
-
-// TidyBuildList trims the build list to the minimal requirements needed to
-// retain the same versions of all packages from the preceding Load* or
-// ImportPaths* call.
-func TidyBuildList() {
- used := map[module.Version]bool{Target: true}
- for _, pkg := range loaded.pkgs {
- used[pkg.mod] = true
- }
-
- keep := []module.Version{Target}
- var direct []string
- for _, m := range buildList[1:] {
- if used[m] {
- keep = append(keep, m)
- if loaded.direct[m.Path] {
- direct = append(direct, m.Path)
- }
- } else if cfg.BuildV {
- if _, ok := index.require[m]; ok {
- fmt.Fprintf(os.Stderr, "unused %s\n", m.Path)
- }
- }
- }
-
- min, err := mvs.Req(Target, direct, &mvsReqs{buildList: keep})
- if err != nil {
- base.Fatalf("go: %v", err)
- }
- buildList = append([]module.Version{Target}, min...)
-}
-
// ImportMap returns the actual package import path
// for an import path found in source code.
// If the given import path does not appear in the source code
--
cgit v1.3
From 521393e7e05cd9272ae6023387fa92839d72eb4f Mon Sep 17 00:00:00 2001
From: "Bryan C. Mills"
Date: Mon, 27 Jul 2020 13:57:12 -0400
Subject: cmd/go/internal/modget: move MVS code to a separate file
For #36460
Change-Id: Ie81c03df18c6987527da765d5f6575556340cb01
Reviewed-on: https://go-review.googlesource.com/c/go/+/249877
Run-TryBot: Bryan C. Mills
TryBot-Result: Gobot Gobot
Reviewed-by: Michael Matloob
Reviewed-by: Jay Conrod
---
src/cmd/go/internal/modget/get.go | 188 +----------------------------------
src/cmd/go/internal/modget/mvs.go | 202 ++++++++++++++++++++++++++++++++++++++
2 files changed, 203 insertions(+), 187 deletions(-)
create mode 100644 src/cmd/go/internal/modget/mvs.go
(limited to 'src/cmd')
diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go
index 126b1f4bd4..cf9ad66b3d 100644
--- a/src/cmd/go/internal/modget/get.go
+++ b/src/cmd/go/internal/modget/get.go
@@ -290,7 +290,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
// what was requested.
modload.DisallowWriteGoMod()
- // Allow looking up modules for import paths outside of a module.
+ // Allow looking up modules for import paths when outside of a module.
// 'go get' is expected to do this, unlike other commands.
modload.AllowMissingModuleImports()
@@ -885,192 +885,6 @@ func getQuery(ctx context.Context, path, vers string, prevM module.Version, forc
return m, nil
}
-// An upgrader adapts an underlying mvs.Reqs to apply an
-// upgrade policy to a list of targets and their dependencies.
-type upgrader struct {
- mvs.Reqs
-
- // cmdline maps a module path to a query made for that module at a
- // specific target version. Each query corresponds to a module
- // matched by a command line argument.
- cmdline map[string]*query
-
- // upgrade is a set of modules providing dependencies of packages
- // matched by command line arguments. If -u or -u=patch is set,
- // these modules are upgraded accordingly.
- upgrade map[string]bool
-}
-
-// newUpgrader creates an upgrader. cmdline contains queries made at
-// specific versions for modules matched by command line arguments. pkgs
-// is the set of packages matched by command line arguments. If -u or -u=patch
-// is set, modules providing dependencies of pkgs are upgraded accordingly.
-func newUpgrader(cmdline map[string]*query, pkgs map[string]bool) *upgrader {
- u := &upgrader{
- Reqs: modload.Reqs(),
- cmdline: cmdline,
- }
- if getU != "" {
- u.upgrade = make(map[string]bool)
-
- // Traverse package import graph.
- // Initialize work queue with root packages.
- seen := make(map[string]bool)
- var work []string
- add := func(path string) {
- if !seen[path] {
- seen[path] = true
- work = append(work, path)
- }
- }
- for pkg := range pkgs {
- add(pkg)
- }
- for len(work) > 0 {
- pkg := work[0]
- work = work[1:]
- m := modload.PackageModule(pkg)
- u.upgrade[m.Path] = true
-
- // testImports is empty unless test imports were actually loaded,
- // i.e., -t was set or "all" was one of the arguments.
- imports, testImports := modload.PackageImports(pkg)
- for _, imp := range imports {
- add(imp)
- }
- for _, imp := range testImports {
- add(imp)
- }
- }
- }
- return u
-}
-
-// Required returns the requirement list for m.
-// For the main module, we override requirements with the modules named
-// one the command line, and we include new requirements. Otherwise,
-// we defer to u.Reqs.
-func (u *upgrader) Required(m module.Version) ([]module.Version, error) {
- rs, err := u.Reqs.Required(m)
- if err != nil {
- return nil, err
- }
- if m != modload.Target {
- return rs, nil
- }
-
- overridden := make(map[string]bool)
- for i, m := range rs {
- if q := u.cmdline[m.Path]; q != nil && q.m.Version != "none" {
- rs[i] = q.m
- overridden[q.m.Path] = true
- }
- }
- for _, q := range u.cmdline {
- if !overridden[q.m.Path] && q.m.Path != modload.Target.Path && q.m.Version != "none" {
- rs = append(rs, q.m)
- }
- }
- return rs, nil
-}
-
-// Upgrade returns the desired upgrade for m.
-//
-// If m was requested at a specific version on the command line, then
-// Upgrade returns that version.
-//
-// If -u is set and m provides a dependency of a package matched by
-// command line arguments, then Upgrade may provider a newer tagged version.
-// If m is a tagged version, then Upgrade will return the latest tagged
-// version (with the same minor version number if -u=patch).
-// If m is a pseudo-version, then Upgrade returns the latest tagged version
-// only if that version has a time-stamp newer than m. This special case
-// prevents accidental downgrades when already using a pseudo-version
-// newer than the latest tagged version.
-//
-// If none of the above cases apply, then Upgrade returns m.
-func (u *upgrader) Upgrade(m module.Version) (module.Version, error) {
- // Allow pkg@vers on the command line to override the upgrade choice v.
- // If q's version is < m.Version, then we're going to downgrade anyway,
- // and it's cleaner to avoid moving back and forth and picking up
- // extraneous other newer dependencies.
- // If q's version is > m.Version, then we're going to upgrade past
- // m.Version anyway, and again it's cleaner to avoid moving back and forth
- // picking up extraneous other newer dependencies.
- if q := u.cmdline[m.Path]; q != nil {
- return q.m, nil
- }
-
- if !u.upgrade[m.Path] {
- // Not involved in upgrade. Leave alone.
- return m, nil
- }
-
- // Run query required by upgrade semantics.
- // Note that Query "latest" is not the same as using repo.Latest,
- // which may return a pseudoversion for the latest commit.
- // Query "latest" returns the newest tagged version or the newest
- // prerelease version if there are no non-prereleases, or repo.Latest
- // if there aren't any tagged versions.
- // If we're querying "upgrade" or "patch", Query will compare the current
- // version against the chosen version and will return the current version
- // if it is newer.
- info, err := modload.Query(context.TODO(), m.Path, string(getU), m.Version, modload.CheckAllowed)
- if err != nil {
- // Report error but return m, to let version selection continue.
- // (Reporting the error will fail the command at the next base.ExitIfErrors.)
-
- // Special case: if the error is for m.Version itself and m.Version has a
- // replacement, then keep it and don't report the error: the fact that the
- // version is invalid is likely the reason it was replaced to begin with.
- var vErr *module.InvalidVersionError
- if errors.As(err, &vErr) && vErr.Version == m.Version && modload.Replacement(m).Path != "" {
- return m, nil
- }
-
- // Special case: if the error is "no matching versions" then don't
- // even report the error. Because Query does not consider pseudo-versions,
- // it may happen that we have a pseudo-version but during -u=patch
- // the query v0.0 matches no versions (not even the one we're using).
- var noMatch *modload.NoMatchingVersionError
- if !errors.As(err, &noMatch) {
- base.Errorf("go get: upgrading %s@%s: %v", m.Path, m.Version, err)
- }
- return m, nil
- }
-
- if info.Version != m.Version {
- logOncef("go: %s %s => %s", m.Path, getU, info.Version)
- }
- return module.Version{Path: m.Path, Version: info.Version}, nil
-}
-
-// buildListForLostUpgrade returns the build list for the module graph
-// rooted at lost. Unlike mvs.BuildList, the target module (lost) is not
-// treated specially. The returned build list may contain a newer version
-// of lost.
-//
-// buildListForLostUpgrade is used after a downgrade has removed a module
-// requested at a specific version. This helps us understand the requirements
-// implied by each downgrade.
-func buildListForLostUpgrade(lost module.Version, reqs mvs.Reqs) ([]module.Version, error) {
- return mvs.BuildList(lostUpgradeRoot, &lostUpgradeReqs{Reqs: reqs, lost: lost})
-}
-
-var lostUpgradeRoot = module.Version{Path: "lost-upgrade-root", Version: ""}
-
-type lostUpgradeReqs struct {
- mvs.Reqs
- lost module.Version
-}
-
-func (r *lostUpgradeReqs) Required(mod module.Version) ([]module.Version, error) {
- if mod == lostUpgradeRoot {
- return []module.Version{r.lost}, nil
- }
- return r.Reqs.Required(mod)
-}
-
// reportRetractions prints warnings if any modules in the build list are
// retracted.
func reportRetractions(ctx context.Context) {
diff --git a/src/cmd/go/internal/modget/mvs.go b/src/cmd/go/internal/modget/mvs.go
new file mode 100644
index 0000000000..19fffd2947
--- /dev/null
+++ b/src/cmd/go/internal/modget/mvs.go
@@ -0,0 +1,202 @@
+// Copyright 2020 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.
+
+package modget
+
+import (
+ "context"
+ "errors"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/modload"
+ "cmd/go/internal/mvs"
+
+ "golang.org/x/mod/module"
+)
+
+// An upgrader adapts an underlying mvs.Reqs to apply an
+// upgrade policy to a list of targets and their dependencies.
+type upgrader struct {
+ mvs.Reqs
+
+ // cmdline maps a module path to a query made for that module at a
+ // specific target version. Each query corresponds to a module
+ // matched by a command line argument.
+ cmdline map[string]*query
+
+ // upgrade is a set of modules providing dependencies of packages
+ // matched by command line arguments. If -u or -u=patch is set,
+ // these modules are upgraded accordingly.
+ upgrade map[string]bool
+}
+
+// newUpgrader creates an upgrader. cmdline contains queries made at
+// specific versions for modules matched by command line arguments. pkgs
+// is the set of packages matched by command line arguments. If -u or -u=patch
+// is set, modules providing dependencies of pkgs are upgraded accordingly.
+func newUpgrader(cmdline map[string]*query, pkgs map[string]bool) *upgrader {
+ u := &upgrader{
+ Reqs: modload.Reqs(),
+ cmdline: cmdline,
+ }
+ if getU != "" {
+ u.upgrade = make(map[string]bool)
+
+ // Traverse package import graph.
+ // Initialize work queue with root packages.
+ seen := make(map[string]bool)
+ var work []string
+ add := func(path string) {
+ if !seen[path] {
+ seen[path] = true
+ work = append(work, path)
+ }
+ }
+ for pkg := range pkgs {
+ add(pkg)
+ }
+ for len(work) > 0 {
+ pkg := work[0]
+ work = work[1:]
+ m := modload.PackageModule(pkg)
+ u.upgrade[m.Path] = true
+
+ // testImports is empty unless test imports were actually loaded,
+ // i.e., -t was set or "all" was one of the arguments.
+ imports, testImports := modload.PackageImports(pkg)
+ for _, imp := range imports {
+ add(imp)
+ }
+ for _, imp := range testImports {
+ add(imp)
+ }
+ }
+ }
+ return u
+}
+
+// Required returns the requirement list for m.
+// For the main module, we override requirements with the modules named
+// one the command line, and we include new requirements. Otherwise,
+// we defer to u.Reqs.
+func (u *upgrader) Required(m module.Version) ([]module.Version, error) {
+ rs, err := u.Reqs.Required(m)
+ if err != nil {
+ return nil, err
+ }
+ if m != modload.Target {
+ return rs, nil
+ }
+
+ overridden := make(map[string]bool)
+ for i, m := range rs {
+ if q := u.cmdline[m.Path]; q != nil && q.m.Version != "none" {
+ rs[i] = q.m
+ overridden[q.m.Path] = true
+ }
+ }
+ for _, q := range u.cmdline {
+ if !overridden[q.m.Path] && q.m.Path != modload.Target.Path && q.m.Version != "none" {
+ rs = append(rs, q.m)
+ }
+ }
+ return rs, nil
+}
+
+// Upgrade returns the desired upgrade for m.
+//
+// If m was requested at a specific version on the command line, then
+// Upgrade returns that version.
+//
+// If -u is set and m provides a dependency of a package matched by
+// command line arguments, then Upgrade may provider a newer tagged version.
+// If m is a tagged version, then Upgrade will return the latest tagged
+// version (with the same minor version number if -u=patch).
+// If m is a pseudo-version, then Upgrade returns the latest tagged version
+// only if that version has a time-stamp newer than m. This special case
+// prevents accidental downgrades when already using a pseudo-version
+// newer than the latest tagged version.
+//
+// If none of the above cases apply, then Upgrade returns m.
+func (u *upgrader) Upgrade(m module.Version) (module.Version, error) {
+ // Allow pkg@vers on the command line to override the upgrade choice v.
+ // If q's version is < m.Version, then we're going to downgrade anyway,
+ // and it's cleaner to avoid moving back and forth and picking up
+ // extraneous other newer dependencies.
+ // If q's version is > m.Version, then we're going to upgrade past
+ // m.Version anyway, and again it's cleaner to avoid moving back and forth
+ // picking up extraneous other newer dependencies.
+ if q := u.cmdline[m.Path]; q != nil {
+ return q.m, nil
+ }
+
+ if !u.upgrade[m.Path] {
+ // Not involved in upgrade. Leave alone.
+ return m, nil
+ }
+
+ // Run query required by upgrade semantics.
+ // Note that Query "latest" is not the same as using repo.Latest,
+ // which may return a pseudoversion for the latest commit.
+ // Query "latest" returns the newest tagged version or the newest
+ // prerelease version if there are no non-prereleases, or repo.Latest
+ // if there aren't any tagged versions.
+ // If we're querying "upgrade" or "patch", Query will compare the current
+ // version against the chosen version and will return the current version
+ // if it is newer.
+ info, err := modload.Query(context.TODO(), m.Path, string(getU), m.Version, modload.CheckAllowed)
+ if err != nil {
+ // Report error but return m, to let version selection continue.
+ // (Reporting the error will fail the command at the next base.ExitIfErrors.)
+
+ // Special case: if the error is for m.Version itself and m.Version has a
+ // replacement, then keep it and don't report the error: the fact that the
+ // version is invalid is likely the reason it was replaced to begin with.
+ var vErr *module.InvalidVersionError
+ if errors.As(err, &vErr) && vErr.Version == m.Version && modload.Replacement(m).Path != "" {
+ return m, nil
+ }
+
+ // Special case: if the error is "no matching versions" then don't
+ // even report the error. Because Query does not consider pseudo-versions,
+ // it may happen that we have a pseudo-version but during -u=patch
+ // the query v0.0 matches no versions (not even the one we're using).
+ var noMatch *modload.NoMatchingVersionError
+ if !errors.As(err, &noMatch) {
+ base.Errorf("go get: upgrading %s@%s: %v", m.Path, m.Version, err)
+ }
+ return m, nil
+ }
+
+ if info.Version != m.Version {
+ logOncef("go: %s %s => %s", m.Path, getU, info.Version)
+ }
+ return module.Version{Path: m.Path, Version: info.Version}, nil
+}
+
+// buildListForLostUpgrade returns the build list for the module graph
+// rooted at lost. Unlike mvs.BuildList, the target module (lost) is not
+// treated specially. The returned build list may contain a newer version
+// of lost.
+//
+// buildListForLostUpgrade is used after a downgrade has removed a module
+// requested at a specific version. This helps us understand the requirements
+// implied by each downgrade.
+func buildListForLostUpgrade(lost module.Version, reqs mvs.Reqs) ([]module.Version, error) {
+ return mvs.BuildList(lostUpgradeRoot, &lostUpgradeReqs{Reqs: reqs, lost: lost})
+}
+
+var lostUpgradeRoot = module.Version{Path: "lost-upgrade-root", Version: ""}
+
+type lostUpgradeReqs struct {
+ mvs.Reqs
+ lost module.Version
+}
+
+func (r *lostUpgradeReqs) Required(mod module.Version) ([]module.Version, error) {
+ if mod == lostUpgradeRoot {
+ return []module.Version{r.lost}, nil
+ }
+ return r.Reqs.Required(mod)
+}
--
cgit v1.3
From 564b350c08a1906e8f6a876fef4cca71f6516d4c Mon Sep 17 00:00:00 2001
From: "Bryan C. Mills"
Date: Mon, 27 Jul 2020 12:57:36 -0400
Subject: cmd/go/internal/modload: rename LoadBuildList and BuildList
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
With lazy loading, the “build list” can be refined as packages are loaded.
Rename functions that return the build list to more precisely describe
the set of modules returned by the call.
Also eliminate a redundant call to LoadBuildList (right before
ListModules, which itself begins with the same call).
For #36460
Change-Id: I0fc4f9dd7602e0df5e166e329ee5d516d810ca53
Reviewed-on: https://go-review.googlesource.com/c/go/+/249878
Run-TryBot: Bryan C. Mills
TryBot-Result: Gobot Gobot
Reviewed-by: Jay Conrod
Reviewed-by: Michael Matloob
---
src/cmd/go/internal/list/list.go | 2 --
src/cmd/go/internal/modcmd/graph.go | 2 +-
src/cmd/go/internal/modcmd/vendor.go | 2 +-
src/cmd/go/internal/modcmd/verify.go | 2 +-
src/cmd/go/internal/modget/get.go | 10 +++----
src/cmd/go/internal/modload/build.go | 2 +-
src/cmd/go/internal/modload/buildlist.go | 47 ++++++++++++++++++--------------
src/cmd/go/internal/modload/list.go | 2 +-
8 files changed, 36 insertions(+), 33 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go
index 65003dc883..23500dd9d8 100644
--- a/src/cmd/go/internal/list/list.go
+++ b/src/cmd/go/internal/list/list.go
@@ -437,8 +437,6 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
}
}
- modload.LoadBuildList(ctx)
-
mods := modload.ListModules(ctx, args, *listU, *listVersions, *listRetracted)
if !*listE {
for _, m := range mods {
diff --git a/src/cmd/go/internal/modcmd/graph.go b/src/cmd/go/internal/modcmd/graph.go
index 6da12b9cab..513536a010 100644
--- a/src/cmd/go/internal/modcmd/graph.go
+++ b/src/cmd/go/internal/modcmd/graph.go
@@ -48,7 +48,7 @@ func runGraph(ctx context.Context, cmd *base.Command, args []string) {
base.Fatalf("go: cannot find main module; see 'go help modules'")
}
}
- modload.LoadBuildList(ctx)
+ modload.LoadAllModules(ctx)
reqs := modload.MinReqs()
format := func(m module.Version) string {
diff --git a/src/cmd/go/internal/modcmd/vendor.go b/src/cmd/go/internal/modcmd/vendor.go
index e5353b5c7f..30334f3a42 100644
--- a/src/cmd/go/internal/modcmd/vendor.go
+++ b/src/cmd/go/internal/modcmd/vendor.go
@@ -77,7 +77,7 @@ func runVendor(ctx context.Context, cmd *base.Command, args []string) {
}
var buf bytes.Buffer
- for _, m := range modload.BuildList()[1:] {
+ for _, m := range modload.LoadedModules()[1:] {
if pkgs := modpkgs[m]; len(pkgs) > 0 || isExplicit[m] {
line := moduleLine(m, modload.Replacement(m))
buf.WriteString(line)
diff --git a/src/cmd/go/internal/modcmd/verify.go b/src/cmd/go/internal/modcmd/verify.go
index 73ab714d10..d542825823 100644
--- a/src/cmd/go/internal/modcmd/verify.go
+++ b/src/cmd/go/internal/modcmd/verify.go
@@ -60,7 +60,7 @@ func runVerify(ctx context.Context, cmd *base.Command, args []string) {
sem := make(chan token, runtime.GOMAXPROCS(0))
// Use a slice of result channels, so that the output is deterministic.
- mods := modload.LoadBuildList(ctx)[1:]
+ mods := modload.LoadAllModules(ctx)[1:]
errsChans := make([]<-chan []error, len(mods))
for i, mod := range mods {
diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go
index cf9ad66b3d..a2a8287d84 100644
--- a/src/cmd/go/internal/modget/get.go
+++ b/src/cmd/go/internal/modget/get.go
@@ -278,7 +278,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
}
modload.LoadTests = *getT
- buildList := modload.LoadBuildList(ctx)
+ buildList := modload.LoadAllModules(ctx)
buildList = buildList[:len(buildList):len(buildList)] // copy on append
versionByPath := make(map[string]string)
for _, m := range buildList {
@@ -599,7 +599,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
base.ExitIfErrors()
// Stop if no changes have been made to the build list.
- buildList = modload.BuildList()
+ buildList = modload.LoadedModules()
eq := len(buildList) == len(prevBuildList)
for i := 0; eq && i < len(buildList); i++ {
eq = buildList[i] == prevBuildList[i]
@@ -617,7 +617,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
// Handle downgrades.
var down []module.Version
- for _, m := range modload.BuildList() {
+ for _, m := range modload.LoadedModules() {
q := byPath[m.Path]
if q != nil && semver.Compare(m.Version, q.m.Version) > 0 {
down = append(down, module.Version{Path: m.Path, Version: q.m.Version})
@@ -641,7 +641,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
var lostUpgrades []*query
if len(down) > 0 {
versionByPath = make(map[string]string)
- for _, m := range modload.BuildList() {
+ for _, m := range modload.LoadedModules() {
versionByPath[m.Path] = m.Version
}
for _, q := range byPath {
@@ -892,7 +892,7 @@ func reportRetractions(ctx context.Context) {
// Use modload.ListModules, since that provides information in the same format
// as 'go list -m'. Don't query for "all", since that's not allowed outside a
// module.
- buildList := modload.BuildList()
+ buildList := modload.LoadedModules()
args := make([]string, 0, len(buildList))
for _, m := range buildList {
if m.Version == "" {
diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go
index e9f9a82fab..9ca6230500 100644
--- a/src/cmd/go/internal/modload/build.go
+++ b/src/cmd/go/internal/modload/build.go
@@ -76,7 +76,7 @@ func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic {
return moduleInfo(ctx, m, fromBuildList, listRetracted)
}
- for _, m := range BuildList() {
+ for _, m := range LoadedModules() {
if m.Path == path {
fromBuildList := true
return moduleInfo(ctx, m, fromBuildList, listRetracted)
diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go
index 2302b044e8..581a1b944a 100644
--- a/src/cmd/go/internal/modload/buildlist.go
+++ b/src/cmd/go/internal/modload/buildlist.go
@@ -27,34 +27,28 @@ import (
//
var buildList []module.Version
-// LoadBuildList loads and returns the build list from go.mod.
-// The loading of the build list happens automatically in ImportPaths:
-// LoadBuildList need only be called if ImportPaths is not
-// (typically in commands that care about the module but
-// no particular package).
-func LoadBuildList(ctx context.Context) []module.Version {
+// LoadAllModules loads and returns the list of modules matching the "all"
+// module pattern, starting with the Target module and in a deterministic
+// (stable) order, without loading any packages.
+//
+// Modules are loaded automatically (and lazily) in ImportPaths:
+// LoadAllModules need only be called if ImportPaths is not,
+// typically in commands that care about modules but no particular package.
+//
+// The caller must not modify the returned list.
+func LoadAllModules(ctx context.Context) []module.Version {
InitMod(ctx)
ReloadBuildList()
WriteGoMod()
return buildList
}
-// ReloadBuildList resets the state of loaded packages, then loads and returns
-// the build list set in SetBuildList.
-func ReloadBuildList() []module.Version {
- loaded = loadFromRoots(loaderParams{
- tags: imports.Tags(),
- listRoots: func() []string { return nil },
- allClosesOverTests: index.allPatternClosesOverTests(), // but doesn't matter because the root list is empty.
- })
- return buildList
-}
-
-// BuildList returns the module build list,
-// typically constructed by a previous call to
-// LoadBuildList or ImportPaths.
+// LoadedModules returns the list of module requirements loaded or set by a
+// previous call (typically LoadAllModules or ImportPaths), starting with the
+// Target module and in a deterministic (stable) order.
+//
// The caller must not modify the returned list.
-func BuildList() []module.Version {
+func LoadedModules() []module.Version {
return buildList
}
@@ -65,6 +59,17 @@ func SetBuildList(list []module.Version) {
buildList = append([]module.Version{}, list...)
}
+// ReloadBuildList resets the state of loaded packages, then loads and returns
+// the build list set in SetBuildList.
+func ReloadBuildList() []module.Version {
+ loaded = loadFromRoots(loaderParams{
+ tags: imports.Tags(),
+ listRoots: func() []string { return nil },
+ allClosesOverTests: index.allPatternClosesOverTests(), // but doesn't matter because the root list is empty.
+ })
+ return buildList
+}
+
// TidyBuildList trims the build list to the minimal requirements needed to
// retain the same versions of all packages from the preceding Load* or
// ImportPaths* call.
diff --git a/src/cmd/go/internal/modload/list.go b/src/cmd/go/internal/modload/list.go
index 8c7b9a3950..3491f941cd 100644
--- a/src/cmd/go/internal/modload/list.go
+++ b/src/cmd/go/internal/modload/list.go
@@ -58,7 +58,7 @@ func ListModules(ctx context.Context, args []string, listU, listVersions, listRe
}
func listModules(ctx context.Context, args []string, listVersions, listRetracted bool) []*modinfo.ModulePublic {
- LoadBuildList(ctx)
+ LoadAllModules(ctx)
if len(args) == 0 {
return []*modinfo.ModulePublic{moduleInfo(ctx, buildList[0], true, listRetracted)}
}
--
cgit v1.3
From aa476ba6f43ebc4e7ddb6599a7ad35d9fbf1ec6d Mon Sep 17 00:00:00 2001
From: "Bryan C. Mills"
Date: Tue, 1 Sep 2020 00:34:03 -0400
Subject: cmd/go/internal/modload: refactor pathInModuleCache
I found the control flow of this function a bit tricky to reason about
due to nesting and interaction between conditions and iteration. This
change factors out a helper function that can return early instead of
mixing conditionals and 'continue' statements.
Also remove the (unused) ModuleUsedDirectly function.
For #36460
Change-Id: I60a2a5a1b32989e5a17a14e1a8c858b280cda8f2
Reviewed-on: https://go-review.googlesource.com/c/go/+/251998
Run-TryBot: Bryan C. Mills
TryBot-Result: Gobot Gobot
Reviewed-by: Jay Conrod
Reviewed-by: Michael Matloob
---
src/cmd/go/internal/modload/load.go | 33 ++++++++++++++++++++-------------
1 file changed, 20 insertions(+), 13 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index 6050646594..1664d8c5be 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -374,7 +374,7 @@ var (
// pathInModuleCache returns the import path of the directory dir,
// if dir is in the module cache copy of a module in our build list.
func pathInModuleCache(dir string) string {
- for _, m := range buildList[1:] {
+ tryMod := func(m module.Version) (string, bool) {
var root string
var err error
if repl := Replacement(m); repl.Path != "" && repl.Version == "" {
@@ -388,13 +388,26 @@ func pathInModuleCache(dir string) string {
root, err = modfetch.DownloadDir(m)
}
if err != nil {
- continue
+ return "", false
}
- if sub := search.InDir(dir, root); sub != "" {
- sub = filepath.ToSlash(sub)
- if !strings.Contains(sub, "/vendor/") && !strings.HasPrefix(sub, "vendor/") && !strings.Contains(sub, "@") {
- return path.Join(m.Path, filepath.ToSlash(sub))
- }
+
+ sub := search.InDir(dir, root)
+ if sub == "" {
+ return "", false
+ }
+ sub = filepath.ToSlash(sub)
+ if strings.Contains(sub, "/vendor/") || strings.HasPrefix(sub, "vendor/") || strings.Contains(sub, "@") {
+ return "", false
+ }
+
+ return path.Join(m.Path, filepath.ToSlash(sub)), true
+ }
+
+ for _, m := range buildList[1:] {
+ if importPath, ok := tryMod(m); ok {
+ // checkMultiplePaths ensures that a module can be used for at most one
+ // requirement, so this must be it.
+ return importPath
}
}
return ""
@@ -568,12 +581,6 @@ func PackageImports(path string) (imports, testImports []string) {
return imports, testImports
}
-// ModuleUsedDirectly reports whether the main module directly imports
-// some package in the module with the given path.
-func ModuleUsedDirectly(path string) bool {
- return loaded.direct[path]
-}
-
// Lookup returns the source directory, import path, and any loading error for
// the package at path as imported from the package in parentDir.
// Lookup requires that one of the Load functions in this package has already
--
cgit v1.3
From dfdc3880b01d46d1d8125ab9eea0606b2fa5b819 Mon Sep 17 00:00:00 2001
From: fanzha02
Date: Thu, 20 Aug 2020 17:02:18 +0800
Subject: cmd/internal/obj/arm64: enable some SIMD instructions
Enable VBSL, VBIT, VCMTST, VUXTL VUXTL2 and FMOVQ SIMD
instructions required by the issue #40725. And FMOVQ
instrucion is used to move a large constant to a Vn
register.
Add test cases.
Fixes #40725
Change-Id: I1cac1922a0a0165d698a4b73a41f7a5f0a0ad549
Reviewed-on: https://go-review.googlesource.com/c/go/+/249758
Reviewed-by: Cherry Zhang
---
src/cmd/asm/internal/asm/testdata/arm64.s | 15 +++
src/cmd/asm/internal/asm/testdata/arm64error.s | 5 +
src/cmd/internal/obj/arm64/a.out.go | 6 ++
src/cmd/internal/obj/arm64/anames.go | 6 ++
src/cmd/internal/obj/arm64/asm7.go | 121 ++++++++++++++++++++++---
5 files changed, 139 insertions(+), 14 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/asm/internal/asm/testdata/arm64.s b/src/cmd/asm/internal/asm/testdata/arm64.s
index f0c716a2b5..451ca749ba 100644
--- a/src/cmd/asm/internal/asm/testdata/arm64.s
+++ b/src/cmd/asm/internal/asm/testdata/arm64.s
@@ -145,6 +145,17 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8
VZIP2 V10.D2, V13.D2, V3.D2 // a379ca4e
VZIP1 V17.S2, V4.S2, V26.S2 // 9a38910e
VZIP2 V25.S2, V14.S2, V25.S2 // d979990e
+ VUXTL V30.B8, V30.H8 // dea7082f
+ VUXTL V30.H4, V29.S4 // dda7102f
+ VUXTL V29.S2, V2.D2 // a2a7202f
+ VUXTL2 V30.H8, V30.S4 // dea7106f
+ VUXTL2 V29.S4, V2.D2 // a2a7206f
+ VUXTL2 V30.B16, V2.H8 // c2a7086f
+ VBIT V21.B16, V25.B16, V4.B16 // 241fb56e
+ VBSL V23.B16, V3.B16, V7.B16 // 671c776e
+ VCMTST V2.B8, V29.B8, V2.B8 // a28f220e
+ VCMTST V2.D2, V23.D2, V3.D2 // e38ee24e
+ VSUB V2.B8, V30.B8, V30.B8 // de87222e
MOVD (R2)(R6.SXTW), R4 // 44c866f8
MOVD (R3)(R6), R5 // MOVD (R3)(R6*1), R5 // 656866f8
MOVD (R2)(R6), R4 // MOVD (R2)(R6*1), R4 // 446866f8
@@ -186,6 +197,10 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8
FMOVS $(0.96875), F3 // 03f02d1e
FMOVD $(28.0), F4 // 0490671e
+// move a large constant to a Vd.
+ FMOVD $0x8040201008040201, V20 // FMOVD $-9205322385119247871, V20
+ FMOVQ $0x8040201008040202, V29 // FMOVQ $-9205322385119247870, V29
+
FMOVS (R2)(R6), F4 // FMOVS (R2)(R6*1), F4 // 446866bc
FMOVS (R2)(R6<<2), F4 // 447866bc
FMOVD (R2)(R6), F4 // FMOVD (R2)(R6*1), F4 // 446866fc
diff --git a/src/cmd/asm/internal/asm/testdata/arm64error.s b/src/cmd/asm/internal/asm/testdata/arm64error.s
index 9f377817a9..2a911b4cce 100644
--- a/src/cmd/asm/internal/asm/testdata/arm64error.s
+++ b/src/cmd/asm/internal/asm/testdata/arm64error.s
@@ -340,4 +340,9 @@ TEXT errors(SB),$0
MRS PMSWINC_EL0, R3 // ERROR "system register is not readable"
MRS OSLAR_EL1, R3 // ERROR "system register is not readable"
VLD3R.P 24(R15), [V15.H4,V16.H4,V17.H4] // ERROR "invalid post-increment offset"
+ VBIT V1.H4, V12.H4, V3.H4 // ERROR "invalid arrangement"
+ VBSL V1.D2, V12.D2, V3.D2 // ERROR "invalid arrangement"
+ VUXTL V30.D2, V30.H8 // ERROR "operand mismatch"
+ VUXTL2 V20.B8, V21.H8 // ERROR "operand mismatch"
+ VUXTL V3.D2, V4.B8 // ERROR "operand mismatch"
RET
diff --git a/src/cmd/internal/obj/arm64/a.out.go b/src/cmd/internal/obj/arm64/a.out.go
index 03e0278a33..ab065e07e5 100644
--- a/src/cmd/internal/obj/arm64/a.out.go
+++ b/src/cmd/internal/obj/arm64/a.out.go
@@ -874,6 +874,7 @@ const (
AFLDPS
AFMOVD
AFMOVS
+ AFMOVQ
AFMULD
AFMULS
AFNEGD
@@ -987,9 +988,14 @@ const (
AVUSHR
AVSHL
AVSRI
+ AVBSL
+ AVBIT
AVTBL
AVZIP1
AVZIP2
+ AVCMTST
+ AVUXTL
+ AVUXTL2
ALAST
AB = obj.AJMP
ABL = obj.ACALL
diff --git a/src/cmd/internal/obj/arm64/anames.go b/src/cmd/internal/obj/arm64/anames.go
index 65ecd007ea..8961f04b0c 100644
--- a/src/cmd/internal/obj/arm64/anames.go
+++ b/src/cmd/internal/obj/arm64/anames.go
@@ -381,6 +381,7 @@ var Anames = []string{
"FLDPS",
"FMOVD",
"FMOVS",
+ "FMOVQ",
"FMULD",
"FMULS",
"FNEGD",
@@ -494,8 +495,13 @@ var Anames = []string{
"VUSHR",
"VSHL",
"VSRI",
+ "VBSL",
+ "VBIT",
"VTBL",
"VZIP1",
"VZIP2",
+ "VCMTST",
+ "VUXTL",
+ "VUXTL2",
"LAST",
}
diff --git a/src/cmd/internal/obj/arm64/asm7.go b/src/cmd/internal/obj/arm64/asm7.go
index 0b90e31392..7ce18d0f13 100644
--- a/src/cmd/internal/obj/arm64/asm7.go
+++ b/src/cmd/internal/obj/arm64/asm7.go
@@ -393,6 +393,11 @@ var optab = []Optab{
{AMOVK, C_VCON, C_NONE, C_NONE, C_REG, 33, 4, 0, 0, 0},
{AMOVD, C_AACON, C_NONE, C_NONE, C_REG, 4, 4, REGFROM, 0, 0},
+ // Move a large constant to a Vn.
+ {AFMOVQ, C_VCON, C_NONE, C_NONE, C_VREG, 101, 4, 0, LFROM, 0},
+ {AFMOVD, C_VCON, C_NONE, C_NONE, C_VREG, 101, 4, 0, LFROM, 0},
+ {AFMOVS, C_LCON, C_NONE, C_NONE, C_VREG, 101, 4, 0, LFROM, 0},
+
/* jump operations */
{AB, C_NONE, C_NONE, C_NONE, C_SBRA, 5, 4, 0, 0, 0},
{ABL, C_NONE, C_NONE, C_NONE, C_SBRA, 5, 4, 0, 0, 0},
@@ -403,12 +408,14 @@ var optab = []Optab{
{obj.ARET, C_NONE, C_NONE, C_NONE, C_REG, 6, 4, 0, 0, 0},
{obj.ARET, C_NONE, C_NONE, C_NONE, C_ZOREG, 6, 4, 0, 0, 0},
{ABEQ, C_NONE, C_NONE, C_NONE, C_SBRA, 7, 4, 0, 0, 0},
- {AADRP, C_SBRA, C_NONE, C_NONE, C_REG, 60, 4, 0, 0, 0},
- {AADR, C_SBRA, C_NONE, C_NONE, C_REG, 61, 4, 0, 0, 0},
{ACBZ, C_REG, C_NONE, C_NONE, C_SBRA, 39, 4, 0, 0, 0},
{ATBZ, C_VCON, C_REG, C_NONE, C_SBRA, 40, 4, 0, 0, 0},
{AERET, C_NONE, C_NONE, C_NONE, C_NONE, 41, 4, 0, 0, 0},
+ // get a PC-relative address
+ {AADRP, C_SBRA, C_NONE, C_NONE, C_REG, 60, 4, 0, 0, 0},
+ {AADR, C_SBRA, C_NONE, C_NONE, C_REG, 61, 4, 0, 0, 0},
+
{ACLREX, C_NONE, C_NONE, C_NONE, C_VCON, 38, 4, 0, 0, 0},
{ACLREX, C_NONE, C_NONE, C_NONE, C_NONE, 38, 4, 0, 0, 0},
{ABFM, C_VCON, C_REG, C_VCON, C_REG, 42, 4, 0, 0, 0},
@@ -473,6 +480,7 @@ var optab = []Optab{
{AVTBL, C_ARNG, C_NONE, C_LIST, C_ARNG, 100, 4, 0, 0, 0},
{AVUSHR, C_VCON, C_ARNG, C_NONE, C_ARNG, 95, 4, 0, 0, 0},
{AVZIP1, C_ARNG, C_ARNG, C_NONE, C_ARNG, 72, 4, 0, 0, 0},
+ {AVUXTL, C_ARNG, C_NONE, C_NONE, C_ARNG, 102, 4, 0, 0, 0},
/* conditional operations */
{ACSEL, C_COND, C_REG, C_REG, C_REG, 18, 4, 0, 0, 0},
@@ -2657,7 +2665,7 @@ func buildop(ctxt *obj.Link) {
case AFCSELD:
oprangeset(AFCSELS, t)
- case AFMOVS, AFMOVD:
+ case AFMOVS, AFMOVD, AFMOVQ:
break
case AFCVTZSD:
@@ -2740,6 +2748,9 @@ func buildop(ctxt *obj.Link) {
oprangeset(AVCMEQ, t)
oprangeset(AVORR, t)
oprangeset(AVEOR, t)
+ oprangeset(AVBSL, t)
+ oprangeset(AVBIT, t)
+ oprangeset(AVCMTST, t)
case AVADD:
oprangeset(AVSUB, t)
@@ -2787,6 +2798,9 @@ func buildop(ctxt *obj.Link) {
case AVZIP1:
oprangeset(AVZIP2, t)
+ case AVUXTL:
+ oprangeset(AVUXTL2, t)
+
case AVLD1R:
oprangeset(AVLD2, t)
oprangeset(AVLD2R, t)
@@ -4163,7 +4177,7 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) {
rel.Add = 0
rel.Type = objabi.R_ARM64_GOTPCREL
- case 72: /* vaddp/vand/vcmeq/vorr/vadd/veor/vfmla/vfmls Vm., Vn., Vd. */
+ case 72: /* vaddp/vand/vcmeq/vorr/vadd/veor/vfmla/vfmls/vbit/vbsl/vcmtst/vsub Vm., Vn., Vd. */
af := int((p.From.Reg >> 5) & 15)
af3 := int((p.Reg >> 5) & 15)
at := int((p.To.Reg >> 5) & 15)
@@ -4204,17 +4218,24 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) {
c.ctxt.Diag("invalid arrangement: %v", p)
}
- if (p.As == AVORR || p.As == AVAND || p.As == AVEOR) &&
- (af != ARNG_16B && af != ARNG_8B) {
- c.ctxt.Diag("invalid arrangement: %v", p)
- } else if (p.As == AVFMLA || p.As == AVFMLS) &&
- (af != ARNG_2D && af != ARNG_2S && af != ARNG_4S) {
- c.ctxt.Diag("invalid arrangement: %v", p)
- } else if p.As == AVORR {
- size = 2
- } else if p.As == AVAND || p.As == AVEOR {
+ switch p.As {
+ case AVORR, AVAND, AVEOR, AVBIT, AVBSL:
+ if af != ARNG_16B && af != ARNG_8B {
+ c.ctxt.Diag("invalid arrangement: %v", p)
+ }
+ case AVFMLA, AVFMLS:
+ if af != ARNG_2D && af != ARNG_2S && af != ARNG_4S {
+ c.ctxt.Diag("invalid arrangement: %v", p)
+ }
+ }
+ switch p.As {
+ case AVAND, AVEOR:
size = 0
- } else if p.As == AVFMLA || p.As == AVFMLS {
+ case AVBSL:
+ size = 1
+ case AVORR, AVBIT:
+ size = 2
+ case AVFMLA, AVFMLS:
if af == ARNG_2D {
size = 1
} else {
@@ -5096,6 +5117,59 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) {
o1 = q<<30 | 0xe<<24 | len<<13
o1 |= (uint32(rf&31) << 16) | uint32(offset&31)<<5 | uint32(rt&31)
+ case 101: // FOMVQ/FMOVD $vcon, Vd -> load from constant pool.
+ o1 = c.omovlit(p.As, p, &p.From, int(p.To.Reg))
+
+ case 102: // VUXTL{2} Vn., Vd.
+ af := int((p.From.Reg >> 5) & 15)
+ at := int((p.To.Reg >> 5) & 15)
+ var Q, immh uint32
+ switch at {
+ case ARNG_8H:
+ if af == ARNG_8B {
+ immh = 1
+ Q = 0
+ } else if af == ARNG_16B {
+ immh = 1
+ Q = 1
+ } else {
+ c.ctxt.Diag("operand mismatch: %v\n", p)
+ }
+ case ARNG_4S:
+ if af == ARNG_4H {
+ immh = 2
+ Q = 0
+ } else if af == ARNG_8H {
+ immh = 2
+ Q = 1
+ } else {
+ c.ctxt.Diag("operand mismatch: %v\n", p)
+ }
+ case ARNG_2D:
+ if af == ARNG_2S {
+ immh = 4
+ Q = 0
+ } else if af == ARNG_4S {
+ immh = 4
+ Q = 1
+ } else {
+ c.ctxt.Diag("operand mismatch: %v\n", p)
+ }
+ default:
+ c.ctxt.Diag("operand mismatch: %v\n", p)
+ }
+
+ if p.As == AVUXTL && Q == 1 {
+ c.ctxt.Diag("operand mismatch: %v\n", p)
+ }
+ if p.As == AVUXTL2 && Q == 0 {
+ c.ctxt.Diag("operand mismatch: %v\n", p)
+ }
+
+ o1 = c.oprrr(p, p.As)
+ rf := int((p.From.Reg) & 31)
+ rt := int((p.To.Reg) & 31)
+ o1 |= Q<<30 | immh<<19 | uint32((rf&31)<<5) | uint32(rt&31)
}
out[0] = o1
out[1] = o2
@@ -5662,6 +5736,9 @@ func (c *ctxt7) oprrr(p *obj.Prog, a obj.As) uint32 {
case AVADD:
return 7<<25 | 1<<21 | 1<<15 | 1<<10
+ case AVSUB:
+ return 0x17<<25 | 1<<21 | 1<<15 | 1<<10
+
case AVADDP:
return 7<<25 | 1<<21 | 1<<15 | 15<<10
@@ -5724,6 +5801,18 @@ func (c *ctxt7) oprrr(p *obj.Prog, a obj.As) uint32 {
case AVLD2R, AVLD4R:
return 0xD<<24 | 3<<21
+
+ case AVBIT:
+ return 1<<29 | 0x75<<21 | 7<<10
+
+ case AVBSL:
+ return 1<<29 | 0x73<<21 | 7<<10
+
+ case AVCMTST:
+ return 0xE<<24 | 1<<21 | 0x23<<10
+
+ case AVUXTL, AVUXTL2:
+ return 0x5e<<23 | 0x29<<10
}
c.ctxt.Diag("%v: bad rrr %d %v", p, a, a)
@@ -6566,6 +6655,10 @@ func (c *ctxt7) omovlit(as obj.As, p *obj.Prog, a *obj.Addr, dr int) uint32 {
fp = 1
w = 1 /* 64-bit SIMD/FP */
+ case AFMOVQ:
+ fp = 1
+ w = 2 /* 128-bit SIMD/FP */
+
case AMOVD:
if p.Pool.As == ADWORD {
w = 1 /* 64-bit */
--
cgit v1.3
From 9b2df72b63ff977004756e9b847f926b4fb8d8a8 Mon Sep 17 00:00:00 2001
From: Cherry Zhang
Date: Thu, 10 Sep 2020 11:07:48 -0400
Subject: cmd/link: add copyright header
Change-Id: I44f57019bb8e659d4aa3da8b13e8bd9a20b9d2e1
Reviewed-on: https://go-review.googlesource.com/c/go/+/253920
Reviewed-by: Than McIntosh
---
src/cmd/link/link_test.go | 4 ++++
1 file changed, 4 insertions(+)
(limited to 'src/cmd')
diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go
index 72ff01c932..98798be465 100644
--- a/src/cmd/link/link_test.go
+++ b/src/cmd/link/link_test.go
@@ -1,3 +1,7 @@
+// Copyright 2016 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.
+
package main
import (
--
cgit v1.3
From d7ab277eed4d2e5ede4f3361adf42d4ad76ced8f Mon Sep 17 00:00:00 2001
From: Junchen Li
Date: Mon, 31 Aug 2020 13:32:33 +0800
Subject: cmd/asm: add more SIMD instructions on arm64
This CL adds USHLL, USHLL2, UZP1, UZP2, and BIF instructions requested
by #40725. And since UXTL* are aliases of USHLL*, this CL also merges
them into one case.
Updates #40725
Change-Id: I404a4fdaf953319f72eea548175bec1097a2a816
Reviewed-on: https://go-review.googlesource.com/c/go/+/253659
Reviewed-by: Cherry Zhang
Run-TryBot: Cherry Zhang
TryBot-Result: Gobot Gobot
---
src/cmd/asm/internal/asm/testdata/arm64.s | 20 +++++
src/cmd/asm/internal/asm/testdata/arm64error.s | 8 ++
src/cmd/internal/obj/arm64/a.out.go | 9 +-
src/cmd/internal/obj/arm64/anames.go | 9 +-
src/cmd/internal/obj/arm64/asm7.go | 109 +++++++++++++------------
5 files changed, 100 insertions(+), 55 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/asm/internal/asm/testdata/arm64.s b/src/cmd/asm/internal/asm/testdata/arm64.s
index 451ca749ba..e106ff2ae1 100644
--- a/src/cmd/asm/internal/asm/testdata/arm64.s
+++ b/src/cmd/asm/internal/asm/testdata/arm64.s
@@ -156,6 +156,26 @@ TEXT foo(SB), DUPOK|NOSPLIT, $-8
VCMTST V2.B8, V29.B8, V2.B8 // a28f220e
VCMTST V2.D2, V23.D2, V3.D2 // e38ee24e
VSUB V2.B8, V30.B8, V30.B8 // de87222e
+ VUZP1 V0.B8, V30.B8, V1.B8 // c11b000e
+ VUZP1 V1.B16, V29.B16, V2.B16 // a21b014e
+ VUZP1 V2.H4, V28.H4, V3.H4 // 831b420e
+ VUZP1 V3.H8, V27.H8, V4.H8 // 641b434e
+ VUZP1 V28.S2, V2.S2, V5.S2 // 45189c0e
+ VUZP1 V29.S4, V1.S4, V6.S4 // 26189d4e
+ VUZP1 V30.D2, V0.D2, V7.D2 // 0718de4e
+ VUZP2 V0.D2, V30.D2, V1.D2 // c15bc04e
+ VUZP2 V30.D2, V0.D2, V29.D2 // 1d58de4e
+ VUSHLL $0, V30.B8, V30.H8 // dea7082f
+ VUSHLL $0, V30.H4, V29.S4 // dda7102f
+ VUSHLL $0, V29.S2, V2.D2 // a2a7202f
+ VUSHLL2 $0, V30.B16, V2.H8 // c2a7086f
+ VUSHLL2 $0, V30.H8, V30.S4 // dea7106f
+ VUSHLL2 $0, V29.S4, V2.D2 // a2a7206f
+ VUSHLL $7, V30.B8, V30.H8 // dea70f2f
+ VUSHLL $15, V30.H4, V29.S4 // dda71f2f
+ VUSHLL2 $31, V30.S4, V2.D2 // c2a73f6f
+ VBIF V0.B8, V30.B8, V1.B8 // c11fe02e
+ VBIF V30.B16, V0.B16, V2.B16 // 021cfe6e
MOVD (R2)(R6.SXTW), R4 // 44c866f8
MOVD (R3)(R6), R5 // MOVD (R3)(R6*1), R5 // 656866f8
MOVD (R2)(R6), R4 // MOVD (R2)(R6*1), R4 // 446866f8
diff --git a/src/cmd/asm/internal/asm/testdata/arm64error.s b/src/cmd/asm/internal/asm/testdata/arm64error.s
index 2a911b4cce..20b1f3e9f0 100644
--- a/src/cmd/asm/internal/asm/testdata/arm64error.s
+++ b/src/cmd/asm/internal/asm/testdata/arm64error.s
@@ -345,4 +345,12 @@ TEXT errors(SB),$0
VUXTL V30.D2, V30.H8 // ERROR "operand mismatch"
VUXTL2 V20.B8, V21.H8 // ERROR "operand mismatch"
VUXTL V3.D2, V4.B8 // ERROR "operand mismatch"
+ VUZP1 V0.B8, V30.B8, V1.B16 // ERROR "operand mismatch"
+ VUZP2 V0.Q1, V30.Q1, V1.Q1 // ERROR "invalid arrangement"
+ VUSHLL $0, V30.D2, V30.H8 // ERROR "operand mismatch"
+ VUSHLL2 $0, V20.B8, V21.H8 // ERROR "operand mismatch"
+ VUSHLL $8, V30.B8, V30.H8 // ERROR "shift amount out of range"
+ VUSHLL2 $32, V30.S4, V2.D2 // ERROR "shift amount out of range"
+ VBIF V0.B8, V1.B8, V2.B16 // ERROR "operand mismatch"
+ VBIF V0.D2, V1.D2, V2.D2 // ERROR "invalid arrangement"
RET
diff --git a/src/cmd/internal/obj/arm64/a.out.go b/src/cmd/internal/obj/arm64/a.out.go
index ab065e07e5..2839da1437 100644
--- a/src/cmd/internal/obj/arm64/a.out.go
+++ b/src/cmd/internal/obj/arm64/a.out.go
@@ -954,6 +954,7 @@ const (
AVADD
AVADDP
AVAND
+ AVBIF
AVCMEQ
AVCNT
AVEOR
@@ -986,6 +987,12 @@ const (
AVEXT
AVRBIT
AVUSHR
+ AVUSHLL
+ AVUSHLL2
+ AVUXTL
+ AVUXTL2
+ AVUZP1
+ AVUZP2
AVSHL
AVSRI
AVBSL
@@ -994,8 +1001,6 @@ const (
AVZIP1
AVZIP2
AVCMTST
- AVUXTL
- AVUXTL2
ALAST
AB = obj.AJMP
ABL = obj.ACALL
diff --git a/src/cmd/internal/obj/arm64/anames.go b/src/cmd/internal/obj/arm64/anames.go
index 8961f04b0c..48c066abfd 100644
--- a/src/cmd/internal/obj/arm64/anames.go
+++ b/src/cmd/internal/obj/arm64/anames.go
@@ -461,6 +461,7 @@ var Anames = []string{
"VADD",
"VADDP",
"VAND",
+ "VBIF",
"VCMEQ",
"VCNT",
"VEOR",
@@ -493,6 +494,12 @@ var Anames = []string{
"VEXT",
"VRBIT",
"VUSHR",
+ "VUSHLL",
+ "VUSHLL2",
+ "VUXTL",
+ "VUXTL2",
+ "VUZP1",
+ "VUZP2",
"VSHL",
"VSRI",
"VBSL",
@@ -501,7 +508,5 @@ var Anames = []string{
"VZIP1",
"VZIP2",
"VCMTST",
- "VUXTL",
- "VUXTL2",
"LAST",
}
diff --git a/src/cmd/internal/obj/arm64/asm7.go b/src/cmd/internal/obj/arm64/asm7.go
index 7ce18d0f13..df4bbbbd35 100644
--- a/src/cmd/internal/obj/arm64/asm7.go
+++ b/src/cmd/internal/obj/arm64/asm7.go
@@ -480,6 +480,7 @@ var optab = []Optab{
{AVTBL, C_ARNG, C_NONE, C_LIST, C_ARNG, 100, 4, 0, 0, 0},
{AVUSHR, C_VCON, C_ARNG, C_NONE, C_ARNG, 95, 4, 0, 0, 0},
{AVZIP1, C_ARNG, C_ARNG, C_NONE, C_ARNG, 72, 4, 0, 0, 0},
+ {AVUSHLL, C_VCON, C_ARNG, C_NONE, C_ARNG, 102, 4, 0, 0, 0},
{AVUXTL, C_ARNG, C_NONE, C_NONE, C_ARNG, 102, 4, 0, 0, 0},
/* conditional operations */
@@ -2751,6 +2752,9 @@ func buildop(ctxt *obj.Link) {
oprangeset(AVBSL, t)
oprangeset(AVBIT, t)
oprangeset(AVCMTST, t)
+ oprangeset(AVUZP1, t)
+ oprangeset(AVUZP2, t)
+ oprangeset(AVBIF, t)
case AVADD:
oprangeset(AVSUB, t)
@@ -2801,6 +2805,9 @@ func buildop(ctxt *obj.Link) {
case AVUXTL:
oprangeset(AVUXTL2, t)
+ case AVUSHLL:
+ oprangeset(AVUSHLL2, t)
+
case AVLD1R:
oprangeset(AVLD2, t)
oprangeset(AVLD2R, t)
@@ -4177,7 +4184,7 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) {
rel.Add = 0
rel.Type = objabi.R_ARM64_GOTPCREL
- case 72: /* vaddp/vand/vcmeq/vorr/vadd/veor/vfmla/vfmls/vbit/vbsl/vcmtst/vsub Vm., Vn., Vd. */
+ case 72: /* vaddp/vand/vcmeq/vorr/vadd/veor/vfmla/vfmls/vbit/vbsl/vcmtst/vsub/vbif/vuzip1/vuzip2 Vm., Vn., Vd. */
af := int((p.From.Reg >> 5) & 15)
af3 := int((p.Reg >> 5) & 15)
at := int((p.To.Reg >> 5) & 15)
@@ -4219,7 +4226,7 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) {
}
switch p.As {
- case AVORR, AVAND, AVEOR, AVBIT, AVBSL:
+ case AVORR, AVAND, AVEOR, AVBIT, AVBSL, AVBIF:
if af != ARNG_16B && af != ARNG_8B {
c.ctxt.Diag("invalid arrangement: %v", p)
}
@@ -4233,7 +4240,7 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) {
size = 0
case AVBSL:
size = 1
- case AVORR, AVBIT:
+ case AVORR, AVBIT, AVBIF:
size = 2
case AVFMLA, AVFMLS:
if af == ARNG_2D {
@@ -5120,56 +5127,44 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) {
case 101: // FOMVQ/FMOVD $vcon, Vd -> load from constant pool.
o1 = c.omovlit(p.As, p, &p.From, int(p.To.Reg))
- case 102: // VUXTL{2} Vn., Vd.
- af := int((p.From.Reg >> 5) & 15)
- at := int((p.To.Reg >> 5) & 15)
- var Q, immh uint32
- switch at {
- case ARNG_8H:
- if af == ARNG_8B {
- immh = 1
- Q = 0
- } else if af == ARNG_16B {
- immh = 1
- Q = 1
- } else {
- c.ctxt.Diag("operand mismatch: %v\n", p)
- }
- case ARNG_4S:
- if af == ARNG_4H {
- immh = 2
- Q = 0
- } else if af == ARNG_8H {
- immh = 2
- Q = 1
- } else {
- c.ctxt.Diag("operand mismatch: %v\n", p)
- }
- case ARNG_2D:
- if af == ARNG_2S {
- immh = 4
- Q = 0
- } else if af == ARNG_4S {
- immh = 4
- Q = 1
- } else {
- c.ctxt.Diag("operand mismatch: %v\n", p)
- }
+ case 102: /* vushll, vushll2, vuxtl, vuxtl2 */
+ o1 = c.opirr(p, p.As)
+ rf := p.Reg
+ af := uint8((p.Reg >> 5) & 15)
+ at := uint8((p.To.Reg >> 5) & 15)
+ shift := int(p.From.Offset)
+ if p.As == AVUXTL || p.As == AVUXTL2 {
+ rf = p.From.Reg
+ af = uint8((p.From.Reg >> 5) & 15)
+ shift = 0
+ }
+
+ pack := func(q, x, y uint8) uint32 {
+ return uint32(q)<<16 | uint32(x)<<8 | uint32(y)
+ }
+
+ var Q uint8 = uint8(o1>>30) & 1
+ var immh, width uint8
+ switch pack(Q, af, at) {
+ case pack(0, ARNG_8B, ARNG_8H):
+ immh, width = 1, 8
+ case pack(1, ARNG_16B, ARNG_8H):
+ immh, width = 1, 8
+ case pack(0, ARNG_4H, ARNG_4S):
+ immh, width = 2, 16
+ case pack(1, ARNG_8H, ARNG_4S):
+ immh, width = 2, 16
+ case pack(0, ARNG_2S, ARNG_2D):
+ immh, width = 4, 32
+ case pack(1, ARNG_4S, ARNG_2D):
+ immh, width = 4, 32
default:
c.ctxt.Diag("operand mismatch: %v\n", p)
}
-
- if p.As == AVUXTL && Q == 1 {
- c.ctxt.Diag("operand mismatch: %v\n", p)
+ if !(0 <= shift && shift <= int(width-1)) {
+ c.ctxt.Diag("shift amount out of range: %v\n", p)
}
- if p.As == AVUXTL2 && Q == 0 {
- c.ctxt.Diag("operand mismatch: %v\n", p)
- }
-
- o1 = c.oprrr(p, p.As)
- rf := int((p.From.Reg) & 31)
- rt := int((p.To.Reg) & 31)
- o1 |= Q<<30 | immh<<19 | uint32((rf&31)<<5) | uint32(rt&31)
+ o1 |= uint32(immh)<<19 | uint32(shift)<<16 | uint32(rf&31)<<5 | uint32(p.To.Reg&31)
}
out[0] = o1
out[1] = o2
@@ -5802,6 +5797,9 @@ func (c *ctxt7) oprrr(p *obj.Prog, a obj.As) uint32 {
case AVLD2R, AVLD4R:
return 0xD<<24 | 3<<21
+ case AVBIF:
+ return 1<<29 | 7<<25 | 7<<21 | 7<<10
+
case AVBIT:
return 1<<29 | 0x75<<21 | 7<<10
@@ -5811,8 +5809,11 @@ func (c *ctxt7) oprrr(p *obj.Prog, a obj.As) uint32 {
case AVCMTST:
return 0xE<<24 | 1<<21 | 0x23<<10
- case AVUXTL, AVUXTL2:
- return 0x5e<<23 | 0x29<<10
+ case AVUZP1:
+ return 7<<25 | 3<<11
+
+ case AVUZP2:
+ return 7<<25 | 1<<14 | 3<<11
}
c.ctxt.Diag("%v: bad rrr %d %v", p, a, a)
@@ -6011,6 +6012,12 @@ func (c *ctxt7) opirr(p *obj.Prog, a obj.As) uint32 {
case AVSRI:
return 0x5E<<23 | 17<<10
+
+ case AVUSHLL, AVUXTL:
+ return 1<<29 | 15<<24 | 0x29<<10
+
+ case AVUSHLL2, AVUXTL2:
+ return 3<<29 | 15<<24 | 0x29<<10
}
c.ctxt.Diag("%v: bad irr %v", p, a)
--
cgit v1.3
From 03a686069191e3515c7f27f6d90b66d272e0e3a2 Mon Sep 17 00:00:00 2001
From: Jay Conrod
Date: Wed, 9 Sep 2020 15:58:05 -0400
Subject: cmd/go: update tests to work with -mod=readonly on by default
For #40728
Change-Id: Ic2b025ff75c6e73c0cb58c1737e44e2a41c71571
Reviewed-on: https://go-review.googlesource.com/c/go/+/253837
Run-TryBot: Jay Conrod
TryBot-Result: Gobot Gobot
Reviewed-by: Michael Matloob
Reviewed-by: Bryan C. Mills
---
.../mod/example.com_retract_missingmod_v1.0.0.txt | 2 ++
src/cmd/go/testdata/script/mod_auth.txt | 2 +-
src/cmd/go/testdata/script/mod_case.txt | 7 ++++++-
src/cmd/go/testdata/script/mod_concurrent.txt | 1 +
src/cmd/go/testdata/script/mod_doc.txt | 2 ++
src/cmd/go/testdata/script/mod_domain_root.txt | 2 +-
src/cmd/go/testdata/script/mod_download.txt | 2 +-
src/cmd/go/testdata/script/mod_download_partial.txt | 9 +++++++--
src/cmd/go/testdata/script/mod_get_incompatible.txt | 2 +-
src/cmd/go/testdata/script/mod_get_indirect.txt | 2 +-
.../go/testdata/script/mod_get_latest_pseudo.txt | 2 +-
.../go/testdata/script/mod_get_trailing_slash.txt | 3 +++
src/cmd/go/testdata/script/mod_import.txt | 2 +-
src/cmd/go/testdata/script/mod_in_testdata_dir.txt | 6 +++---
src/cmd/go/testdata/script/mod_init_dep.txt | 21 ++++-----------------
.../go/testdata/script/mod_install_versioned.txt | 2 ++
src/cmd/go/testdata/script/mod_internal.txt | 14 ++++++++++----
src/cmd/go/testdata/script/mod_invalid_version.txt | 1 +
src/cmd/go/testdata/script/mod_list.txt | 8 ++++----
src/cmd/go/testdata/script/mod_list_dir.txt | 5 +++++
src/cmd/go/testdata/script/mod_list_direct.txt | 2 +-
src/cmd/go/testdata/script/mod_list_replace_dir.txt | 12 ++++++++++--
src/cmd/go/testdata/script/mod_list_upgrade.txt | 4 ++++
src/cmd/go/testdata/script/mod_load_badchain.txt | 4 ++--
src/cmd/go/testdata/script/mod_load_badmod.txt | 7 +++----
src/cmd/go/testdata/script/mod_load_badzip.txt | 4 +---
.../testdata/script/mod_missingpkg_prerelease.txt | 2 +-
src/cmd/go/testdata/script/mod_modinfo.txt | 1 +
src/cmd/go/testdata/script/mod_multirepo.txt | 1 +
src/cmd/go/testdata/script/mod_notall.txt | 1 +
src/cmd/go/testdata/script/mod_permissions.txt | 2 +-
src/cmd/go/testdata/script/mod_query.txt | 10 ++++++++++
src/cmd/go/testdata/script/mod_replace.txt | 4 ++--
src/cmd/go/testdata/script/mod_replace_gopkgin.txt | 1 +
src/cmd/go/testdata/script/mod_replace_import.txt | 1 +
src/cmd/go/testdata/script/mod_require_exclude.txt | 8 ++++----
src/cmd/go/testdata/script/mod_retention.txt | 6 +++---
src/cmd/go/testdata/script/mod_retract.txt | 5 ++++-
src/cmd/go/testdata/script/mod_retract_replace.txt | 14 ++++++++++++--
src/cmd/go/testdata/script/mod_sum_lookup.txt | 5 +++--
src/cmd/go/testdata/script/mod_sumdb_golang.txt | 4 ++--
src/cmd/go/testdata/script/mod_symlink.txt | 5 ++++-
src/cmd/go/testdata/script/mod_test.txt | 1 +
src/cmd/go/testdata/script/mod_tidy_replace.txt | 1 +
src/cmd/go/testdata/script/mod_upgrade_patch.txt | 1 +
src/cmd/go/testdata/script/mod_vcs_missing.txt | 4 ++--
src/cmd/go/testdata/script/mod_vendor_build.txt | 3 +++
src/cmd/go/testdata/script/mod_verify.txt | 2 +-
src/cmd/go/testdata/script/mod_why.txt | 3 +++
src/cmd/go/testdata/script/modfile_flag.txt | 6 +++---
src/cmd/go/testdata/script/version.txt | 1 +
src/cmd/go/testdata/script/version_replace.txt | 2 +-
52 files changed, 146 insertions(+), 76 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/go/testdata/mod/example.com_retract_missingmod_v1.0.0.txt b/src/cmd/go/testdata/mod/example.com_retract_missingmod_v1.0.0.txt
index 2023c7b096..1d8d81071e 100644
--- a/src/cmd/go/testdata/mod/example.com_retract_missingmod_v1.0.0.txt
+++ b/src/cmd/go/testdata/mod/example.com_retract_missingmod_v1.0.0.txt
@@ -6,3 +6,5 @@ module example.com/retract/missingmod
go 1.14
-- .info --
{"Version":"v1.0.0"}
+-- missingmod.go --
+package missingmod
diff --git a/src/cmd/go/testdata/script/mod_auth.txt b/src/cmd/go/testdata/script/mod_auth.txt
index 5bcbcd1a18..544acbc1f8 100644
--- a/src/cmd/go/testdata/script/mod_auth.txt
+++ b/src/cmd/go/testdata/script/mod_auth.txt
@@ -7,7 +7,7 @@ env GOSUMDB=off
# Without credentials, downloading a module from a path that requires HTTPS
# basic auth should fail.
env NETRC=$WORK/empty
-! go list all
+! go mod tidy
stderr '^\tserver response: ACCESS DENIED, buddy$'
stderr '^\tserver response: File\? What file\?$'
diff --git a/src/cmd/go/testdata/script/mod_case.txt b/src/cmd/go/testdata/script/mod_case.txt
index ee818c2c07..6f8d869c44 100644
--- a/src/cmd/go/testdata/script/mod_case.txt
+++ b/src/cmd/go/testdata/script/mod_case.txt
@@ -1,6 +1,6 @@
env GO111MODULE=on
-go get rsc.io/QUOTE
+go get -d
go list -m all
stdout '^rsc.io/quote v1.5.2'
stdout '^rsc.io/QUOTE v1.5.2'
@@ -18,3 +18,8 @@ stdout '!q!u!o!t!e@v1.5.3-!p!r!e'
-- go.mod --
module x
+
+-- use.go --
+package use
+
+import _ "rsc.io/QUOTE/QUOTE"
diff --git a/src/cmd/go/testdata/script/mod_concurrent.txt b/src/cmd/go/testdata/script/mod_concurrent.txt
index e03e5e5edb..8c21525158 100644
--- a/src/cmd/go/testdata/script/mod_concurrent.txt
+++ b/src/cmd/go/testdata/script/mod_concurrent.txt
@@ -1,6 +1,7 @@
env GO111MODULE=on
# Concurrent builds should succeed, even if they need to download modules.
+go get -d ./x ./y
go build ./x &
go build ./y
wait
diff --git a/src/cmd/go/testdata/script/mod_doc.txt b/src/cmd/go/testdata/script/mod_doc.txt
index aac3db00be..595ad679fc 100644
--- a/src/cmd/go/testdata/script/mod_doc.txt
+++ b/src/cmd/go/testdata/script/mod_doc.txt
@@ -1,6 +1,7 @@
# go doc should find module documentation
env GO111MODULE=on
+env GOFLAGS=-mod=mod
[short] skip
# Check when module x is inside GOPATH/src.
@@ -48,6 +49,7 @@ stderr '^doc: cannot find module providing package example.com/hello: module loo
# path used in source code, not to the absolute path relative to GOROOT.
cd $GOROOT/src
+env GOFLAGS=
go doc cryptobyte
stdout '// import "golang.org/x/crypto/cryptobyte"'
diff --git a/src/cmd/go/testdata/script/mod_domain_root.txt b/src/cmd/go/testdata/script/mod_domain_root.txt
index e34cc29fa6..14745b5812 100644
--- a/src/cmd/go/testdata/script/mod_domain_root.txt
+++ b/src/cmd/go/testdata/script/mod_domain_root.txt
@@ -2,7 +2,7 @@
# (example.com not example.com/something)
env GO111MODULE=on
-go build
+go get -d
-- go.mod --
module x
diff --git a/src/cmd/go/testdata/script/mod_download.txt b/src/cmd/go/testdata/script/mod_download.txt
index 5acb83266b..b9bf67cad5 100644
--- a/src/cmd/go/testdata/script/mod_download.txt
+++ b/src/cmd/go/testdata/script/mod_download.txt
@@ -46,7 +46,7 @@ go mod edit -require rsc.io/quote@v1.5.3-pre1
! exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.3-pre1.zip
# module loading will page in the info and mod files
-go list -m all
+go list -m -mod=mod all
exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.3-pre1.info
exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.3-pre1.mod
! exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.3-pre1.zip
diff --git a/src/cmd/go/testdata/script/mod_download_partial.txt b/src/cmd/go/testdata/script/mod_download_partial.txt
index 4978982dab..8d31970160 100644
--- a/src/cmd/go/testdata/script/mod_download_partial.txt
+++ b/src/cmd/go/testdata/script/mod_download_partial.txt
@@ -1,5 +1,5 @@
-# Download a module
-go mod download -modcacherw rsc.io/quote
+# Download modules and populate go.sum.
+go get -d -modcacherw
exists $GOPATH/pkg/mod/rsc.io/quote@v1.5.2/go.mod
# 'go mod verify' should fail if we delete a file.
@@ -61,4 +61,9 @@ go 1.14
require rsc.io/quote v1.5.2
+-- use.go --
+package use
+
+import _ "rsc.io/quote"
+
-- empty --
diff --git a/src/cmd/go/testdata/script/mod_get_incompatible.txt b/src/cmd/go/testdata/script/mod_get_incompatible.txt
index b210715a5d..b28718a694 100644
--- a/src/cmd/go/testdata/script/mod_get_incompatible.txt
+++ b/src/cmd/go/testdata/script/mod_get_incompatible.txt
@@ -1,6 +1,6 @@
env GO111MODULE=on
-go list x
+go get -d x
go list -m all
stdout 'rsc.io/breaker v2.0.0\+incompatible'
diff --git a/src/cmd/go/testdata/script/mod_get_indirect.txt b/src/cmd/go/testdata/script/mod_get_indirect.txt
index f25e170a49..e1cc1ab411 100644
--- a/src/cmd/go/testdata/script/mod_get_indirect.txt
+++ b/src/cmd/go/testdata/script/mod_get_indirect.txt
@@ -27,7 +27,7 @@ grep 'golang.org/x/text v0.3.0 // indirect$' go.mod
# indirect tag should be removed upon seeing direct import.
cp $WORK/tmp/uselang.go x.go
-go list
+go get -d
grep 'rsc.io/quote v1.5.2$' go.mod
grep 'golang.org/x/text [v0-9a-f\.-]+$' go.mod
diff --git a/src/cmd/go/testdata/script/mod_get_latest_pseudo.txt b/src/cmd/go/testdata/script/mod_get_latest_pseudo.txt
index 825ee8cf89..241a0c2f0d 100644
--- a/src/cmd/go/testdata/script/mod_get_latest_pseudo.txt
+++ b/src/cmd/go/testdata/script/mod_get_latest_pseudo.txt
@@ -5,6 +5,6 @@
env GO111MODULE=on
go mod init m
-go list example.com/notags
+go get -d example.com/notags
go list -m all
stdout '^example.com/notags v0.0.0-20190507143103-cc8cbe209b64$'
diff --git a/src/cmd/go/testdata/script/mod_get_trailing_slash.txt b/src/cmd/go/testdata/script/mod_get_trailing_slash.txt
index 7b5d90c50b..3b38d8ba7d 100644
--- a/src/cmd/go/testdata/script/mod_get_trailing_slash.txt
+++ b/src/cmd/go/testdata/script/mod_get_trailing_slash.txt
@@ -1,3 +1,6 @@
+# Populate go.sum
+go mod download
+
# go list should succeed to load a package ending with ".go" if the path does
# not correspond to an existing local file. Listing a pattern ending with
# ".go/" should try to list a package regardless of whether a file exists at the
diff --git a/src/cmd/go/testdata/script/mod_import.txt b/src/cmd/go/testdata/script/mod_import.txt
index 3985b43144..28358b5b0c 100644
--- a/src/cmd/go/testdata/script/mod_import.txt
+++ b/src/cmd/go/testdata/script/mod_import.txt
@@ -1,7 +1,7 @@
env GO111MODULE=on
# latest rsc.io/quote should be v1.5.2 not v1.5.3-pre1
-go list
+go get -d
go list -m all
stdout 'rsc.io/quote v1.5.2'
diff --git a/src/cmd/go/testdata/script/mod_in_testdata_dir.txt b/src/cmd/go/testdata/script/mod_in_testdata_dir.txt
index f582569798..66f79faa6d 100644
--- a/src/cmd/go/testdata/script/mod_in_testdata_dir.txt
+++ b/src/cmd/go/testdata/script/mod_in_testdata_dir.txt
@@ -8,8 +8,8 @@ env GO111MODULE=on
cd $WORK/testdata
go mod init testdata.tld/foo
-# Building a package within that module should resolve its dependencies.
-go build
+# Getting a package within that module should resolve its dependencies.
+go get -d
grep 'rsc.io/quote' go.mod
# Tidying the module should preserve those dependencies.
@@ -26,7 +26,7 @@ exists vendor/rsc.io/quote
cd $WORK/_ignored
go mod init testdata.tld/foo
-go build
+go get
grep 'rsc.io/quote' go.mod
go mod tidy
diff --git a/src/cmd/go/testdata/script/mod_init_dep.txt b/src/cmd/go/testdata/script/mod_init_dep.txt
index 755076eae8..f8cf1d563a 100644
--- a/src/cmd/go/testdata/script/mod_init_dep.txt
+++ b/src/cmd/go/testdata/script/mod_init_dep.txt
@@ -1,24 +1,14 @@
env GO111MODULE=on
+env GOFLAGS=-mod=mod
# modconv uses git directly to examine what old 'go get' would
[!net] skip
[!exec:git] skip
-# go build should populate go.mod from Gopkg.lock
-cp go.mod1 go.mod
-go build
+# go mod init should populate go.mod from Gopkg.lock
+go mod init x
stderr 'copying requirements from Gopkg.lock'
go list -m all
-! stderr 'copying requirements from Gopkg.lock'
-stdout 'rsc.io/sampler v1.0.0'
-
-# go list should populate go.mod from Gopkg.lock
-cp go.mod1 go.mod
-go list
-stderr 'copying requirements from Gopkg.lock'
-go list
-! stderr 'copying requirements from Gopkg.lock'
-go list -m all
stdout 'rsc.io/sampler v1.0.0'
# test dep replacement
@@ -26,9 +16,6 @@ cd y
go mod init
cmpenv go.mod go.mod.replace
--- go.mod1 --
-module x
-
-- x.go --
package x
@@ -54,4 +41,4 @@ go $goversion
replace z v1.0.0 => rsc.io/quote v1.0.0
-require rsc.io/quote v1.0.0
\ No newline at end of file
+require rsc.io/quote v1.0.0
diff --git a/src/cmd/go/testdata/script/mod_install_versioned.txt b/src/cmd/go/testdata/script/mod_install_versioned.txt
index 03986d06a0..c6bce418b4 100644
--- a/src/cmd/go/testdata/script/mod_install_versioned.txt
+++ b/src/cmd/go/testdata/script/mod_install_versioned.txt
@@ -1,9 +1,11 @@
env GO111MODULE=on
+go get -d rsc.io/fortune
go list -f '{{.Target}}' rsc.io/fortune
! stdout fortune@v1
stdout 'fortune(\.exe)?$'
+go get -d rsc.io/fortune/v2
go list -f '{{.Target}}' rsc.io/fortune/v2
! stdout v2
stdout 'fortune(\.exe)?$'
diff --git a/src/cmd/go/testdata/script/mod_internal.txt b/src/cmd/go/testdata/script/mod_internal.txt
index 1193d528ec..687269d18f 100644
--- a/src/cmd/go/testdata/script/mod_internal.txt
+++ b/src/cmd/go/testdata/script/mod_internal.txt
@@ -3,30 +3,34 @@ env GO111MODULE=on
# golang.org/x/internal should be importable from other golang.org/x modules.
go mod edit -module=golang.org/x/anything
-go build .
+go get -d .
# ...and their tests...
go test
stdout PASS
# ...but that should not leak into other modules.
+go get -d ./baddep
! go build ./baddep
stderr golang.org[/\\]notx[/\\]useinternal
stderr 'use of internal package golang.org/x/.* not allowed'
# Internal packages in the standard library should not leak into modules.
+go get -d ./fromstd
! go build ./fromstd
stderr 'use of internal package internal/testenv not allowed'
# Dependencies should be able to use their own internal modules...
go mod edit -module=golang.org/notx
-go build ./throughdep
+go get -d ./throughdep
# ... but other modules should not, even if they have transitive dependencies.
+go get -d .
! go build .
stderr 'use of internal package golang.org/x/.* not allowed'
# And transitive dependencies still should not leak.
+go get -d ./baddep
! go build ./baddep
stderr golang.org[/\\]notx[/\\]useinternal
stderr 'use of internal package golang.org/x/.* not allowed'
@@ -34,15 +38,17 @@ stderr 'use of internal package golang.org/x/.* not allowed'
# Replacing an internal module should keep it internal to the same paths.
go mod edit -module=golang.org/notx
go mod edit -replace golang.org/x/internal=./replace/golang.org/notx/internal
-go build ./throughdep
+go get -d ./throughdep
+go get -d ./baddep
! go build ./baddep
stderr golang.org[/\\]notx[/\\]useinternal
stderr 'use of internal package golang.org/x/.* not allowed'
go mod edit -replace golang.org/x/internal=./vendor/golang.org/x/internal
-go build ./throughdep
+go get -d ./throughdep
+go get -d ./baddep
! go build ./baddep
stderr golang.org[/\\]notx[/\\]useinternal
stderr 'use of internal package golang.org/x/.* not allowed'
diff --git a/src/cmd/go/testdata/script/mod_invalid_version.txt b/src/cmd/go/testdata/script/mod_invalid_version.txt
index 6dddd4b036..f9dfdd6346 100644
--- a/src/cmd/go/testdata/script/mod_invalid_version.txt
+++ b/src/cmd/go/testdata/script/mod_invalid_version.txt
@@ -4,6 +4,7 @@
env GO111MODULE=on
env GOPROXY=direct
env GOSUMDB=off
+env GOFLAGS=-mod=mod
# Regression test for golang.org/issue/27173: if the user (or go.mod file)
# requests a pseudo-version that does not match both the module path and commit
diff --git a/src/cmd/go/testdata/script/mod_list.txt b/src/cmd/go/testdata/script/mod_list.txt
index 17b33fcc7b..1ba6d7c910 100644
--- a/src/cmd/go/testdata/script/mod_list.txt
+++ b/src/cmd/go/testdata/script/mod_list.txt
@@ -2,12 +2,12 @@ env GO111MODULE=on
[short] skip
# list {{.Dir}} shows main module and go.mod but not not-yet-downloaded dependency dir.
-go list -m -f '{{.Path}} {{.Main}} {{.GoMod}} {{.Dir}}' all
+go list -mod=mod -m -f '{{.Path}} {{.Main}} {{.GoMod}} {{.Dir}}' all
stdout '^x true .*[\\/]src[\\/]go.mod .*[\\/]src$'
stdout '^rsc.io/quote false .*[\\/]v1.5.2.mod $'
# list {{.Dir}} shows dependency after download (and go list without -m downloads it)
-go list -f '{{.Dir}}' rsc.io/quote
+go list -mod=mod -f '{{.Dir}}' rsc.io/quote
stdout '.*mod[\\/]rsc.io[\\/]quote@v1.5.2$'
# downloaded dependencies are read-only
@@ -20,7 +20,7 @@ go clean -modcache
# list {{.Dir}} shows replaced directories
cp go.mod2 go.mod
-go list -f {{.Dir}} rsc.io/quote
+go list -mod=mod -f {{.Dir}} rsc.io/quote
go list -m -f '{{.Path}} {{.Version}} {{.Dir}}{{with .Replace}} {{.GoMod}} => {{.Version}} {{.Dir}} {{.GoMod}}{{end}}' all
stdout 'mod[\\/]rsc.io[\\/]quote@v1.5.1'
stdout 'v1.3.0.*mod[\\/]rsc.io[\\/]sampler@v1.3.1 .*[\\/]v1.3.1.mod => v1.3.1.*sampler@v1.3.1 .*[\\/]v1.3.1.mod'
@@ -30,7 +30,7 @@ go list std
stdout ^math/big
# rsc.io/quote/buggy should be listable as a package
-go list rsc.io/quote/buggy
+go list -mod=mod rsc.io/quote/buggy
# rsc.io/quote/buggy should not be listable as a module
go list -m -e -f '{{.Error.Err}}' nonexist rsc.io/quote/buggy
diff --git a/src/cmd/go/testdata/script/mod_list_dir.txt b/src/cmd/go/testdata/script/mod_list_dir.txt
index 6653435a06..1adab8f027 100644
--- a/src/cmd/go/testdata/script/mod_list_dir.txt
+++ b/src/cmd/go/testdata/script/mod_list_dir.txt
@@ -2,6 +2,9 @@
# go list with path to directory should work
+# populate go.sum
+go get -d
+
env GO111MODULE=off
go list -f '{{.ImportPath}}' $GOROOT/src/math
stdout ^math$
@@ -29,3 +32,5 @@ require rsc.io/quote v1.5.2
-- x.go --
package x
+
+import _ "rsc.io/quote"
diff --git a/src/cmd/go/testdata/script/mod_list_direct.txt b/src/cmd/go/testdata/script/mod_list_direct.txt
index 8f85871189..62a472f475 100644
--- a/src/cmd/go/testdata/script/mod_list_direct.txt
+++ b/src/cmd/go/testdata/script/mod_list_direct.txt
@@ -10,7 +10,7 @@ env GOSUMDB=off
# For a while, (*modfetch.codeRepo).Stat was not checking for a go.mod file,
# which would produce a hard error at the subsequent call to GoMod.
-go list all
+go get -d
-- go.mod --
module example.com
diff --git a/src/cmd/go/testdata/script/mod_list_replace_dir.txt b/src/cmd/go/testdata/script/mod_list_replace_dir.txt
index cad7fe2528..f2f2d2b2bb 100644
--- a/src/cmd/go/testdata/script/mod_list_replace_dir.txt
+++ b/src/cmd/go/testdata/script/mod_list_replace_dir.txt
@@ -2,8 +2,11 @@
# module within the module cache.
# Verifies golang.org/issue/29548
-env GO111MODULE=on
-go mod download rsc.io/quote@v1.5.1 rsc.io/quote@v1.5.2
+# Populate go.sum and download dependencies.
+go get -d
+
+# Ensure v1.5.2 is also in the cache so we can list it.
+go mod download rsc.io/quote@v1.5.2
! go list $GOPATH/pkg/mod/rsc.io/quote@v1.5.2
stderr '^directory ..[/\\]pkg[/\\]mod[/\\]rsc.io[/\\]quote@v1.5.2 outside available modules$'
@@ -17,3 +20,8 @@ module example.com/quoter
require rsc.io/quote v1.5.2
replace rsc.io/quote => rsc.io/quote v1.5.1
+
+-- use.go --
+package use
+
+import _ "rsc.io/quote"
diff --git a/src/cmd/go/testdata/script/mod_list_upgrade.txt b/src/cmd/go/testdata/script/mod_list_upgrade.txt
index 474df0dc26..0cef04b89a 100644
--- a/src/cmd/go/testdata/script/mod_list_upgrade.txt
+++ b/src/cmd/go/testdata/script/mod_list_upgrade.txt
@@ -1,5 +1,9 @@
env GO111MODULE=on
+# Populate go.sum
+go list -m -mod=mod all
+
+# Check for upgrades.
go list -m -u all
stdout 'rsc.io/quote v1.2.0 \[v1\.5\.2\]'
diff --git a/src/cmd/go/testdata/script/mod_load_badchain.txt b/src/cmd/go/testdata/script/mod_load_badchain.txt
index 67d9a1584f..e943179c54 100644
--- a/src/cmd/go/testdata/script/mod_load_badchain.txt
+++ b/src/cmd/go/testdata/script/mod_load_badchain.txt
@@ -28,10 +28,10 @@ cmp stderr list-expected
# Try listing a package that imports a package
# in a module without a requirement.
go mod edit -droprequire example.com/badchain/a
-! go list m/use
+! go list -mod=mod m/use
cmp stderr list-missing-expected
-! go list -test m/testuse
+! go list -mod=mod -test m/testuse
cmp stderr list-missing-test-expected
-- go.mod.orig --
diff --git a/src/cmd/go/testdata/script/mod_load_badmod.txt b/src/cmd/go/testdata/script/mod_load_badmod.txt
index 68c8b3792b..fa22e1808b 100644
--- a/src/cmd/go/testdata/script/mod_load_badmod.txt
+++ b/src/cmd/go/testdata/script/mod_load_badmod.txt
@@ -1,14 +1,13 @@
# Unknown lines should be ignored in dependency go.mod files.
-env GO111MODULE=on
-go list -m all
+go list -m -mod=mod all
# ... and in replaced dependency go.mod files.
cp go.mod go.mod.usesub
-go list -m all
+go list -m -mod=mod all
# ... but not in the main module.
cp go.mod.bad go.mod
-! go list -m all
+! go list -m -mod=mod all
stderr 'unknown directive: hello'
-- go.mod --
diff --git a/src/cmd/go/testdata/script/mod_load_badzip.txt b/src/cmd/go/testdata/script/mod_load_badzip.txt
index c5ba18e9f0..65374d2a6d 100644
--- a/src/cmd/go/testdata/script/mod_load_badzip.txt
+++ b/src/cmd/go/testdata/script/mod_load_badzip.txt
@@ -5,10 +5,8 @@ env GO111MODULE=on
stderr 'zip for rsc.io/badzip@v1.0.0 has unexpected file rsc.io/badzip@v1.0.0.txt'
! grep rsc.io/badzip go.mod
-# TODO(golang.org/issue/31730): 'go build' should print the error below if the
-# requirement is not present.
go mod edit -require rsc.io/badzip@v1.0.0
-! go build rsc.io/badzip
+! go build -mod=mod rsc.io/badzip
stderr 'zip for rsc.io/badzip@v1.0.0 has unexpected file rsc.io/badzip@v1.0.0.txt'
-- go.mod --
diff --git a/src/cmd/go/testdata/script/mod_missingpkg_prerelease.txt b/src/cmd/go/testdata/script/mod_missingpkg_prerelease.txt
index 1ba8d3d22a..9c250e7d1c 100644
--- a/src/cmd/go/testdata/script/mod_missingpkg_prerelease.txt
+++ b/src/cmd/go/testdata/script/mod_missingpkg_prerelease.txt
@@ -1,6 +1,6 @@
env GO111MODULE=on
-! go list -deps use.go
+! go list -mod=mod -deps use.go
stderr '^use.go:4:2: package example.com/missingpkg/deprecated provided by example.com/missingpkg at latest version v1.0.0 but not at required version v1.0.1-beta$'
-- go.mod --
diff --git a/src/cmd/go/testdata/script/mod_modinfo.txt b/src/cmd/go/testdata/script/mod_modinfo.txt
index fb31f9e43b..d9e9fdec21 100644
--- a/src/cmd/go/testdata/script/mod_modinfo.txt
+++ b/src/cmd/go/testdata/script/mod_modinfo.txt
@@ -6,6 +6,7 @@ env GO111MODULE=on
cd x
go mod edit -require=rsc.io/quote@v1.5.2
go mod edit -replace=rsc.io/quote@v1.5.2=rsc.io/quote@v1.0.0
+go mod tidy # populate go.sum
# Build a binary and ensure that it can output its own debug info.
# The debug info should be accessible before main starts (golang.org/issue/29628).
diff --git a/src/cmd/go/testdata/script/mod_multirepo.txt b/src/cmd/go/testdata/script/mod_multirepo.txt
index 7f977e80f6..0f335a11f0 100644
--- a/src/cmd/go/testdata/script/mod_multirepo.txt
+++ b/src/cmd/go/testdata/script/mod_multirepo.txt
@@ -7,6 +7,7 @@ go list -deps -f {{.Dir}}
# v2 import should use a downloaded module
# both without an explicit go.mod entry ...
cp tmp/use_v2.go x.go
+go get -d .
go list -deps -f {{.Dir}}
stdout 'pkg[\\/]mod[\\/]rsc.io[\\/]quote[\\/]v2@v2.0.1$'
diff --git a/src/cmd/go/testdata/script/mod_notall.txt b/src/cmd/go/testdata/script/mod_notall.txt
index 29ca6066fa..1657c8d2d0 100644
--- a/src/cmd/go/testdata/script/mod_notall.txt
+++ b/src/cmd/go/testdata/script/mod_notall.txt
@@ -5,6 +5,7 @@
# module, but not should not include test dependencies of packages imported only
# by other root patterns.
+env GOFLAGS=-mod=mod
cp go.mod go.mod.orig
go list -deps all x/otherroot
diff --git a/src/cmd/go/testdata/script/mod_permissions.txt b/src/cmd/go/testdata/script/mod_permissions.txt
index 11fb4754f8..2d32dcd10f 100644
--- a/src/cmd/go/testdata/script/mod_permissions.txt
+++ b/src/cmd/go/testdata/script/mod_permissions.txt
@@ -12,7 +12,7 @@ chmod 0640 go.mod
chmod 0604 go.sum
go mod edit -module=golang.org/issue/34634
-go build .
+go get -d
cmp go.mod go.mod.want
cmp go.sum go.sum.want
diff --git a/src/cmd/go/testdata/script/mod_query.txt b/src/cmd/go/testdata/script/mod_query.txt
index e87ca302f0..e10185709d 100644
--- a/src/cmd/go/testdata/script/mod_query.txt
+++ b/src/cmd/go/testdata/script/mod_query.txt
@@ -1,5 +1,10 @@
env GO111MODULE=on
+# Populate go.sum.
+# TODO(golang.org/issue/41297): we shouldn't need go.sum. None of the commands
+# below depend on the build list.
+go mod download
+
go list -m -versions rsc.io/quote
stdout '^rsc.io/quote v1.0.0 v1.1.0 v1.2.0 v1.2.1 v1.3.0 v1.4.0 v1.5.0 v1.5.1 v1.5.2 v1.5.3-pre1$'
@@ -30,3 +35,8 @@ stdout 'no matching versions for query ">v1.5.3"'
-- go.mod --
module x
require rsc.io/quote v1.0.0
+
+-- use.go --
+package use
+
+import _ "rsc.io/quote"
diff --git a/src/cmd/go/testdata/script/mod_replace.txt b/src/cmd/go/testdata/script/mod_replace.txt
index c21f172002..dc9667f1d0 100644
--- a/src/cmd/go/testdata/script/mod_replace.txt
+++ b/src/cmd/go/testdata/script/mod_replace.txt
@@ -4,7 +4,7 @@ env GO111MODULE=on
cp go.mod go.mod.orig
# Make sure the test builds without replacement.
-go build -o a1.exe .
+go build -mod=mod -o a1.exe .
exec ./a1.exe
stdout 'Don''t communicate by sharing memory'
@@ -32,7 +32,7 @@ stderr 'rsc.io/quote/v3@v3.0.0 used for two different module paths \(not-rsc.io/
# Modules that do not (yet) exist upstream can be replaced too.
cp go.mod.orig go.mod
go mod edit -replace=not-rsc.io/quote/v3@v3.1.0=./local/rsc.io/quote/v3
-go build -o a5.exe ./usenewmodule
+go build -mod=mod -o a5.exe ./usenewmodule
! stderr 'finding not-rsc.io/quote/v3'
grep 'not-rsc.io/quote/v3 v3.1.0' go.mod
exec ./a5.exe
diff --git a/src/cmd/go/testdata/script/mod_replace_gopkgin.txt b/src/cmd/go/testdata/script/mod_replace_gopkgin.txt
index 674c99cb0c..df752d9716 100644
--- a/src/cmd/go/testdata/script/mod_replace_gopkgin.txt
+++ b/src/cmd/go/testdata/script/mod_replace_gopkgin.txt
@@ -11,6 +11,7 @@
env GO111MODULE=on
env GOPROXY=direct
env GOSUMDB=off
+env GOFLAGS=-mod=mod
# Replacing gopkg.in/[…].vN with a repository with a root go.mod file
# specifying […].vN and a compatible version should succeed, even if
diff --git a/src/cmd/go/testdata/script/mod_replace_import.txt b/src/cmd/go/testdata/script/mod_replace_import.txt
index 54b1a12448..b4de5c50f7 100644
--- a/src/cmd/go/testdata/script/mod_replace_import.txt
+++ b/src/cmd/go/testdata/script/mod_replace_import.txt
@@ -7,6 +7,7 @@ cp go.mod go.mod.orig
cmp go.mod go.mod.orig
# 'go list' should resolve imports using replacements.
+go get -d
go list all
stdout 'example.com/a/b$'
stdout 'example.com/x/v3$'
diff --git a/src/cmd/go/testdata/script/mod_require_exclude.txt b/src/cmd/go/testdata/script/mod_require_exclude.txt
index 1a0fc3097b..9156d4ce5d 100644
--- a/src/cmd/go/testdata/script/mod_require_exclude.txt
+++ b/src/cmd/go/testdata/script/mod_require_exclude.txt
@@ -20,7 +20,7 @@ cmp go.mod go.mod.orig
# With the selected version excluded, commands that load only modules should
# drop the excluded module.
-go list -m all
+go list -m -mod=mod all
stderr '^go: dropping requirement on excluded version rsc.io/sampler v1\.99\.99$'
stdout '^x$'
! stdout '^rsc.io/sampler'
@@ -30,7 +30,7 @@ cmp go.mod go.moddrop
# from the next-highest version.
cp go.mod.orig go.mod
-go list -f '{{with .Module}}{{.Path}} {{.Version}}{{end}}' all
+go list -mod=mod -f '{{with .Module}}{{.Path}} {{.Version}}{{end}}' all
stderr '^go: dropping requirement on excluded version rsc.io/sampler v1\.99\.99$'
stdout '^x $'
! stdout '^rsc.io/sampler v1.99.99'
@@ -38,13 +38,13 @@ stdout '^rsc.io/sampler v1.3.0'
# build with newer version available
cp go.mod2 go.mod
-go list -f '{{with .Module}}{{.Path}} {{.Version}}{{end}}' all
+go list -mod=mod -f '{{with .Module}}{{.Path}} {{.Version}}{{end}}' all
stderr '^go: dropping requirement on excluded version rsc.io/quote v1\.5\.1$'
stdout 'rsc.io/quote v1.5.2'
# build with excluded newer version
cp go.mod3 go.mod
-go list -f '{{with .Module}}{{.Path}} {{.Version}}{{end}}' all
+go list -mod=mod -f '{{with .Module}}{{.Path}} {{.Version}}{{end}}' all
! stderr '^go: dropping requirement'
stdout 'rsc.io/quote v1.5.1'
diff --git a/src/cmd/go/testdata/script/mod_retention.txt b/src/cmd/go/testdata/script/mod_retention.txt
index 1d83e6c07e..a4441c4b3c 100644
--- a/src/cmd/go/testdata/script/mod_retention.txt
+++ b/src/cmd/go/testdata/script/mod_retention.txt
@@ -7,7 +7,7 @@ env GO111MODULE=on
# Control case: verify that go.mod.tidy is actually tidy.
cp go.mod.tidy go.mod
-go list all
+go list -mod=mod all
cmp go.mod go.mod.tidy
@@ -35,7 +35,7 @@ cmp go.mod go.mod.tidy
# "// indirect" comments should be removed if direct dependencies are seen.
# changes.
cp go.mod.indirect go.mod
-go list all
+go list -mod=mod all
cmp go.mod go.mod.tidy
# "// indirect" comments should be added if appropriate.
@@ -63,7 +63,7 @@ cmp go.mod go.mod.tidy
# A missing "go" version directive should be added.
# However, that should not remove other redundant requirements.
cp go.mod.nogo go.mod
-go list all
+go list -mod=mod all
cmpenv go.mod go.mod.currentgo
diff --git a/src/cmd/go/testdata/script/mod_retract.txt b/src/cmd/go/testdata/script/mod_retract.txt
index 5d21902043..a52e05bc72 100644
--- a/src/cmd/go/testdata/script/mod_retract.txt
+++ b/src/cmd/go/testdata/script/mod_retract.txt
@@ -1,5 +1,8 @@
cp go.mod go.mod.orig
+# Populate go.sum.
+go mod download
+
# 'go list pkg' does not report an error when a retracted version is used.
go list -e -f '{{if .Error}}{{.Error}}{{end}}' ./use
! stdout .
@@ -17,7 +20,7 @@ exists $GOPATH/pkg/mod/cache/download/example.com/retract/@v/v1.0.0-bad.mod
# Importing a package from a module with a retracted latest version will
# select the latest non-retracted version.
-go list ./use_self_prev
+go get -d ./use_self_prev
go list -m example.com/retract/self/prev
stdout '^example.com/retract/self/prev v1.1.0$'
exists $GOPATH/pkg/mod/cache/download/example.com/retract/self/prev/@v/v1.9.0.mod
diff --git a/src/cmd/go/testdata/script/mod_retract_replace.txt b/src/cmd/go/testdata/script/mod_retract_replace.txt
index b710485fa7..7aec438dda 100644
--- a/src/cmd/go/testdata/script/mod_retract_replace.txt
+++ b/src/cmd/go/testdata/script/mod_retract_replace.txt
@@ -1,6 +1,9 @@
# If the latest unretracted version of a module is replaced, 'go list' should
# obtain retractions from the replacement.
+# Populate go.sum.
+go get -d
+
# The latest version, v1.9.0, is not available on the proxy.
! go list -m -retracted example.com/retract/missingmod
stderr '^go list -m: loading module retractions: example.com/retract/missingmod@v1.9.0:.*404 Not Found$'
@@ -24,9 +27,9 @@ go list -m -retracted -f '{{range .Retracted}}{{.}}{{end}}' example.com/retract
go list -m -retracted -f '{{if .Replace}}replaced{{end}}' example.com/retract
! stdout .
go mod edit -replace example.com/retract@v1.0.0-good=example.com/retract@v1.0.0-bad
-go list -m -retracted -f '{{range .Retracted}}{{.}}{{end}}' example.com/retract
+go list -m -mod=mod -retracted -f '{{range .Retracted}}{{.}}{{end}}' example.com/retract
stdout '^bad$'
-go list -m -retracted -f '{{with .Replace}}{{range .Retracted}}{{.}}{{end}}{{end}}' example.com/retract
+go list -m -mod=mod -retracted -f '{{with .Replace}}{{range .Retracted}}{{.}}{{end}}{{end}}' example.com/retract
stdout '^bad$'
-- go.mod --
@@ -38,6 +41,13 @@ require (
example.com/retract v1.0.0-good
example.com/retract/missingmod v1.0.0
)
+-- use.go --
+package use
+
+import (
+ _ "example.com/retract"
+ _ "example.com/retract/missingmod"
+)
-- missingmod-v1.0.0/go.mod --
module example.com/retract/missingmod
diff --git a/src/cmd/go/testdata/script/mod_sum_lookup.txt b/src/cmd/go/testdata/script/mod_sum_lookup.txt
index ed80a44984..e021921380 100644
--- a/src/cmd/go/testdata/script/mod_sum_lookup.txt
+++ b/src/cmd/go/testdata/script/mod_sum_lookup.txt
@@ -1,13 +1,14 @@
# When we attempt to resolve an import that doesn't exist, we should not save
# hashes for downloaded modules.
# Verifies golang.org/issue/36260.
-go list -e -tags=ignore ./noexist
+# TODO(golang.org/issue/26603): use 'go mod tidy -e' when implemented.
+go list -e -mod=mod -tags=ignore ./noexist
! exists go.sum
# When an import is resolved successfully, we should only save hashes for
# the module that provides the package, not for other modules looked up.
# Verifies golang.org/issue/31580.
-go list ./exist
+go get -d ./exist
grep '^example.com/join v1.1.0 h1:' go.sum
! grep '^example.com/join/subpkg' go.sum
cp go.sum go.list.sum
diff --git a/src/cmd/go/testdata/script/mod_sumdb_golang.txt b/src/cmd/go/testdata/script/mod_sumdb_golang.txt
index d9fb63acb0..cc0b0da474 100644
--- a/src/cmd/go/testdata/script/mod_sumdb_golang.txt
+++ b/src/cmd/go/testdata/script/mod_sumdb_golang.txt
@@ -34,7 +34,7 @@ cmp go.sum saved.sum
# Should use the checksum database to validate new go.sum lines,
# but not need to fetch any new data from the proxy.
rm go.sum
-go list -x rsc.io/quote
+go list -mod=mod -x rsc.io/quote
! stderr github
! stderr proxy.golang.org/rsc.io/quote
stderr sum.golang.org/tile
@@ -45,7 +45,7 @@ cmp go.sum saved.sum
env TESTGOPROXY404=1
go clean -modcache
rm go.sum
-go list -x rsc.io/quote
+go list -mod=mod -x rsc.io/quote
stderr 'proxy.golang.org.*404 testing'
stderr github.com/rsc
cmp go.sum saved.sum
diff --git a/src/cmd/go/testdata/script/mod_symlink.txt b/src/cmd/go/testdata/script/mod_symlink.txt
index 49bece2b84..dbc23fb8f0 100644
--- a/src/cmd/go/testdata/script/mod_symlink.txt
+++ b/src/cmd/go/testdata/script/mod_symlink.txt
@@ -1,16 +1,19 @@
env GO111MODULE=on
[!symlink] skip
-# 'go list' should resolve modules of imported packages.
+# 'go get -d' should resolve modules of imported packages.
+go get -d
go list -deps -f '{{.Module}}' .
stdout golang.org/x/text
+go get -d ./subpkg
go list -deps -f '{{.Module}}' ./subpkg
stdout golang.org/x/text
# Create a copy of the module using symlinks in src/links.
mkdir links
symlink links/go.mod -> $GOPATH/src/go.mod
+symlink links/go.sum -> $GOPATH/src/go.sum
symlink links/issue.go -> $GOPATH/src/issue.go
mkdir links/subpkg
symlink links/subpkg/issue.go -> $GOPATH/src/subpkg/issue.go
diff --git a/src/cmd/go/testdata/script/mod_test.txt b/src/cmd/go/testdata/script/mod_test.txt
index 8f2da2f2a5..50f00355c1 100644
--- a/src/cmd/go/testdata/script/mod_test.txt
+++ b/src/cmd/go/testdata/script/mod_test.txt
@@ -1,4 +1,5 @@
env GO111MODULE=on
+env GOFLAGS=-mod=mod
[short] skip
# TODO(bcmills): Convert the 'go test' calls below to 'go list -test' once 'go
diff --git a/src/cmd/go/testdata/script/mod_tidy_replace.txt b/src/cmd/go/testdata/script/mod_tidy_replace.txt
index c3158f8610..7b00bf1384 100644
--- a/src/cmd/go/testdata/script/mod_tidy_replace.txt
+++ b/src/cmd/go/testdata/script/mod_tidy_replace.txt
@@ -1,4 +1,5 @@
env GO111MODULE=on
+env GOFLAGS=-mod=mod
[short] skip
# golang.org/issue/30166: 'go mod tidy' should not crash if a replaced module is
diff --git a/src/cmd/go/testdata/script/mod_upgrade_patch.txt b/src/cmd/go/testdata/script/mod_upgrade_patch.txt
index 3939e54c1b..1ef25b9aef 100644
--- a/src/cmd/go/testdata/script/mod_upgrade_patch.txt
+++ b/src/cmd/go/testdata/script/mod_upgrade_patch.txt
@@ -2,6 +2,7 @@ env GO111MODULE=on
[short] skip
# Initially, we are at v1.0.0 for all dependencies.
+go get -d
cp go.mod go.mod.orig
go list -m all
stdout '^patch.example.com/direct v1.0.0'
diff --git a/src/cmd/go/testdata/script/mod_vcs_missing.txt b/src/cmd/go/testdata/script/mod_vcs_missing.txt
index a755935b53..f8be43cf4c 100644
--- a/src/cmd/go/testdata/script/mod_vcs_missing.txt
+++ b/src/cmd/go/testdata/script/mod_vcs_missing.txt
@@ -5,14 +5,14 @@ env GO111MODULE=on
env GOPROXY=direct
cd empty
-! go list launchpad.net/gocheck
+! go get -d launchpad.net/gocheck
stderr '"bzr": executable file not found'
cd ..
# 1.11 used to give the cryptic error "cannot find module for path" here, but
# only for a main package.
cd main
-! go build
+! go build -mod=mod
stderr '"bzr": executable file not found'
cd ..
diff --git a/src/cmd/go/testdata/script/mod_vendor_build.txt b/src/cmd/go/testdata/script/mod_vendor_build.txt
index 0c359cea6e..4efda55e08 100644
--- a/src/cmd/go/testdata/script/mod_vendor_build.txt
+++ b/src/cmd/go/testdata/script/mod_vendor_build.txt
@@ -1,6 +1,9 @@
env GO111MODULE=on
[short] skip
+# Populate go.mod and go.sum.
+go mod tidy
+
# initial conditions: using sampler v1.3.0, not listed in go.mod.
go list -deps
stdout rsc.io/sampler
diff --git a/src/cmd/go/testdata/script/mod_verify.txt b/src/cmd/go/testdata/script/mod_verify.txt
index 3918400435..43812d069f 100644
--- a/src/cmd/go/testdata/script/mod_verify.txt
+++ b/src/cmd/go/testdata/script/mod_verify.txt
@@ -56,7 +56,7 @@ go mod tidy
# Packages below module root should not be mentioned in go.sum.
rm go.sum
go mod edit -droprequire rsc.io/quote
-go list rsc.io/quote/buggy # re-resolves import path and updates go.mod
+go get -d rsc.io/quote/buggy
grep '^rsc.io/quote v1.5.2/go.mod ' go.sum
! grep buggy go.sum
diff --git a/src/cmd/go/testdata/script/mod_why.txt b/src/cmd/go/testdata/script/mod_why.txt
index 10a4f9fbea..c0ff4647a7 100644
--- a/src/cmd/go/testdata/script/mod_why.txt
+++ b/src/cmd/go/testdata/script/mod_why.txt
@@ -1,6 +1,9 @@
env GO111MODULE=on
[short] skip
+# Populate go.sum.
+go mod tidy
+
go list -test all
stdout rsc.io/quote
stdout golang.org/x/text/language
diff --git a/src/cmd/go/testdata/script/modfile_flag.txt b/src/cmd/go/testdata/script/modfile_flag.txt
index f05bf03fbf..0ad0880817 100644
--- a/src/cmd/go/testdata/script/modfile_flag.txt
+++ b/src/cmd/go/testdata/script/modfile_flag.txt
@@ -37,10 +37,10 @@ go mod why rsc.io/quote
# 'go list' and other commands with build flags should work.
# They should update the alternate go.mod when a dependency is missing.
go mod edit -droprequire rsc.io/quote
-go list .
+go list -mod=mod .
grep rsc.io/quote go.alt.mod
-go build -n .
-go test -n .
+go build -n -mod=mod .
+go test -n -mod=mod .
go get -d rsc.io/quote
diff --git a/src/cmd/go/testdata/script/version.txt b/src/cmd/go/testdata/script/version.txt
index 0123ac6d53..81ca698620 100644
--- a/src/cmd/go/testdata/script/version.txt
+++ b/src/cmd/go/testdata/script/version.txt
@@ -14,6 +14,7 @@ env GO111MODULE=on
[short] skip
# Check that 'go version' and 'go version -m' work on a binary built in module mode.
+go get -d rsc.io/fortune
go build -o fortune.exe rsc.io/fortune
go version fortune.exe
stdout '^fortune.exe: .+'
diff --git a/src/cmd/go/testdata/script/version_replace.txt b/src/cmd/go/testdata/script/version_replace.txt
index b657086f09..ec98f4e3f3 100644
--- a/src/cmd/go/testdata/script/version_replace.txt
+++ b/src/cmd/go/testdata/script/version_replace.txt
@@ -1,7 +1,7 @@
[short] skip
go mod download example.com/printversion@v0.1.0 example.com/printversion@v1.0.0
-
+go get -d example.com/printversion@v0.1.0
go install example.com/printversion
go run example.com/printversion
--
cgit v1.3
From b22af9b407dc29d1a733976484904ad0ab168466 Mon Sep 17 00:00:00 2001
From: Cherry Zhang
Date: Thu, 10 Sep 2020 22:41:29 -0400
Subject: cmd/link: record only the first occurance in Reachparent graph
In the deadcode pass, a type symbol may be marked twice, one
without UsedInIface, one with. For the second time, don't
update the Reachparent graph, so it only records the path of
the first time the symbol is reached. This ensures the
Reachparent graph is acyclic.
TODO: add a test. (This only affects GOEXPERIMENT=fieldtrack)
Change-Id: I68e8a1a69c3830bc8aee5df946151dc22dcb2b29
Reviewed-on: https://go-review.googlesource.com/c/go/+/254297
Run-TryBot: Cherry Zhang
TryBot-Result: Gobot Gobot
Reviewed-by: Than McIntosh
---
src/cmd/link/internal/ld/deadcode.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'src/cmd')
diff --git a/src/cmd/link/internal/ld/deadcode.go b/src/cmd/link/internal/ld/deadcode.go
index 0269429723..35545f950e 100644
--- a/src/cmd/link/internal/ld/deadcode.go
+++ b/src/cmd/link/internal/ld/deadcode.go
@@ -209,7 +209,7 @@ func (d *deadcodePass) mark(symIdx, parent loader.Sym) {
if symIdx != 0 && !d.ldr.AttrReachable(symIdx) {
d.wq.push(symIdx)
d.ldr.SetAttrReachable(symIdx, true)
- if objabi.Fieldtrack_enabled != 0 {
+ if objabi.Fieldtrack_enabled != 0 && d.ldr.Reachparent[symIdx] == 0 {
d.ldr.Reachparent[symIdx] = parent
}
if *flagDumpDep {
--
cgit v1.3
From 6e3df749b1058ecfaf5f6601f6f8678c0971da8e Mon Sep 17 00:00:00 2001
From: Jay Conrod
Date: Wed, 9 Sep 2020 16:35:56 -0400
Subject: cmd/go: refactor -mod flag parsing
Keep track of whether the -mod flag was set explicitly. When
-mod=readonly is the default, we'll want to adjust our error messages
if it's set explicitly.
Also, register the -mod, -modcacherw, and -modfile flags in functions
in internal/base instead of internal/work. 'go mod' commands that
don't load packages shouldn't depend on internal/work.
For #40728
Change-Id: I272aea9e19908ba37e151baac4ea8630e90f241f
Reviewed-on: https://go-review.googlesource.com/c/go/+/253744
Run-TryBot: Jay Conrod
TryBot-Result: Gobot Gobot
Reviewed-by: Michael Matloob
Reviewed-by: Bryan C. Mills
---
src/cmd/go/internal/base/flag.go | 35 +++++++++++++++++++++++++++++++---
src/cmd/go/internal/cfg/cfg.go | 3 ++-
src/cmd/go/internal/fmtcmd/fmt.go | 3 ++-
src/cmd/go/internal/modcmd/download.go | 3 +--
src/cmd/go/internal/modcmd/edit.go | 3 +--
src/cmd/go/internal/modcmd/graph.go | 3 +--
src/cmd/go/internal/modcmd/init.go | 3 +--
src/cmd/go/internal/modcmd/tidy.go | 3 +--
src/cmd/go/internal/modcmd/vendor.go | 3 +--
src/cmd/go/internal/modcmd/verify.go | 3 +--
src/cmd/go/internal/modcmd/why.go | 3 +--
src/cmd/go/internal/modload/init.go | 23 ++++++++++++----------
src/cmd/go/internal/work/build.go | 14 +++-----------
src/cmd/go/internal/work/init.go | 2 +-
14 files changed, 61 insertions(+), 43 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/go/internal/base/flag.go b/src/cmd/go/internal/base/flag.go
index 6727196816..c97c744520 100644
--- a/src/cmd/go/internal/base/flag.go
+++ b/src/cmd/go/internal/base/flag.go
@@ -28,13 +28,42 @@ func (v *StringsFlag) String() string {
return ""
}
+// explicitStringFlag is like a regular string flag, but it also tracks whether
+// the string was set explicitly to a non-empty value.
+type explicitStringFlag struct {
+ value *string
+ explicit *bool
+}
+
+func (f explicitStringFlag) String() string {
+ if f.value == nil {
+ return ""
+ }
+ return *f.value
+}
+
+func (f explicitStringFlag) Set(v string) error {
+ *f.value = v
+ if v != "" {
+ *f.explicit = true
+ }
+ return nil
+}
+
// AddBuildFlagsNX adds the -n and -x build flags to the flag set.
func AddBuildFlagsNX(flags *flag.FlagSet) {
flags.BoolVar(&cfg.BuildN, "n", false, "")
flags.BoolVar(&cfg.BuildX, "x", false, "")
}
-// AddLoadFlags adds the -mod build flag to the flag set.
-func AddLoadFlags(flags *flag.FlagSet) {
- flags.StringVar(&cfg.BuildMod, "mod", "", "")
+// AddModFlag adds the -mod build flag to the flag set.
+func AddModFlag(flags *flag.FlagSet) {
+ flags.Var(explicitStringFlag{value: &cfg.BuildMod, explicit: &cfg.BuildModExplicit}, "mod", "")
+}
+
+// AddModCommonFlags adds the module-related flags common to build commands
+// and 'go mod' subcommands.
+func AddModCommonFlags(flags *flag.FlagSet) {
+ flags.BoolVar(&cfg.ModCacheRW, "modcacherw", false, "")
+ flags.StringVar(&cfg.ModFile, "modfile", "", "")
}
diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go
index f9bbcd9180..f874b880a6 100644
--- a/src/cmd/go/internal/cfg/cfg.go
+++ b/src/cmd/go/internal/cfg/cfg.go
@@ -27,7 +27,8 @@ var (
BuildBuildmode string // -buildmode flag
BuildContext = defaultContext()
BuildMod string // -mod flag
- BuildModReason string // reason -mod flag is set, if set by default
+ BuildModExplicit bool // whether -mod was set explicitly
+ BuildModReason string // reason -mod was set, if set by default
BuildI bool // -i flag
BuildLinkshared bool // -linkshared flag
BuildMSan bool // -msan flag
diff --git a/src/cmd/go/internal/fmtcmd/fmt.go b/src/cmd/go/internal/fmtcmd/fmt.go
index f96cff429c..b0c1c59b40 100644
--- a/src/cmd/go/internal/fmtcmd/fmt.go
+++ b/src/cmd/go/internal/fmtcmd/fmt.go
@@ -23,7 +23,8 @@ import (
func init() {
base.AddBuildFlagsNX(&CmdFmt.Flag)
- base.AddLoadFlags(&CmdFmt.Flag)
+ base.AddModFlag(&CmdFmt.Flag)
+ base.AddModCommonFlags(&CmdFmt.Flag)
}
var CmdFmt = &base.Command{
diff --git a/src/cmd/go/internal/modcmd/download.go b/src/cmd/go/internal/modcmd/download.go
index 41f294d475..0ea5638e70 100644
--- a/src/cmd/go/internal/modcmd/download.go
+++ b/src/cmd/go/internal/modcmd/download.go
@@ -14,7 +14,6 @@ import (
"cmd/go/internal/cfg"
"cmd/go/internal/modfetch"
"cmd/go/internal/modload"
- "cmd/go/internal/work"
"golang.org/x/mod/module"
)
@@ -64,7 +63,7 @@ func init() {
// TODO(jayconrod): https://golang.org/issue/35849 Apply -x to other 'go mod' commands.
cmdDownload.Flag.BoolVar(&cfg.BuildX, "x", false, "")
- work.AddModCommonFlags(cmdDownload)
+ base.AddModCommonFlags(&cmdDownload.Flag)
}
type moduleJSON struct {
diff --git a/src/cmd/go/internal/modcmd/edit.go b/src/cmd/go/internal/modcmd/edit.go
index 18bdd34cd0..03a774b824 100644
--- a/src/cmd/go/internal/modcmd/edit.go
+++ b/src/cmd/go/internal/modcmd/edit.go
@@ -19,7 +19,6 @@ import (
"cmd/go/internal/lockedfile"
"cmd/go/internal/modfetch"
"cmd/go/internal/modload"
- "cmd/go/internal/work"
"golang.org/x/mod/modfile"
"golang.org/x/mod/module"
@@ -154,7 +153,7 @@ func init() {
cmdEdit.Flag.Var(flagFunc(flagRetract), "retract", "")
cmdEdit.Flag.Var(flagFunc(flagDropRetract), "dropretract", "")
- work.AddModCommonFlags(cmdEdit)
+ base.AddModCommonFlags(&cmdEdit.Flag)
base.AddBuildFlagsNX(&cmdEdit.Flag)
}
diff --git a/src/cmd/go/internal/modcmd/graph.go b/src/cmd/go/internal/modcmd/graph.go
index 513536a010..a149b65605 100644
--- a/src/cmd/go/internal/modcmd/graph.go
+++ b/src/cmd/go/internal/modcmd/graph.go
@@ -15,7 +15,6 @@ import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/modload"
- "cmd/go/internal/work"
"golang.org/x/mod/module"
)
@@ -33,7 +32,7 @@ path@version, except for the main module, which has no @version suffix.
}
func init() {
- work.AddModCommonFlags(cmdGraph)
+ base.AddModCommonFlags(&cmdGraph.Flag)
}
func runGraph(ctx context.Context, cmd *base.Command, args []string) {
diff --git a/src/cmd/go/internal/modcmd/init.go b/src/cmd/go/internal/modcmd/init.go
index b6cffd332d..21b235653e 100644
--- a/src/cmd/go/internal/modcmd/init.go
+++ b/src/cmd/go/internal/modcmd/init.go
@@ -9,7 +9,6 @@ package modcmd
import (
"cmd/go/internal/base"
"cmd/go/internal/modload"
- "cmd/go/internal/work"
"context"
"os"
"strings"
@@ -30,7 +29,7 @@ To override this guess, supply the module path as an argument.
}
func init() {
- work.AddModCommonFlags(cmdInit)
+ base.AddModCommonFlags(&cmdInit.Flag)
}
func runInit(ctx context.Context, cmd *base.Command, args []string) {
diff --git a/src/cmd/go/internal/modcmd/tidy.go b/src/cmd/go/internal/modcmd/tidy.go
index 4dcb62e02f..30df674ef6 100644
--- a/src/cmd/go/internal/modcmd/tidy.go
+++ b/src/cmd/go/internal/modcmd/tidy.go
@@ -10,7 +10,6 @@ import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/modload"
- "cmd/go/internal/work"
"context"
)
@@ -32,7 +31,7 @@ to standard error.
func init() {
cmdTidy.Run = runTidy // break init cycle
cmdTidy.Flag.BoolVar(&cfg.BuildV, "v", false, "")
- work.AddModCommonFlags(cmdTidy)
+ base.AddModCommonFlags(&cmdTidy.Flag)
}
func runTidy(ctx context.Context, cmd *base.Command, args []string) {
diff --git a/src/cmd/go/internal/modcmd/vendor.go b/src/cmd/go/internal/modcmd/vendor.go
index 30334f3a42..91d2509452 100644
--- a/src/cmd/go/internal/modcmd/vendor.go
+++ b/src/cmd/go/internal/modcmd/vendor.go
@@ -19,7 +19,6 @@ import (
"cmd/go/internal/cfg"
"cmd/go/internal/imports"
"cmd/go/internal/modload"
- "cmd/go/internal/work"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
@@ -41,7 +40,7 @@ modules and packages to standard error.
func init() {
cmdVendor.Flag.BoolVar(&cfg.BuildV, "v", false, "")
- work.AddModCommonFlags(cmdVendor)
+ base.AddModCommonFlags(&cmdVendor.Flag)
}
func runVendor(ctx context.Context, cmd *base.Command, args []string) {
diff --git a/src/cmd/go/internal/modcmd/verify.go b/src/cmd/go/internal/modcmd/verify.go
index d542825823..7700588bde 100644
--- a/src/cmd/go/internal/modcmd/verify.go
+++ b/src/cmd/go/internal/modcmd/verify.go
@@ -17,7 +17,6 @@ import (
"cmd/go/internal/cfg"
"cmd/go/internal/modfetch"
"cmd/go/internal/modload"
- "cmd/go/internal/work"
"golang.org/x/mod/module"
"golang.org/x/mod/sumdb/dirhash"
@@ -38,7 +37,7 @@ non-zero status.
}
func init() {
- work.AddModCommonFlags(cmdVerify)
+ base.AddModCommonFlags(&cmdVerify.Flag)
}
func runVerify(ctx context.Context, cmd *base.Command, args []string) {
diff --git a/src/cmd/go/internal/modcmd/why.go b/src/cmd/go/internal/modcmd/why.go
index 30b15fc153..8454fdfec6 100644
--- a/src/cmd/go/internal/modcmd/why.go
+++ b/src/cmd/go/internal/modcmd/why.go
@@ -11,7 +11,6 @@ import (
"cmd/go/internal/base"
"cmd/go/internal/modload"
- "cmd/go/internal/work"
"golang.org/x/mod/module"
)
@@ -58,7 +57,7 @@ var (
func init() {
cmdWhy.Run = runWhy // break init cycle
- work.AddModCommonFlags(cmdWhy)
+ base.AddModCommonFlags(&cmdWhy.Flag)
}
func runWhy(ctx context.Context, cmd *base.Command, args []string) {
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 8e8fb9e6a1..1f50dcb11c 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -518,17 +518,20 @@ func modFileToBuildList() {
// setDefaultBuildMod sets a default value for cfg.BuildMod
// if it is currently empty.
func setDefaultBuildMod() {
- if cfg.BuildMod != "" {
+ if cfg.BuildModExplicit {
// Don't override an explicit '-mod=' argument.
return
}
- cfg.BuildMod = "mod"
+
if cfg.CmdName == "get" || strings.HasPrefix(cfg.CmdName, "mod ") {
- // Don't set -mod implicitly for commands whose purpose is to
- // manipulate the build list.
+ // 'get' and 'go mod' commands may update go.mod automatically.
+ // TODO(jayconrod): should this narrower? Should 'go mod download' or
+ // 'go mod graph' update go.mod by default?
+ cfg.BuildMod = "mod"
return
}
if modRoot == "" {
+ cfg.BuildMod = "mod"
return
}
@@ -546,18 +549,18 @@ func setDefaultBuildMod() {
}
}
- // Since a vendor directory exists, we have a non-trivial reason for
- // choosing -mod=mod, although it probably won't be used for anything.
- // Record the reason anyway for consistency.
- // It may be overridden if we switch to mod=readonly below.
- cfg.BuildModReason = fmt.Sprintf("Go version in go.mod is %s.", modGo)
+ // Since a vendor directory exists, we should record why we didn't use it.
+ // This message won't normally be shown, but it may appear with import errors.
+ cfg.BuildModReason = fmt.Sprintf("Go version in go.mod is %s, so vendor directory was not used.", modGo)
}
p := ModFilePath()
if fi, err := os.Stat(p); err == nil && !hasWritePerm(p, fi) {
cfg.BuildMod = "readonly"
cfg.BuildModReason = "go.mod file is read-only."
+ return
}
+ cfg.BuildMod = "mod"
}
func legacyModInit() {
@@ -857,7 +860,7 @@ func WriteGoMod() {
// prefer to report a dirty go.mod over a dirty go.sum
if cfg.BuildModReason != "" {
base.Fatalf("go: updates to go.mod needed, disabled by -mod=readonly\n\t(%s)", cfg.BuildModReason)
- } else {
+ } else if cfg.BuildModExplicit {
base.Fatalf("go: updates to go.mod needed, disabled by -mod=readonly")
}
}
diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go
index d020aa6e9f..e99982ed36 100644
--- a/src/cmd/go/internal/work/build.go
+++ b/src/cmd/go/internal/work/build.go
@@ -240,13 +240,12 @@ const (
// AddBuildFlags adds the flags common to the build, clean, get,
// install, list, run, and test commands.
func AddBuildFlags(cmd *base.Command, mask BuildFlagMask) {
+ base.AddBuildFlagsNX(&cmd.Flag)
cmd.Flag.BoolVar(&cfg.BuildA, "a", false, "")
- cmd.Flag.BoolVar(&cfg.BuildN, "n", false, "")
cmd.Flag.IntVar(&cfg.BuildP, "p", cfg.BuildP, "")
if mask&OmitVFlag == 0 {
cmd.Flag.BoolVar(&cfg.BuildV, "v", false, "")
}
- cmd.Flag.BoolVar(&cfg.BuildX, "x", false, "")
cmd.Flag.Var(&load.BuildAsmflags, "asmflags", "")
cmd.Flag.Var(buildCompiler{}, "compiler", "")
@@ -254,10 +253,10 @@ func AddBuildFlags(cmd *base.Command, mask BuildFlagMask) {
cmd.Flag.Var(&load.BuildGcflags, "gcflags", "")
cmd.Flag.Var(&load.BuildGccgoflags, "gccgoflags", "")
if mask&OmitModFlag == 0 {
- cmd.Flag.StringVar(&cfg.BuildMod, "mod", "", "")
+ base.AddModFlag(&cmd.Flag)
}
if mask&OmitModCommonFlags == 0 {
- AddModCommonFlags(cmd)
+ base.AddModCommonFlags(&cmd.Flag)
}
cmd.Flag.StringVar(&cfg.BuildContext.InstallSuffix, "installsuffix", "", "")
cmd.Flag.Var(&load.BuildLdflags, "ldflags", "")
@@ -275,13 +274,6 @@ func AddBuildFlags(cmd *base.Command, mask BuildFlagMask) {
cmd.Flag.StringVar(&cfg.DebugTrace, "debug-trace", "", "")
}
-// AddModCommonFlags adds the module-related flags common to build commands
-// and 'go mod' subcommands.
-func AddModCommonFlags(cmd *base.Command) {
- cmd.Flag.BoolVar(&cfg.ModCacheRW, "modcacherw", false, "")
- cmd.Flag.StringVar(&cfg.ModFile, "modfile", "", "")
-}
-
// tagsFlag is the implementation of the -tags flag.
type tagsFlag []string
diff --git a/src/cmd/go/internal/work/init.go b/src/cmd/go/internal/work/init.go
index dad3b10111..f78020032c 100644
--- a/src/cmd/go/internal/work/init.go
+++ b/src/cmd/go/internal/work/init.go
@@ -252,7 +252,7 @@ func buildModeInit() {
switch cfg.BuildMod {
case "":
- // ok
+ // Behavior will be determined automatically, as if no flag were passed.
case "readonly", "vendor", "mod":
if !cfg.ModulesEnabled && !inGOFLAGS("-mod") {
base.Fatalf("build flag -mod=%s only valid when using modules", cfg.BuildMod)
--
cgit v1.3
From 9214677e7df1e6130249bc83d721130b00d829c4 Mon Sep 17 00:00:00 2001
From: Jay Conrod
Date: Wed, 9 Sep 2020 16:41:55 -0400
Subject: cmd/go: refactor modload.Import for better -mod=readonly errors
When -mod=readonly is set, Import will now allow imports from
replacements without explicit requirements. With -mod=mod, this would
add a new requirement but does not trigger a module lookup, so it's
determinisitic.
Before reporting an error for an unknown import with -mod=readonly,
check whether the import is valid. If there's a typo in the import,
that's more relevant.
For #40728
Change-Id: I05e138ff76ba3d0eb2e3010c15589fa363deb8d3
Reviewed-on: https://go-review.googlesource.com/c/go/+/253745
Run-TryBot: Jay Conrod
TryBot-Result: Gobot Gobot
Reviewed-by: Michael Matloob
Reviewed-by: Bryan C. Mills
---
src/cmd/go/internal/modload/import.go | 53 +++++++++++++++++------
src/cmd/go/testdata/script/mod_build_info_err.txt | 2 +-
2 files changed, 41 insertions(+), 14 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go
index c625184b8b..10b1e7f4b8 100644
--- a/src/cmd/go/internal/modload/import.go
+++ b/src/cmd/go/internal/modload/import.go
@@ -107,6 +107,25 @@ func (e *AmbiguousImportError) Error() string {
var _ load.ImportPathError = &AmbiguousImportError{}
+type invalidImportError struct {
+ importPath string
+ err error
+}
+
+func (e *invalidImportError) ImportPath() string {
+ return e.importPath
+}
+
+func (e *invalidImportError) Error() string {
+ return e.err.Error()
+}
+
+func (e *invalidImportError) Unwrap() error {
+ return e.err
+}
+
+var _ load.ImportPathError = &invalidImportError{}
+
// importFromBuildList finds the module and directory in the build list
// containing the package with the given import path. The answer must be unique:
// importFromBuildList returns an error if multiple modules attempt to provide
@@ -207,17 +226,6 @@ func importFromBuildList(ctx context.Context, path string) (m module.Version, di
func queryImport(ctx context.Context, path string) (module.Version, error) {
pathIsStd := search.IsStandardImportPath(path)
- if cfg.BuildMod == "readonly" {
- var queryErr error
- if !pathIsStd {
- if cfg.BuildModReason == "" {
- queryErr = fmt.Errorf("import lookup disabled by -mod=%s", cfg.BuildMod)
- } else {
- queryErr = fmt.Errorf("import lookup disabled by -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason)
- }
- }
- return module.Version{}, &ImportMissingError{Path: path, QueryErr: queryErr}
- }
if modRoot == "" && !allowMissingModuleImports {
return module.Version{}, &ImportMissingError{
Path: path,
@@ -226,8 +234,9 @@ func queryImport(ctx context.Context, path string) (module.Version, error) {
}
// Not on build list.
- // To avoid spurious remote fetches, next try the latest replacement for each module.
- // (golang.org/issue/26241)
+ // To avoid spurious remote fetches, next try the latest replacement for each
+ // module (golang.org/issue/26241). This should give a useful message
+ // in -mod=readonly, and it will allow us to add a requirement with -mod=mod.
if modFile != nil {
latest := map[string]string{} // path -> version
for _, r := range modFile.Replace {
@@ -288,6 +297,11 @@ func queryImport(ctx context.Context, path string) (module.Version, error) {
}
}
+ // Before any further lookup, check that the path is valid.
+ if err := module.CheckImportPath(path); err != nil {
+ return module.Version{}, &invalidImportError{importPath: path, err: err}
+ }
+
if pathIsStd {
// This package isn't in the standard library, isn't in any module already
// in the build list, and isn't in any other module that the user has
@@ -299,6 +313,19 @@ func queryImport(ctx context.Context, path string) (module.Version, error) {
return module.Version{}, &ImportMissingError{Path: path}
}
+ if cfg.BuildMod == "readonly" {
+ var queryErr error
+ if cfg.BuildModExplicit {
+ queryErr = fmt.Errorf("import lookup disabled by -mod=%s", cfg.BuildMod)
+ } else if cfg.BuildModReason != "" {
+ queryErr = fmt.Errorf("import lookup disabled by -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason)
+ }
+ return module.Version{}, &ImportMissingError{Path: path, QueryErr: queryErr}
+ }
+
+ // Look up module containing the package, for addition to the build list.
+ // Goal is to determine the module, download it to dir,
+ // and return m, dir, ImpportMissingError.
fmt.Fprintf(os.Stderr, "go: finding module for package %s\n", path)
candidates, err := QueryPackage(ctx, path, "latest", CheckAllowed)
diff --git a/src/cmd/go/testdata/script/mod_build_info_err.txt b/src/cmd/go/testdata/script/mod_build_info_err.txt
index 87a099b219..a6853b5c86 100644
--- a/src/cmd/go/testdata/script/mod_build_info_err.txt
+++ b/src/cmd/go/testdata/script/mod_build_info_err.txt
@@ -2,7 +2,7 @@
# Verifies golang.org/issue/34393.
go list -e -deps -f '{{with .Error}}{{.Pos}}: {{.Err}}{{end}}' ./main
-stdout 'bad[/\\]bad.go:3:8: malformed module path "🐧.example.com/string": invalid char ''🐧'''
+stdout 'bad[/\\]bad.go:3:8: malformed import path "🐧.example.com/string": invalid char ''🐧'''
-- go.mod --
module m
--
cgit v1.3
From a531bd5a59177dfef354df8b5b5b529a2a55d015 Mon Sep 17 00:00:00 2001
From: Cherry Zhang
Date: Tue, 8 Sep 2020 20:41:51 -0400
Subject: cmd/internal/objfile: recognize Mach-O __DATA_CONST segment as
read-only data
Updates #38830.
Change-Id: I826c6b0a42bc8e48fcda556250ca4a95c73987eb
Reviewed-on: https://go-review.googlesource.com/c/go/+/253918
Run-TryBot: Cherry Zhang
TryBot-Result: Gobot Gobot
Reviewed-by: Than McIntosh
---
src/cmd/internal/objfile/macho.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'src/cmd')
diff --git a/src/cmd/internal/objfile/macho.go b/src/cmd/internal/objfile/macho.go
index fdb7e76dfc..1d6963f7c4 100644
--- a/src/cmd/internal/objfile/macho.go
+++ b/src/cmd/internal/objfile/macho.go
@@ -60,7 +60,7 @@ func (f *machoFile) symbols() ([]Sym, error) {
} else if int(s.Sect) <= len(f.macho.Sections) {
sect := f.macho.Sections[s.Sect-1]
switch sect.Seg {
- case "__TEXT":
+ case "__TEXT", "__DATA_CONST":
sym.Code = 'R'
case "__DATA":
sym.Code = 'D'
--
cgit v1.3
From ffd95aadcddc34ec2c83971346f04cf7234e0fca Mon Sep 17 00:00:00 2001
From: Cherry Zhang
Date: Thu, 3 Sep 2020 12:59:09 -0400
Subject: cmd/link: put read-only data in __DATA_CONST segment
On darwin, we put read-only data in __TEXT segment on AMD64 in
exe (non-PIE) buildmode, and in __DATA on everywhere else. This
is not ideal: things in __DATA segment are not read-only, and
being mapped R/W may use more run-time resources.
In fact, newer darwin systems support a __DATA_CONST segment,
which the dynamic linker will map it read-only after applying
relocations. Use that.
Fixes #38830.
Change-Id: Ic281e6c6ca8ef5fec4bb7c5b71c50dd5393e78ae
Reviewed-on: https://go-review.googlesource.com/c/go/+/253919
Reviewed-by: Than McIntosh
---
src/cmd/link/internal/ld/data.go | 20 ++++++++++++-------
src/cmd/link/internal/ld/macho.go | 41 +++++++++++++++++++++++---------------
src/cmd/link/internal/ld/target.go | 6 +++++-
3 files changed, 43 insertions(+), 24 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go
index 8324a98a26..a730125cf2 100644
--- a/src/cmd/link/internal/ld/data.go
+++ b/src/cmd/link/internal/ld/data.go
@@ -930,7 +930,7 @@ func writeBlock(ctxt *Link, out *OutBuf, ldr *loader.Loader, syms []loader.Sym,
break
}
if val < addr {
- ldr.Errorf(s, "phase error: addr=%#x but sym=%#x type=%d", addr, val, ldr.SymType(s))
+ ldr.Errorf(s, "phase error: addr=%#x but sym=%#x type=%v sect=%v", addr, val, ldr.SymType(s), ldr.SymSect(s).Name)
errorexit()
}
if addr < val {
@@ -1308,9 +1308,9 @@ func (state *dodataState) makeRelroForSharedLib(target *Link) {
// relro Type before it reaches here.
isRelro = true
case sym.SFUNCTAB:
- if target.IsAIX() && ldr.SymName(s) == "runtime.etypes" {
+ if ldr.SymName(s) == "runtime.etypes" {
// runtime.etypes must be at the end of
- // the relro datas.
+ // the relro data.
isRelro = true
}
}
@@ -1706,7 +1706,7 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.ebss", 0), sect)
bssGcEnd := state.datsize - int64(sect.Vaddr)
- // Emit gcdata for bcc symbols now that symbol values have been assigned.
+ // Emit gcdata for bss symbols now that symbol values have been assigned.
gcsToEmit := []struct {
symName string
symKind sym.SymKind
@@ -1826,13 +1826,16 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
const fallbackPerm = 04
relroSecPerm := fallbackPerm
genrelrosecname := func(suffix string) string {
+ if suffix == "" {
+ return ".rodata"
+ }
return suffix
}
seg := segro
if ctxt.UseRelro() {
segrelro := &Segrelrodata
- if ctxt.LinkMode == LinkExternal && ctxt.HeadType != objabi.Haix {
+ if ctxt.LinkMode == LinkExternal && !ctxt.IsAIX() && !ctxt.IsDarwin() {
// Using a separate segment with an external
// linker results in some programs moving
// their data sections unexpectedly, which
@@ -1845,9 +1848,12 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
state.datsize = 0
}
- genrelrosecname = func(suffix string) string {
- return ".data.rel.ro" + suffix
+ if !ctxt.IsDarwin() { // We don't need the special names on darwin.
+ genrelrosecname = func(suffix string) string {
+ return ".data.rel.ro" + suffix
+ }
}
+
relroReadOnly := []sym.SymKind{}
for _, symnro := range sym.ReadOnly {
symn := sym.RelROMap[symnro]
diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go
index f6356729a6..9765ce18d3 100644
--- a/src/cmd/link/internal/ld/macho.go
+++ b/src/cmd/link/internal/ld/macho.go
@@ -499,16 +499,7 @@ func machoadddynlib(lib string, linkmode LinkMode) {
func machoshbits(ctxt *Link, mseg *MachoSeg, sect *sym.Section, segname string) {
buf := "__" + strings.Replace(sect.Name[1:], ".", "_", -1)
- var msect *MachoSect
- if sect.Rwx&1 == 0 && segname != "__DWARF" && (ctxt.Arch.Family == sys.ARM64 ||
- (ctxt.Arch.Family == sys.AMD64 && ctxt.BuildMode != BuildModeExe)) {
- // Darwin external linker on arm64, and on amd64 in c-shared/c-archive buildmode
- // complains about absolute relocs in __TEXT, so if the section is not
- // executable, put it in __DATA segment.
- msect = newMachoSect(mseg, buf, "__DATA")
- } else {
- msect = newMachoSect(mseg, buf, segname)
- }
+ msect := newMachoSect(mseg, buf, segname)
if sect.Rellen > 0 {
msect.reloc = uint32(sect.Reloff)
@@ -633,13 +624,28 @@ func asmbMacho(ctxt *Link) {
machoshbits(ctxt, ms, sect, "__TEXT")
}
+ /* rodata */
+ if ctxt.LinkMode != LinkExternal && Segrelrodata.Length > 0 {
+ ms = newMachoSeg("__DATA_CONST", 20)
+ ms.vaddr = Segrelrodata.Vaddr
+ ms.vsize = Segrelrodata.Length
+ ms.fileoffset = Segrelrodata.Fileoff
+ ms.filesize = Segrelrodata.Filelen
+ ms.prot1 = 3
+ ms.prot2 = 3
+ ms.flag = 0x10 // SG_READ_ONLY
+ }
+
+ for _, sect := range Segrelrodata.Sections {
+ machoshbits(ctxt, ms, sect, "__DATA_CONST")
+ }
+
/* data */
if ctxt.LinkMode != LinkExternal {
- w := int64(Segdata.Length)
ms = newMachoSeg("__DATA", 20)
- ms.vaddr = uint64(va) + uint64(v)
- ms.vsize = uint64(w)
- ms.fileoffset = uint64(v)
+ ms.vaddr = Segdata.Vaddr
+ ms.vsize = Segdata.Length
+ ms.fileoffset = Segdata.Fileoff
ms.filesize = Segdata.Filelen
ms.prot1 = 3
ms.prot2 = 3
@@ -695,7 +701,7 @@ func asmbMacho(ctxt *Link) {
if ctxt.LinkMode != LinkExternal {
ms := newMachoSeg("__LINKEDIT", 0)
- ms.vaddr = uint64(va) + uint64(v) + uint64(Rnd(int64(Segdata.Length), int64(*FlagRound)))
+ ms.vaddr = uint64(Rnd(int64(Segdata.Vaddr+Segdata.Length), int64(*FlagRound)))
ms.vsize = uint64(s1) + uint64(s2) + uint64(s3) + uint64(s4)
ms.fileoffset = uint64(linkoff)
ms.filesize = ms.vsize
@@ -1008,7 +1014,7 @@ func doMachoLink(ctxt *Link) int64 {
size := int(ldr.SymSize(s1) + ldr.SymSize(s2) + ldr.SymSize(s3) + ldr.SymSize(s4))
if size > 0 {
- linkoff = Rnd(int64(uint64(HEADR)+Segtext.Length), int64(*FlagRound)) + Rnd(int64(Segdata.Filelen), int64(*FlagRound)) + Rnd(int64(Segdwarf.Filelen), int64(*FlagRound))
+ linkoff = Rnd(int64(uint64(HEADR)+Segtext.Length), int64(*FlagRound)) + Rnd(int64(Segrelrodata.Filelen), int64(*FlagRound)) + Rnd(int64(Segdata.Filelen), int64(*FlagRound)) + Rnd(int64(Segdwarf.Filelen), int64(*FlagRound))
ctxt.Out.SeekSet(linkoff)
ctxt.Out.Write(ldr.Data(s1))
@@ -1086,6 +1092,9 @@ func machoEmitReloc(ctxt *Link) {
for _, sect := range Segtext.Sections[1:] {
relocSect(ctxt, sect, ctxt.datap)
}
+ for _, sect := range Segrelrodata.Sections {
+ relocSect(ctxt, sect, ctxt.datap)
+ }
for _, sect := range Segdata.Sections {
relocSect(ctxt, sect, ctxt.datap)
}
diff --git a/src/cmd/link/internal/ld/target.go b/src/cmd/link/internal/ld/target.go
index 102b6c5436..f68de8fff1 100644
--- a/src/cmd/link/internal/ld/target.go
+++ b/src/cmd/link/internal/ld/target.go
@@ -74,8 +74,12 @@ func (t *Target) IsDynlinkingGo() bool {
func (t *Target) UseRelro() bool {
switch t.BuildMode {
case BuildModeCArchive, BuildModeCShared, BuildModeShared, BuildModePIE, BuildModePlugin:
- return t.IsELF || t.HeadType == objabi.Haix
+ return t.IsELF || t.HeadType == objabi.Haix || t.HeadType == objabi.Hdarwin
default:
+ if t.HeadType == objabi.Hdarwin && t.IsARM64() {
+ // On darwin/ARM64, everything is PIE.
+ return true
+ }
return t.linkShared || (t.HeadType == objabi.Haix && t.LinkMode == LinkExternal)
}
}
--
cgit v1.3
From 1ed4f12f4a6b9d783cf9a6fc3a292a433b8539c6 Mon Sep 17 00:00:00 2001
From: Cherry Zhang
Date: Thu, 10 Sep 2020 11:14:27 -0400
Subject: cmd/link: add a test to test RODATA is indeed read-only
Updates #38830.
Change-Id: Ie1f6ccef40a773f038aac587dfc26bf70a1a8536
Reviewed-on: https://go-review.googlesource.com/c/go/+/253921
Run-TryBot: Cherry Zhang
Reviewed-by: Than McIntosh
TryBot-Result: Gobot Gobot
---
src/cmd/link/link_test.go | 14 ++++++++++++++
src/cmd/link/testdata/testRO/x.go | 22 ++++++++++++++++++++++
2 files changed, 36 insertions(+)
create mode 100644 src/cmd/link/testdata/testRO/x.go
(limited to 'src/cmd')
diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go
index 98798be465..4e60996d8e 100644
--- a/src/cmd/link/link_test.go
+++ b/src/cmd/link/link_test.go
@@ -800,3 +800,17 @@ func TestContentAddressableSymbols(t *testing.T) {
t.Errorf("command %s failed: %v\n%s", cmd, err, out)
}
}
+
+func TestReadOnly(t *testing.T) {
+ // Test that read-only data is indeed read-only.
+ testenv.MustHaveGoBuild(t)
+
+ t.Parallel()
+
+ src := filepath.Join("testdata", "testRO", "x.go")
+ cmd := exec.Command(testenv.GoToolPath(t), "run", src)
+ out, err := cmd.CombinedOutput()
+ if err == nil {
+ t.Errorf("running test program did not fail. output:\n%s", out)
+ }
+}
diff --git a/src/cmd/link/testdata/testRO/x.go b/src/cmd/link/testdata/testRO/x.go
new file mode 100644
index 0000000000..d77db6d563
--- /dev/null
+++ b/src/cmd/link/testdata/testRO/x.go
@@ -0,0 +1,22 @@
+// Copyright 2020 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.
+
+// Test that read-only data is indeed read-only. This
+// program attempts to modify read-only data, and it
+// should fail.
+
+package main
+
+import "unsafe"
+
+var s = "hello"
+
+func main() {
+ println(s)
+ *(*struct {
+ p *byte
+ l int
+ })(unsafe.Pointer(&s)).p = 'H'
+ println(s)
+}
--
cgit v1.3
From b459bc8152210c14b66e23351690ff774cd68d2c Mon Sep 17 00:00:00 2001
From: Jay Conrod
Date: Fri, 11 Sep 2020 09:31:30 -0400
Subject: cmd/go: make 'go mod download' update go.sum after downloads are
complete
'go mod download' calls WriteGoMod once via modload.ListModules when
it loads the build list. This saves sums for go.mod files needed by
MVS, but the write occurs before any zip files are downloaded.
With this change, 'go mod download' calls WriteGoMod again (and thus,
modfetch.WriteGoSum) after downloading and verifying module zip files,
so the sums of the zip files will be saved, too.
Fixes #41341
Change-Id: I7d56754aa255256ed45fd93cb154c2e6ea5f45a9
Reviewed-on: https://go-review.googlesource.com/c/go/+/254357
Run-TryBot: Jay Conrod
TryBot-Result: Gobot Gobot
Reviewed-by: Bryan C. Mills
---
src/cmd/go/internal/modcmd/download.go | 3 +++
src/cmd/go/testdata/script/mod_download.txt | 18 ++++++++++++++++++
2 files changed, 21 insertions(+)
(limited to 'src/cmd')
diff --git a/src/cmd/go/internal/modcmd/download.go b/src/cmd/go/internal/modcmd/download.go
index 0ea5638e70..6227fd9f33 100644
--- a/src/cmd/go/internal/modcmd/download.go
+++ b/src/cmd/go/internal/modcmd/download.go
@@ -187,4 +187,7 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) {
}
base.ExitIfErrors()
}
+
+ // Update go.mod and especially go.sum if needed.
+ modload.WriteGoMod()
}
diff --git a/src/cmd/go/testdata/script/mod_download.txt b/src/cmd/go/testdata/script/mod_download.txt
index b9bf67cad5..c53bbe4567 100644
--- a/src/cmd/go/testdata/script/mod_download.txt
+++ b/src/cmd/go/testdata/script/mod_download.txt
@@ -107,6 +107,14 @@ stderr '^go mod download: skipping argument m that resolves to the main module\n
go mod download m@latest
stderr '^go mod download: skipping argument m@latest that resolves to the main module\n'
+# download updates go.mod and populates go.sum
+cd update
+! exists go.sum
+go mod download
+grep '^rsc.io/sampler v1.3.0 ' go.sum
+go list -m rsc.io/sampler
+stdout '^rsc.io/sampler v1.3.0$'
+
# allow go mod download without go.mod
env GO111MODULE=auto
rm go.mod
@@ -122,3 +130,13 @@ stderr 'get '$GOPROXY
-- go.mod --
module m
+
+-- update/go.mod --
+module m
+
+go 1.16
+
+require (
+ rsc.io/quote v1.5.2
+ rsc.io/sampler v1.2.1 // older version than in build list
+)
--
cgit v1.3
From 86ee84c40e2770ff189b6a4d835849107d9c749a Mon Sep 17 00:00:00 2001
From: Jay Conrod
Date: Wed, 2 Sep 2020 13:25:11 -0400
Subject: cmd/go: move get.Insecure to cfg.Insecure to break dependency cycle
Change-Id: If9c73ff5adc7e080a48ecc6b35ce40822193d66f
Reviewed-on: https://go-review.googlesource.com/c/go/+/254363
Run-TryBot: Jay Conrod
Reviewed-by: Bryan C. Mills
Reviewed-by: Michael Matloob
TryBot-Result: Gobot Gobot
---
src/cmd/go/internal/cfg/cfg.go | 2 ++
src/cmd/go/internal/get/get.go | 6 ++----
src/cmd/go/internal/modfetch/insecure.go | 3 +--
src/cmd/go/internal/modfetch/sumdb.go | 3 +--
src/cmd/go/internal/modget/get.go | 6 +++---
5 files changed, 9 insertions(+), 11 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go
index f874b880a6..9bf1db73ef 100644
--- a/src/cmd/go/internal/cfg/cfg.go
+++ b/src/cmd/go/internal/cfg/cfg.go
@@ -49,6 +49,8 @@ var (
ModCacheRW bool // -modcacherw flag
ModFile string // -modfile flag
+ Insecure bool // -insecure flag
+
CmdName string // "build", "install", "list", "mod tidy", etc.
DebugActiongraph string // -debug-actiongraph flag (undocumented, unstable)
diff --git a/src/cmd/go/internal/get/get.go b/src/cmd/go/internal/get/get.go
index d0be3fe1e7..9e4825eb37 100644
--- a/src/cmd/go/internal/get/get.go
+++ b/src/cmd/go/internal/get/get.go
@@ -108,14 +108,12 @@ var (
getT = CmdGet.Flag.Bool("t", false, "")
getU = CmdGet.Flag.Bool("u", false, "")
getFix = CmdGet.Flag.Bool("fix", false, "")
-
- Insecure bool
)
func init() {
work.AddBuildFlags(CmdGet, work.OmitModFlag|work.OmitModCommonFlags)
CmdGet.Run = runGet // break init loop
- CmdGet.Flag.BoolVar(&Insecure, "insecure", Insecure, "")
+ CmdGet.Flag.BoolVar(&cfg.Insecure, "insecure", cfg.Insecure, "")
}
func runGet(ctx context.Context, cmd *base.Command, args []string) {
@@ -431,7 +429,7 @@ func downloadPackage(p *load.Package) error {
return fmt.Errorf("%s: invalid import path: %v", p.ImportPath, err)
}
security := web.SecureOnly
- if Insecure || module.MatchPrefixPatterns(cfg.GOINSECURE, importPrefix) {
+ if cfg.Insecure || module.MatchPrefixPatterns(cfg.GOINSECURE, importPrefix) {
security = web.Insecure
}
diff --git a/src/cmd/go/internal/modfetch/insecure.go b/src/cmd/go/internal/modfetch/insecure.go
index b692669cba..012d05f29d 100644
--- a/src/cmd/go/internal/modfetch/insecure.go
+++ b/src/cmd/go/internal/modfetch/insecure.go
@@ -6,12 +6,11 @@ package modfetch
import (
"cmd/go/internal/cfg"
- "cmd/go/internal/get"
"golang.org/x/mod/module"
)
// allowInsecure reports whether we are allowed to fetch this path in an insecure manner.
func allowInsecure(path string) bool {
- return get.Insecure || module.MatchPrefixPatterns(cfg.GOINSECURE, path)
+ return cfg.Insecure || module.MatchPrefixPatterns(cfg.GOINSECURE, path)
}
diff --git a/src/cmd/go/internal/modfetch/sumdb.go b/src/cmd/go/internal/modfetch/sumdb.go
index 783c4a433b..47a2571531 100644
--- a/src/cmd/go/internal/modfetch/sumdb.go
+++ b/src/cmd/go/internal/modfetch/sumdb.go
@@ -22,7 +22,6 @@ import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
- "cmd/go/internal/get"
"cmd/go/internal/lockedfile"
"cmd/go/internal/web"
@@ -33,7 +32,7 @@ import (
// useSumDB reports whether to use the Go checksum database for the given module.
func useSumDB(mod module.Version) bool {
- return cfg.GOSUMDB != "off" && !get.Insecure && !module.MatchPrefixPatterns(cfg.GONOSUMDB, mod.Path)
+ return cfg.GOSUMDB != "off" && !cfg.Insecure && !module.MatchPrefixPatterns(cfg.GONOSUMDB, mod.Path)
}
// lookupSumDB returns the Go checksum database's go.sum lines for the given module,
diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go
index a2a8287d84..829cfe055a 100644
--- a/src/cmd/go/internal/modget/get.go
+++ b/src/cmd/go/internal/modget/get.go
@@ -17,7 +17,7 @@ import (
"sync"
"cmd/go/internal/base"
- "cmd/go/internal/get"
+ "cmd/go/internal/cfg"
"cmd/go/internal/imports"
"cmd/go/internal/load"
"cmd/go/internal/modload"
@@ -181,7 +181,7 @@ var (
getM = CmdGet.Flag.Bool("m", false, "")
getT = CmdGet.Flag.Bool("t", false, "")
getU upgradeFlag
- // -insecure is get.Insecure
+ // -insecure is cfg.Insecure
// -v is cfg.BuildV
)
@@ -206,7 +206,7 @@ func (v *upgradeFlag) String() string { return "" }
func init() {
work.AddBuildFlags(CmdGet, work.OmitModFlag)
CmdGet.Run = runGet // break init loop
- CmdGet.Flag.BoolVar(&get.Insecure, "insecure", get.Insecure, "")
+ CmdGet.Flag.BoolVar(&cfg.Insecure, "insecure", cfg.Insecure, "")
CmdGet.Flag.Var(&getU, "u", "")
}
--
cgit v1.3
From 07c1788357cfe6a4ee5f6f6a54d4fe9f579fa844 Mon Sep 17 00:00:00 2001
From: Jay Conrod
Date: Wed, 2 Sep 2020 14:53:02 -0400
Subject: cmd/go: move repository resolution from internal/get to internal/vcs
This is a refactoring intended to break the dependency from
internal/modfetch to internal/get. No change in functionality is intended.
Change-Id: If51aba7139cc0b62ecc9ba454c055c99e8f36f0f
Reviewed-on: https://go-review.googlesource.com/c/go/+/254364
Run-TryBot: Jay Conrod
Reviewed-by: Bryan C. Mills
Reviewed-by: Michael Matloob
TryBot-Result: Gobot Gobot
---
src/cmd/go/internal/get/discovery.go | 97 ---
src/cmd/go/internal/get/get.go | 35 +-
src/cmd/go/internal/get/pkg_test.go | 131 ----
src/cmd/go/internal/get/vcs.go | 1182 ----------------------------
src/cmd/go/internal/get/vcs_test.go | 475 ------------
src/cmd/go/internal/modfetch/repo.go | 12 +-
src/cmd/go/internal/str/str_test.go | 27 +
src/cmd/go/internal/vcs/discovery.go | 97 +++
src/cmd/go/internal/vcs/discovery_test.go | 110 +++
src/cmd/go/internal/vcs/vcs.go | 1187 +++++++++++++++++++++++++++++
src/cmd/go/internal/vcs/vcs_test.go | 475 ++++++++++++
11 files changed, 1920 insertions(+), 1908 deletions(-)
delete mode 100644 src/cmd/go/internal/get/discovery.go
delete mode 100644 src/cmd/go/internal/get/pkg_test.go
delete mode 100644 src/cmd/go/internal/get/vcs.go
delete mode 100644 src/cmd/go/internal/get/vcs_test.go
create mode 100644 src/cmd/go/internal/str/str_test.go
create mode 100644 src/cmd/go/internal/vcs/discovery.go
create mode 100644 src/cmd/go/internal/vcs/discovery_test.go
create mode 100644 src/cmd/go/internal/vcs/vcs.go
create mode 100644 src/cmd/go/internal/vcs/vcs_test.go
(limited to 'src/cmd')
diff --git a/src/cmd/go/internal/get/discovery.go b/src/cmd/go/internal/get/discovery.go
deleted file mode 100644
index afa6ef455f..0000000000
--- a/src/cmd/go/internal/get/discovery.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2012 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.
-
-package get
-
-import (
- "encoding/xml"
- "fmt"
- "io"
- "strings"
-)
-
-// charsetReader returns a reader that converts from the given charset to UTF-8.
-// Currently it only supports UTF-8 and ASCII. Otherwise, it returns a meaningful
-// error which is printed by go get, so the user can find why the package
-// wasn't downloaded if the encoding is not supported. Note that, in
-// order to reduce potential errors, ASCII is treated as UTF-8 (i.e. characters
-// greater than 0x7f are not rejected).
-func charsetReader(charset string, input io.Reader) (io.Reader, error) {
- switch strings.ToLower(charset) {
- case "utf-8", "ascii":
- return input, nil
- default:
- return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
- }
-}
-
-// parseMetaGoImports returns meta imports from the HTML in r.
-// Parsing ends at the end of the section or the beginning of the .
-func parseMetaGoImports(r io.Reader, mod ModuleMode) ([]metaImport, error) {
- d := xml.NewDecoder(r)
- d.CharsetReader = charsetReader
- d.Strict = false
- var imports []metaImport
- for {
- t, err := d.RawToken()
- if err != nil {
- if err != io.EOF && len(imports) == 0 {
- return nil, err
- }
- break
- }
- if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
- break
- }
- if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
- break
- }
- e, ok := t.(xml.StartElement)
- if !ok || !strings.EqualFold(e.Name.Local, "meta") {
- continue
- }
- if attrValue(e.Attr, "name") != "go-import" {
- continue
- }
- if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 {
- imports = append(imports, metaImport{
- Prefix: f[0],
- VCS: f[1],
- RepoRoot: f[2],
- })
- }
- }
-
- // Extract mod entries if we are paying attention to them.
- var list []metaImport
- var have map[string]bool
- if mod == PreferMod {
- have = make(map[string]bool)
- for _, m := range imports {
- if m.VCS == "mod" {
- have[m.Prefix] = true
- list = append(list, m)
- }
- }
- }
-
- // Append non-mod entries, ignoring those superseded by a mod entry.
- for _, m := range imports {
- if m.VCS != "mod" && !have[m.Prefix] {
- list = append(list, m)
- }
- }
- return list, nil
-}
-
-// attrValue returns the attribute value for the case-insensitive key
-// `name', or the empty string if nothing is found.
-func attrValue(attrs []xml.Attr, name string) string {
- for _, a := range attrs {
- if strings.EqualFold(a.Name.Local, name) {
- return a.Value
- }
- }
- return ""
-}
diff --git a/src/cmd/go/internal/get/get.go b/src/cmd/go/internal/get/get.go
index 9e4825eb37..3f7a66384a 100644
--- a/src/cmd/go/internal/get/get.go
+++ b/src/cmd/go/internal/get/get.go
@@ -18,6 +18,7 @@ import (
"cmd/go/internal/load"
"cmd/go/internal/search"
"cmd/go/internal/str"
+ "cmd/go/internal/vcs"
"cmd/go/internal/web"
"cmd/go/internal/work"
@@ -406,7 +407,7 @@ func download(arg string, parent *load.Package, stk *load.ImportStack, mode int)
// to make the first copy of or update a copy of the given package.
func downloadPackage(p *load.Package) error {
var (
- vcs *vcsCmd
+ vcsCmd *vcs.Cmd
repo, rootPath string
err error
blindRepo bool // set if the repo has unusual configuration
@@ -435,16 +436,16 @@ func downloadPackage(p *load.Package) error {
if p.Internal.Build.SrcRoot != "" {
// Directory exists. Look for checkout along path to src.
- vcs, rootPath, err = vcsFromDir(p.Dir, p.Internal.Build.SrcRoot)
+ vcsCmd, rootPath, err = vcs.FromDir(p.Dir, p.Internal.Build.SrcRoot)
if err != nil {
return err
}
repo = "" // should be unused; make distinctive
// Double-check where it came from.
- if *getU && vcs.remoteRepo != nil {
+ if *getU && vcsCmd.RemoteRepo != nil {
dir := filepath.Join(p.Internal.Build.SrcRoot, filepath.FromSlash(rootPath))
- remote, err := vcs.remoteRepo(vcs, dir)
+ remote, err := vcsCmd.RemoteRepo(vcsCmd, dir)
if err != nil {
// Proceed anyway. The package is present; we likely just don't understand
// the repo configuration (e.g. unusual remote protocol).
@@ -452,10 +453,10 @@ func downloadPackage(p *load.Package) error {
}
repo = remote
if !*getF && err == nil {
- if rr, err := RepoRootForImportPath(importPrefix, IgnoreMod, security); err == nil {
+ if rr, err := vcs.RepoRootForImportPath(importPrefix, vcs.IgnoreMod, security); err == nil {
repo := rr.Repo
- if rr.vcs.resolveRepo != nil {
- resolved, err := rr.vcs.resolveRepo(rr.vcs, dir, repo)
+ if rr.VCS.ResolveRepo != nil {
+ resolved, err := rr.VCS.ResolveRepo(rr.VCS, dir, repo)
if err == nil {
repo = resolved
}
@@ -469,13 +470,13 @@ func downloadPackage(p *load.Package) error {
} else {
// Analyze the import path to determine the version control system,
// repository, and the import path for the root of the repository.
- rr, err := RepoRootForImportPath(importPrefix, IgnoreMod, security)
+ rr, err := vcs.RepoRootForImportPath(importPrefix, vcs.IgnoreMod, security)
if err != nil {
return err
}
- vcs, repo, rootPath = rr.vcs, rr.Repo, rr.Root
+ vcsCmd, repo, rootPath = rr.VCS, rr.Repo, rr.Root
}
- if !blindRepo && !vcs.isSecure(repo) && security != web.Insecure {
+ if !blindRepo && !vcsCmd.IsSecure(repo) && security != web.Insecure {
return fmt.Errorf("cannot download, %v uses insecure protocol", repo)
}
@@ -498,7 +499,7 @@ func downloadPackage(p *load.Package) error {
}
root := filepath.Join(p.Internal.Build.SrcRoot, filepath.FromSlash(rootPath))
- if err := checkNestedVCS(vcs, root, p.Internal.Build.SrcRoot); err != nil {
+ if err := vcs.CheckNested(vcsCmd, root, p.Internal.Build.SrcRoot); err != nil {
return err
}
@@ -514,7 +515,7 @@ func downloadPackage(p *load.Package) error {
// Check that this is an appropriate place for the repo to be checked out.
// The target directory must either not exist or have a repo checked out already.
- meta := filepath.Join(root, "."+vcs.cmd)
+ meta := filepath.Join(root, "."+vcsCmd.Cmd)
if _, err := os.Stat(meta); err != nil {
// Metadata file or directory does not exist. Prepare to checkout new copy.
// Some version control tools require the target directory not to exist.
@@ -535,12 +536,12 @@ func downloadPackage(p *load.Package) error {
fmt.Fprintf(os.Stderr, "created GOPATH=%s; see 'go help gopath'\n", p.Internal.Build.Root)
}
- if err = vcs.create(root, repo); err != nil {
+ if err = vcsCmd.Create(root, repo); err != nil {
return err
}
} else {
// Metadata directory does exist; download incremental updates.
- if err = vcs.download(root); err != nil {
+ if err = vcsCmd.Download(root); err != nil {
return err
}
}
@@ -549,12 +550,12 @@ func downloadPackage(p *load.Package) error {
// Do not show tag sync in -n; it's noise more than anything,
// and since we're not running commands, no tag will be found.
// But avoid printing nothing.
- fmt.Fprintf(os.Stderr, "# cd %s; %s sync/update\n", root, vcs.cmd)
+ fmt.Fprintf(os.Stderr, "# cd %s; %s sync/update\n", root, vcsCmd.Cmd)
return nil
}
// Select and sync to appropriate version of the repository.
- tags, err := vcs.tags(root)
+ tags, err := vcsCmd.Tags(root)
if err != nil {
return err
}
@@ -562,7 +563,7 @@ func downloadPackage(p *load.Package) error {
if i := strings.Index(vers, " "); i >= 0 {
vers = vers[:i]
}
- if err := vcs.tagSync(root, selectTag(vers, tags)); err != nil {
+ if err := vcsCmd.TagSync(root, selectTag(vers, tags)); err != nil {
return err
}
diff --git a/src/cmd/go/internal/get/pkg_test.go b/src/cmd/go/internal/get/pkg_test.go
deleted file mode 100644
index fc6a179c2e..0000000000
--- a/src/cmd/go/internal/get/pkg_test.go
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2014 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.
-
-package get
-
-import (
- "cmd/go/internal/str"
- "reflect"
- "strings"
- "testing"
-)
-
-var foldDupTests = []struct {
- list []string
- f1, f2 string
-}{
- {str.StringList("math/rand", "math/big"), "", ""},
- {str.StringList("math", "strings"), "", ""},
- {str.StringList("strings"), "", ""},
- {str.StringList("strings", "strings"), "strings", "strings"},
- {str.StringList("Rand", "rand", "math", "math/rand", "math/Rand"), "Rand", "rand"},
-}
-
-func TestFoldDup(t *testing.T) {
- for _, tt := range foldDupTests {
- f1, f2 := str.FoldDup(tt.list)
- if f1 != tt.f1 || f2 != tt.f2 {
- t.Errorf("foldDup(%q) = %q, %q, want %q, %q", tt.list, f1, f2, tt.f1, tt.f2)
- }
- }
-}
-
-var parseMetaGoImportsTests = []struct {
- in string
- mod ModuleMode
- out []metaImport
-}{
- {
- ``,
- IgnoreMod,
- []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}},
- },
- {
- `
- `,
- IgnoreMod,
- []metaImport{
- {"foo/bar", "git", "https://github.com/rsc/foo/bar"},
- {"baz/quux", "git", "http://github.com/rsc/baz/quux"},
- },
- },
- {
- `
- `,
- IgnoreMod,
- []metaImport{
- {"foo/bar", "git", "https://github.com/rsc/foo/bar"},
- },
- },
- {
- `
- `,
- IgnoreMod,
- []metaImport{
- {"foo/bar", "git", "https://github.com/rsc/foo/bar"},
- },
- },
- {
- `
- `,
- PreferMod,
- []metaImport{
- {"foo/bar", "mod", "http://github.com/rsc/baz/quux"},
- },
- },
- {
- `
-
- `,
- IgnoreMod,
- []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}},
- },
- {
- `
- `,
- IgnoreMod,
- []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}},
- },
- {
- ``,
- IgnoreMod,
- []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}},
- },
- {
- // XML doesn't like .
- `
Page Not FoundDRAFT
`,
- IgnoreMod,
- []metaImport{{"chitin.io/chitin", "git", "https://github.com/chitin-io/chitin"}},
- },
- {
- `
-
- `,
- IgnoreMod,
- []metaImport{{"myitcv.io", "git", "https://github.com/myitcv/x"}},
- },
- {
- `
-
- `,
- PreferMod,
- []metaImport{
- {"myitcv.io/blah2", "mod", "https://raw.githubusercontent.com/myitcv/pubx/master"},
- {"myitcv.io", "git", "https://github.com/myitcv/x"},
- },
- },
-}
-
-func TestParseMetaGoImports(t *testing.T) {
- for i, tt := range parseMetaGoImportsTests {
- out, err := parseMetaGoImports(strings.NewReader(tt.in), tt.mod)
- if err != nil {
- t.Errorf("test#%d: %v", i, err)
- continue
- }
- if !reflect.DeepEqual(out, tt.out) {
- t.Errorf("test#%d:\n\thave %q\n\twant %q", i, out, tt.out)
- }
- }
-}
diff --git a/src/cmd/go/internal/get/vcs.go b/src/cmd/go/internal/get/vcs.go
deleted file mode 100644
index 24c32935d0..0000000000
--- a/src/cmd/go/internal/get/vcs.go
+++ /dev/null
@@ -1,1182 +0,0 @@
-// Copyright 2012 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.
-
-package get
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "internal/lazyregexp"
- "internal/singleflight"
- "log"
- urlpkg "net/url"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
- "strings"
- "sync"
-
- "cmd/go/internal/base"
- "cmd/go/internal/cfg"
- "cmd/go/internal/load"
- "cmd/go/internal/web"
-)
-
-// A vcsCmd describes how to use a version control system
-// like Mercurial, Git, or Subversion.
-type vcsCmd struct {
- name string
- cmd string // name of binary to invoke command
-
- createCmd []string // commands to download a fresh copy of a repository
- downloadCmd []string // commands to download updates into an existing repository
-
- tagCmd []tagCmd // commands to list tags
- tagLookupCmd []tagCmd // commands to lookup tags before running tagSyncCmd
- tagSyncCmd []string // commands to sync to specific tag
- tagSyncDefault []string // commands to sync to default tag
-
- scheme []string
- pingCmd string
-
- remoteRepo func(v *vcsCmd, rootDir string) (remoteRepo string, err error)
- resolveRepo func(v *vcsCmd, rootDir, remoteRepo string) (realRepo string, err error)
-}
-
-var defaultSecureScheme = map[string]bool{
- "https": true,
- "git+ssh": true,
- "bzr+ssh": true,
- "svn+ssh": true,
- "ssh": true,
-}
-
-func (v *vcsCmd) isSecure(repo string) bool {
- u, err := urlpkg.Parse(repo)
- if err != nil {
- // If repo is not a URL, it's not secure.
- return false
- }
- return v.isSecureScheme(u.Scheme)
-}
-
-func (v *vcsCmd) isSecureScheme(scheme string) bool {
- switch v.cmd {
- case "git":
- // GIT_ALLOW_PROTOCOL is an environment variable defined by Git. It is a
- // colon-separated list of schemes that are allowed to be used with git
- // fetch/clone. Any scheme not mentioned will be considered insecure.
- if allow := os.Getenv("GIT_ALLOW_PROTOCOL"); allow != "" {
- for _, s := range strings.Split(allow, ":") {
- if s == scheme {
- return true
- }
- }
- return false
- }
- }
- return defaultSecureScheme[scheme]
-}
-
-// A tagCmd describes a command to list available tags
-// that can be passed to tagSyncCmd.
-type tagCmd struct {
- cmd string // command to list tags
- pattern string // regexp to extract tags from list
-}
-
-// vcsList lists the known version control systems
-var vcsList = []*vcsCmd{
- vcsHg,
- vcsGit,
- vcsSvn,
- vcsBzr,
- vcsFossil,
-}
-
-// vcsByCmd returns the version control system for the given
-// command name (hg, git, svn, bzr).
-func vcsByCmd(cmd string) *vcsCmd {
- for _, vcs := range vcsList {
- if vcs.cmd == cmd {
- return vcs
- }
- }
- return nil
-}
-
-// vcsHg describes how to use Mercurial.
-var vcsHg = &vcsCmd{
- name: "Mercurial",
- cmd: "hg",
-
- createCmd: []string{"clone -U -- {repo} {dir}"},
- downloadCmd: []string{"pull"},
-
- // We allow both tag and branch names as 'tags'
- // for selecting a version. This lets people have
- // a go.release.r60 branch and a go1 branch
- // and make changes in both, without constantly
- // editing .hgtags.
- tagCmd: []tagCmd{
- {"tags", `^(\S+)`},
- {"branches", `^(\S+)`},
- },
- tagSyncCmd: []string{"update -r {tag}"},
- tagSyncDefault: []string{"update default"},
-
- scheme: []string{"https", "http", "ssh"},
- pingCmd: "identify -- {scheme}://{repo}",
- remoteRepo: hgRemoteRepo,
-}
-
-func hgRemoteRepo(vcsHg *vcsCmd, rootDir string) (remoteRepo string, err error) {
- out, err := vcsHg.runOutput(rootDir, "paths default")
- if err != nil {
- return "", err
- }
- return strings.TrimSpace(string(out)), nil
-}
-
-// vcsGit describes how to use Git.
-var vcsGit = &vcsCmd{
- name: "Git",
- cmd: "git",
-
- createCmd: []string{"clone -- {repo} {dir}", "-go-internal-cd {dir} submodule update --init --recursive"},
- downloadCmd: []string{"pull --ff-only", "submodule update --init --recursive"},
-
- tagCmd: []tagCmd{
- // tags/xxx matches a git tag named xxx
- // origin/xxx matches a git branch named xxx on the default remote repository
- {"show-ref", `(?:tags|origin)/(\S+)$`},
- },
- tagLookupCmd: []tagCmd{
- {"show-ref tags/{tag} origin/{tag}", `((?:tags|origin)/\S+)$`},
- },
- tagSyncCmd: []string{"checkout {tag}", "submodule update --init --recursive"},
- // both createCmd and downloadCmd update the working dir.
- // No need to do more here. We used to 'checkout master'
- // but that doesn't work if the default branch is not named master.
- // DO NOT add 'checkout master' here.
- // See golang.org/issue/9032.
- tagSyncDefault: []string{"submodule update --init --recursive"},
-
- scheme: []string{"git", "https", "http", "git+ssh", "ssh"},
-
- // Leave out the '--' separator in the ls-remote command: git 2.7.4 does not
- // support such a separator for that command, and this use should be safe
- // without it because the {scheme} value comes from the predefined list above.
- // See golang.org/issue/33836.
- pingCmd: "ls-remote {scheme}://{repo}",
-
- remoteRepo: gitRemoteRepo,
-}
-
-// scpSyntaxRe matches the SCP-like addresses used by Git to access
-// repositories by SSH.
-var scpSyntaxRe = lazyregexp.New(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)
-
-func gitRemoteRepo(vcsGit *vcsCmd, rootDir string) (remoteRepo string, err error) {
- cmd := "config remote.origin.url"
- errParse := errors.New("unable to parse output of git " + cmd)
- errRemoteOriginNotFound := errors.New("remote origin not found")
- outb, err := vcsGit.run1(rootDir, cmd, nil, false)
- if err != nil {
- // if it doesn't output any message, it means the config argument is correct,
- // but the config value itself doesn't exist
- if outb != nil && len(outb) == 0 {
- return "", errRemoteOriginNotFound
- }
- return "", err
- }
- out := strings.TrimSpace(string(outb))
-
- var repoURL *urlpkg.URL
- if m := scpSyntaxRe.FindStringSubmatch(out); m != nil {
- // Match SCP-like syntax and convert it to a URL.
- // Eg, "git@github.com:user/repo" becomes
- // "ssh://git@github.com/user/repo".
- repoURL = &urlpkg.URL{
- Scheme: "ssh",
- User: urlpkg.User(m[1]),
- Host: m[2],
- Path: m[3],
- }
- } else {
- repoURL, err = urlpkg.Parse(out)
- if err != nil {
- return "", err
- }
- }
-
- // Iterate over insecure schemes too, because this function simply
- // reports the state of the repo. If we can't see insecure schemes then
- // we can't report the actual repo URL.
- for _, s := range vcsGit.scheme {
- if repoURL.Scheme == s {
- return repoURL.String(), nil
- }
- }
- return "", errParse
-}
-
-// vcsBzr describes how to use Bazaar.
-var vcsBzr = &vcsCmd{
- name: "Bazaar",
- cmd: "bzr",
-
- createCmd: []string{"branch -- {repo} {dir}"},
-
- // Without --overwrite bzr will not pull tags that changed.
- // Replace by --overwrite-tags after http://pad.lv/681792 goes in.
- downloadCmd: []string{"pull --overwrite"},
-
- tagCmd: []tagCmd{{"tags", `^(\S+)`}},
- tagSyncCmd: []string{"update -r {tag}"},
- tagSyncDefault: []string{"update -r revno:-1"},
-
- scheme: []string{"https", "http", "bzr", "bzr+ssh"},
- pingCmd: "info -- {scheme}://{repo}",
- remoteRepo: bzrRemoteRepo,
- resolveRepo: bzrResolveRepo,
-}
-
-func bzrRemoteRepo(vcsBzr *vcsCmd, rootDir string) (remoteRepo string, err error) {
- outb, err := vcsBzr.runOutput(rootDir, "config parent_location")
- if err != nil {
- return "", err
- }
- return strings.TrimSpace(string(outb)), nil
-}
-
-func bzrResolveRepo(vcsBzr *vcsCmd, rootDir, remoteRepo string) (realRepo string, err error) {
- outb, err := vcsBzr.runOutput(rootDir, "info "+remoteRepo)
- if err != nil {
- return "", err
- }
- out := string(outb)
-
- // Expect:
- // ...
- // (branch root|repository branch):
- // ...
-
- found := false
- for _, prefix := range []string{"\n branch root: ", "\n repository branch: "} {
- i := strings.Index(out, prefix)
- if i >= 0 {
- out = out[i+len(prefix):]
- found = true
- break
- }
- }
- if !found {
- return "", fmt.Errorf("unable to parse output of bzr info")
- }
-
- i := strings.Index(out, "\n")
- if i < 0 {
- return "", fmt.Errorf("unable to parse output of bzr info")
- }
- out = out[:i]
- return strings.TrimSpace(out), nil
-}
-
-// vcsSvn describes how to use Subversion.
-var vcsSvn = &vcsCmd{
- name: "Subversion",
- cmd: "svn",
-
- createCmd: []string{"checkout -- {repo} {dir}"},
- downloadCmd: []string{"update"},
-
- // There is no tag command in subversion.
- // The branch information is all in the path names.
-
- scheme: []string{"https", "http", "svn", "svn+ssh"},
- pingCmd: "info -- {scheme}://{repo}",
- remoteRepo: svnRemoteRepo,
-}
-
-func svnRemoteRepo(vcsSvn *vcsCmd, rootDir string) (remoteRepo string, err error) {
- outb, err := vcsSvn.runOutput(rootDir, "info")
- if err != nil {
- return "", err
- }
- out := string(outb)
-
- // Expect:
- //
- // ...
- // URL:
- // ...
- //
- // Note that we're not using the Repository Root line,
- // because svn allows checking out subtrees.
- // The URL will be the URL of the subtree (what we used with 'svn co')
- // while the Repository Root may be a much higher parent.
- i := strings.Index(out, "\nURL: ")
- if i < 0 {
- return "", fmt.Errorf("unable to parse output of svn info")
- }
- out = out[i+len("\nURL: "):]
- i = strings.Index(out, "\n")
- if i < 0 {
- return "", fmt.Errorf("unable to parse output of svn info")
- }
- out = out[:i]
- return strings.TrimSpace(out), nil
-}
-
-// fossilRepoName is the name go get associates with a fossil repository. In the
-// real world the file can be named anything.
-const fossilRepoName = ".fossil"
-
-// vcsFossil describes how to use Fossil (fossil-scm.org)
-var vcsFossil = &vcsCmd{
- name: "Fossil",
- cmd: "fossil",
-
- createCmd: []string{"-go-internal-mkdir {dir} clone -- {repo} " + filepath.Join("{dir}", fossilRepoName), "-go-internal-cd {dir} open .fossil"},
- downloadCmd: []string{"up"},
-
- tagCmd: []tagCmd{{"tag ls", `(.*)`}},
- tagSyncCmd: []string{"up tag:{tag}"},
- tagSyncDefault: []string{"up trunk"},
-
- scheme: []string{"https", "http"},
- remoteRepo: fossilRemoteRepo,
-}
-
-func fossilRemoteRepo(vcsFossil *vcsCmd, rootDir string) (remoteRepo string, err error) {
- out, err := vcsFossil.runOutput(rootDir, "remote-url")
- if err != nil {
- return "", err
- }
- return strings.TrimSpace(string(out)), nil
-}
-
-func (v *vcsCmd) String() string {
- return v.name
-}
-
-// run runs the command line cmd in the given directory.
-// keyval is a list of key, value pairs. run expands
-// instances of {key} in cmd into value, but only after
-// splitting cmd into individual arguments.
-// If an error occurs, run prints the command line and the
-// command's combined stdout+stderr to standard error.
-// Otherwise run discards the command's output.
-func (v *vcsCmd) run(dir string, cmd string, keyval ...string) error {
- _, err := v.run1(dir, cmd, keyval, true)
- return err
-}
-
-// runVerboseOnly is like run but only generates error output to standard error in verbose mode.
-func (v *vcsCmd) runVerboseOnly(dir string, cmd string, keyval ...string) error {
- _, err := v.run1(dir, cmd, keyval, false)
- return err
-}
-
-// runOutput is like run but returns the output of the command.
-func (v *vcsCmd) runOutput(dir string, cmd string, keyval ...string) ([]byte, error) {
- return v.run1(dir, cmd, keyval, true)
-}
-
-// run1 is the generalized implementation of run and runOutput.
-func (v *vcsCmd) run1(dir string, cmdline string, keyval []string, verbose bool) ([]byte, error) {
- m := make(map[string]string)
- for i := 0; i < len(keyval); i += 2 {
- m[keyval[i]] = keyval[i+1]
- }
- args := strings.Fields(cmdline)
- for i, arg := range args {
- args[i] = expand(m, arg)
- }
-
- if len(args) >= 2 && args[0] == "-go-internal-mkdir" {
- var err error
- if filepath.IsAbs(args[1]) {
- err = os.Mkdir(args[1], os.ModePerm)
- } else {
- err = os.Mkdir(filepath.Join(dir, args[1]), os.ModePerm)
- }
- if err != nil {
- return nil, err
- }
- args = args[2:]
- }
-
- if len(args) >= 2 && args[0] == "-go-internal-cd" {
- if filepath.IsAbs(args[1]) {
- dir = args[1]
- } else {
- dir = filepath.Join(dir, args[1])
- }
- args = args[2:]
- }
-
- _, err := exec.LookPath(v.cmd)
- if err != nil {
- fmt.Fprintf(os.Stderr,
- "go: missing %s command. See https://golang.org/s/gogetcmd\n",
- v.name)
- return nil, err
- }
-
- cmd := exec.Command(v.cmd, args...)
- cmd.Dir = dir
- cmd.Env = base.AppendPWD(os.Environ(), cmd.Dir)
- if cfg.BuildX {
- fmt.Fprintf(os.Stderr, "cd %s\n", dir)
- fmt.Fprintf(os.Stderr, "%s %s\n", v.cmd, strings.Join(args, " "))
- }
- out, err := cmd.Output()
- if err != nil {
- if verbose || cfg.BuildV {
- fmt.Fprintf(os.Stderr, "# cd %s; %s %s\n", dir, v.cmd, strings.Join(args, " "))
- if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
- os.Stderr.Write(ee.Stderr)
- } else {
- fmt.Fprintf(os.Stderr, err.Error())
- }
- }
- }
- return out, err
-}
-
-// ping pings to determine scheme to use.
-func (v *vcsCmd) ping(scheme, repo string) error {
- return v.runVerboseOnly(".", v.pingCmd, "scheme", scheme, "repo", repo)
-}
-
-// create creates a new copy of repo in dir.
-// The parent of dir must exist; dir must not.
-func (v *vcsCmd) create(dir, repo string) error {
- for _, cmd := range v.createCmd {
- if err := v.run(".", cmd, "dir", dir, "repo", repo); err != nil {
- return err
- }
- }
- return nil
-}
-
-// download downloads any new changes for the repo in dir.
-func (v *vcsCmd) download(dir string) error {
- for _, cmd := range v.downloadCmd {
- if err := v.run(dir, cmd); err != nil {
- return err
- }
- }
- return nil
-}
-
-// tags returns the list of available tags for the repo in dir.
-func (v *vcsCmd) tags(dir string) ([]string, error) {
- var tags []string
- for _, tc := range v.tagCmd {
- out, err := v.runOutput(dir, tc.cmd)
- if err != nil {
- return nil, err
- }
- re := regexp.MustCompile(`(?m-s)` + tc.pattern)
- for _, m := range re.FindAllStringSubmatch(string(out), -1) {
- tags = append(tags, m[1])
- }
- }
- return tags, nil
-}
-
-// tagSync syncs the repo in dir to the named tag,
-// which either is a tag returned by tags or is v.tagDefault.
-func (v *vcsCmd) tagSync(dir, tag string) error {
- if v.tagSyncCmd == nil {
- return nil
- }
- if tag != "" {
- for _, tc := range v.tagLookupCmd {
- out, err := v.runOutput(dir, tc.cmd, "tag", tag)
- if err != nil {
- return err
- }
- re := regexp.MustCompile(`(?m-s)` + tc.pattern)
- m := re.FindStringSubmatch(string(out))
- if len(m) > 1 {
- tag = m[1]
- break
- }
- }
- }
-
- if tag == "" && v.tagSyncDefault != nil {
- for _, cmd := range v.tagSyncDefault {
- if err := v.run(dir, cmd); err != nil {
- return err
- }
- }
- return nil
- }
-
- for _, cmd := range v.tagSyncCmd {
- if err := v.run(dir, cmd, "tag", tag); err != nil {
- return err
- }
- }
- return nil
-}
-
-// A vcsPath describes how to convert an import path into a
-// version control system and repository name.
-type vcsPath struct {
- prefix string // prefix this description applies to
- regexp *lazyregexp.Regexp // compiled pattern for import path
- repo string // repository to use (expand with match of re)
- vcs string // version control system to use (expand with match of re)
- check func(match map[string]string) error // additional checks
- schemelessRepo bool // if true, the repo pattern lacks a scheme
-}
-
-// vcsFromDir inspects dir and its parents to determine the
-// version control system and code repository to use.
-// On return, root is the import path
-// corresponding to the root of the repository.
-func vcsFromDir(dir, srcRoot string) (vcs *vcsCmd, root string, err error) {
- // Clean and double-check that dir is in (a subdirectory of) srcRoot.
- dir = filepath.Clean(dir)
- srcRoot = filepath.Clean(srcRoot)
- if len(dir) <= len(srcRoot) || dir[len(srcRoot)] != filepath.Separator {
- return nil, "", fmt.Errorf("directory %q is outside source root %q", dir, srcRoot)
- }
-
- var vcsRet *vcsCmd
- var rootRet string
-
- origDir := dir
- for len(dir) > len(srcRoot) {
- for _, vcs := range vcsList {
- if _, err := os.Stat(filepath.Join(dir, "."+vcs.cmd)); err == nil {
- root := filepath.ToSlash(dir[len(srcRoot)+1:])
- // Record first VCS we find, but keep looking,
- // to detect mistakes like one kind of VCS inside another.
- if vcsRet == nil {
- vcsRet = vcs
- rootRet = root
- continue
- }
- // Allow .git inside .git, which can arise due to submodules.
- if vcsRet == vcs && vcs.cmd == "git" {
- continue
- }
- // Otherwise, we have one VCS inside a different VCS.
- return nil, "", fmt.Errorf("directory %q uses %s, but parent %q uses %s",
- filepath.Join(srcRoot, rootRet), vcsRet.cmd, filepath.Join(srcRoot, root), vcs.cmd)
- }
- }
-
- // Move to parent.
- ndir := filepath.Dir(dir)
- if len(ndir) >= len(dir) {
- // Shouldn't happen, but just in case, stop.
- break
- }
- dir = ndir
- }
-
- if vcsRet != nil {
- return vcsRet, rootRet, nil
- }
-
- return nil, "", fmt.Errorf("directory %q is not using a known version control system", origDir)
-}
-
-// checkNestedVCS checks for an incorrectly-nested VCS-inside-VCS
-// situation for dir, checking parents up until srcRoot.
-func checkNestedVCS(vcs *vcsCmd, dir, srcRoot string) error {
- if len(dir) <= len(srcRoot) || dir[len(srcRoot)] != filepath.Separator {
- return fmt.Errorf("directory %q is outside source root %q", dir, srcRoot)
- }
-
- otherDir := dir
- for len(otherDir) > len(srcRoot) {
- for _, otherVCS := range vcsList {
- if _, err := os.Stat(filepath.Join(otherDir, "."+otherVCS.cmd)); err == nil {
- // Allow expected vcs in original dir.
- if otherDir == dir && otherVCS == vcs {
- continue
- }
- // Allow .git inside .git, which can arise due to submodules.
- if otherVCS == vcs && vcs.cmd == "git" {
- continue
- }
- // Otherwise, we have one VCS inside a different VCS.
- return fmt.Errorf("directory %q uses %s, but parent %q uses %s", dir, vcs.cmd, otherDir, otherVCS.cmd)
- }
- }
- // Move to parent.
- newDir := filepath.Dir(otherDir)
- if len(newDir) >= len(otherDir) {
- // Shouldn't happen, but just in case, stop.
- break
- }
- otherDir = newDir
- }
-
- return nil
-}
-
-// RepoRoot describes the repository root for a tree of source code.
-type RepoRoot struct {
- Repo string // repository URL, including scheme
- Root string // import path corresponding to root of repo
- IsCustom bool // defined by served tags (as opposed to hard-coded pattern)
- VCS string // vcs type ("mod", "git", ...)
-
- vcs *vcsCmd // internal: vcs command access
-}
-
-func httpPrefix(s string) string {
- for _, prefix := range [...]string{"http:", "https:"} {
- if strings.HasPrefix(s, prefix) {
- return prefix
- }
- }
- return ""
-}
-
-// ModuleMode specifies whether to prefer modules when looking up code sources.
-type ModuleMode int
-
-const (
- IgnoreMod ModuleMode = iota
- PreferMod
-)
-
-// RepoRootForImportPath analyzes importPath to determine the
-// version control system, and code repository to use.
-func RepoRootForImportPath(importPath string, mod ModuleMode, security web.SecurityMode) (*RepoRoot, error) {
- rr, err := repoRootFromVCSPaths(importPath, security, vcsPaths)
- if err == errUnknownSite {
- rr, err = repoRootForImportDynamic(importPath, mod, security)
- if err != nil {
- err = load.ImportErrorf(importPath, "unrecognized import path %q: %v", importPath, err)
- }
- }
- if err != nil {
- rr1, err1 := repoRootFromVCSPaths(importPath, security, vcsPathsAfterDynamic)
- if err1 == nil {
- rr = rr1
- err = nil
- }
- }
-
- // Should have been taken care of above, but make sure.
- if err == nil && strings.Contains(importPath, "...") && strings.Contains(rr.Root, "...") {
- // Do not allow wildcards in the repo root.
- rr = nil
- err = load.ImportErrorf(importPath, "cannot expand ... in %q", importPath)
- }
- return rr, err
-}
-
-var errUnknownSite = errors.New("dynamic lookup required to find mapping")
-
-// repoRootFromVCSPaths attempts to map importPath to a repoRoot
-// using the mappings defined in vcsPaths.
-func repoRootFromVCSPaths(importPath string, security web.SecurityMode, vcsPaths []*vcsPath) (*RepoRoot, error) {
- // A common error is to use https://packagepath because that's what
- // hg and git require. Diagnose this helpfully.
- if prefix := httpPrefix(importPath); prefix != "" {
- // The importPath has been cleaned, so has only one slash. The pattern
- // ignores the slashes; the error message puts them back on the RHS at least.
- return nil, fmt.Errorf("%q not allowed in import path", prefix+"//")
- }
- for _, srv := range vcsPaths {
- if !strings.HasPrefix(importPath, srv.prefix) {
- continue
- }
- m := srv.regexp.FindStringSubmatch(importPath)
- if m == nil {
- if srv.prefix != "" {
- return nil, load.ImportErrorf(importPath, "invalid %s import path %q", srv.prefix, importPath)
- }
- continue
- }
-
- // Build map of named subexpression matches for expand.
- match := map[string]string{
- "prefix": srv.prefix,
- "import": importPath,
- }
- for i, name := range srv.regexp.SubexpNames() {
- if name != "" && match[name] == "" {
- match[name] = m[i]
- }
- }
- if srv.vcs != "" {
- match["vcs"] = expand(match, srv.vcs)
- }
- if srv.repo != "" {
- match["repo"] = expand(match, srv.repo)
- }
- if srv.check != nil {
- if err := srv.check(match); err != nil {
- return nil, err
- }
- }
- vcs := vcsByCmd(match["vcs"])
- if vcs == nil {
- return nil, fmt.Errorf("unknown version control system %q", match["vcs"])
- }
- var repoURL string
- if !srv.schemelessRepo {
- repoURL = match["repo"]
- } else {
- scheme := vcs.scheme[0] // default to first scheme
- repo := match["repo"]
- if vcs.pingCmd != "" {
- // If we know how to test schemes, scan to find one.
- for _, s := range vcs.scheme {
- if security == web.SecureOnly && !vcs.isSecureScheme(s) {
- continue
- }
- if vcs.ping(s, repo) == nil {
- scheme = s
- break
- }
- }
- }
- repoURL = scheme + "://" + repo
- }
- rr := &RepoRoot{
- Repo: repoURL,
- Root: match["root"],
- VCS: vcs.cmd,
- vcs: vcs,
- }
- return rr, nil
- }
- return nil, errUnknownSite
-}
-
-// urlForImportPath returns a partially-populated URL for the given Go import path.
-//
-// The URL leaves the Scheme field blank so that web.Get will try any scheme
-// allowed by the selected security mode.
-func urlForImportPath(importPath string) (*urlpkg.URL, error) {
- slash := strings.Index(importPath, "/")
- if slash < 0 {
- slash = len(importPath)
- }
- host, path := importPath[:slash], importPath[slash:]
- if !strings.Contains(host, ".") {
- return nil, errors.New("import path does not begin with hostname")
- }
- if len(path) == 0 {
- path = "/"
- }
- return &urlpkg.URL{Host: host, Path: path, RawQuery: "go-get=1"}, nil
-}
-
-// repoRootForImportDynamic finds a *RepoRoot for a custom domain that's not
-// statically known by repoRootForImportPathStatic.
-//
-// This handles custom import paths like "name.tld/pkg/foo" or just "name.tld".
-func repoRootForImportDynamic(importPath string, mod ModuleMode, security web.SecurityMode) (*RepoRoot, error) {
- url, err := urlForImportPath(importPath)
- if err != nil {
- return nil, err
- }
- resp, err := web.Get(security, url)
- if err != nil {
- msg := "https fetch: %v"
- if security == web.Insecure {
- msg = "http/" + msg
- }
- return nil, fmt.Errorf(msg, err)
- }
- body := resp.Body
- defer body.Close()
- imports, err := parseMetaGoImports(body, mod)
- if len(imports) == 0 {
- if respErr := resp.Err(); respErr != nil {
- // If the server's status was not OK, prefer to report that instead of
- // an XML parse error.
- return nil, respErr
- }
- }
- if err != nil {
- return nil, fmt.Errorf("parsing %s: %v", importPath, err)
- }
- // Find the matched meta import.
- mmi, err := matchGoImport(imports, importPath)
- if err != nil {
- if _, ok := err.(ImportMismatchError); !ok {
- return nil, fmt.Errorf("parse %s: %v", url, err)
- }
- return nil, fmt.Errorf("parse %s: no go-import meta tags (%s)", resp.URL, err)
- }
- if cfg.BuildV {
- log.Printf("get %q: found meta tag %#v at %s", importPath, mmi, url)
- }
- // If the import was "uni.edu/bob/project", which said the
- // prefix was "uni.edu" and the RepoRoot was "evilroot.com",
- // make sure we don't trust Bob and check out evilroot.com to
- // "uni.edu" yet (possibly overwriting/preempting another
- // non-evil student). Instead, first verify the root and see
- // if it matches Bob's claim.
- if mmi.Prefix != importPath {
- if cfg.BuildV {
- log.Printf("get %q: verifying non-authoritative meta tag", importPath)
- }
- var imports []metaImport
- url, imports, err = metaImportsForPrefix(mmi.Prefix, mod, security)
- if err != nil {
- return nil, err
- }
- metaImport2, err := matchGoImport(imports, importPath)
- if err != nil || mmi != metaImport2 {
- return nil, fmt.Errorf("%s and %s disagree about go-import for %s", resp.URL, url, mmi.Prefix)
- }
- }
-
- if err := validateRepoRoot(mmi.RepoRoot); err != nil {
- return nil, fmt.Errorf("%s: invalid repo root %q: %v", resp.URL, mmi.RepoRoot, err)
- }
- vcs := vcsByCmd(mmi.VCS)
- if vcs == nil && mmi.VCS != "mod" {
- return nil, fmt.Errorf("%s: unknown vcs %q", resp.URL, mmi.VCS)
- }
-
- rr := &RepoRoot{
- Repo: mmi.RepoRoot,
- Root: mmi.Prefix,
- IsCustom: true,
- VCS: mmi.VCS,
- vcs: vcs,
- }
- return rr, nil
-}
-
-// validateRepoRoot returns an error if repoRoot does not seem to be
-// a valid URL with scheme.
-func validateRepoRoot(repoRoot string) error {
- url, err := urlpkg.Parse(repoRoot)
- if err != nil {
- return err
- }
- if url.Scheme == "" {
- return errors.New("no scheme")
- }
- if url.Scheme == "file" {
- return errors.New("file scheme disallowed")
- }
- return nil
-}
-
-var fetchGroup singleflight.Group
-var (
- fetchCacheMu sync.Mutex
- fetchCache = map[string]fetchResult{} // key is metaImportsForPrefix's importPrefix
-)
-
-// metaImportsForPrefix takes a package's root import path as declared in a tag
-// and returns its HTML discovery URL and the parsed metaImport lines
-// found on the page.
-//
-// The importPath is of the form "golang.org/x/tools".
-// It is an error if no imports are found.
-// url will still be valid if err != nil.
-// The returned url will be of the form "https://golang.org/x/tools?go-get=1"
-func metaImportsForPrefix(importPrefix string, mod ModuleMode, security web.SecurityMode) (*urlpkg.URL, []metaImport, error) {
- setCache := func(res fetchResult) (fetchResult, error) {
- fetchCacheMu.Lock()
- defer fetchCacheMu.Unlock()
- fetchCache[importPrefix] = res
- return res, nil
- }
-
- resi, _, _ := fetchGroup.Do(importPrefix, func() (resi interface{}, err error) {
- fetchCacheMu.Lock()
- if res, ok := fetchCache[importPrefix]; ok {
- fetchCacheMu.Unlock()
- return res, nil
- }
- fetchCacheMu.Unlock()
-
- url, err := urlForImportPath(importPrefix)
- if err != nil {
- return setCache(fetchResult{err: err})
- }
- resp, err := web.Get(security, url)
- if err != nil {
- return setCache(fetchResult{url: url, err: fmt.Errorf("fetching %s: %v", importPrefix, err)})
- }
- body := resp.Body
- defer body.Close()
- imports, err := parseMetaGoImports(body, mod)
- if len(imports) == 0 {
- if respErr := resp.Err(); respErr != nil {
- // If the server's status was not OK, prefer to report that instead of
- // an XML parse error.
- return setCache(fetchResult{url: url, err: respErr})
- }
- }
- if err != nil {
- return setCache(fetchResult{url: url, err: fmt.Errorf("parsing %s: %v", resp.URL, err)})
- }
- if len(imports) == 0 {
- err = fmt.Errorf("fetching %s: no go-import meta tag found in %s", importPrefix, resp.URL)
- }
- return setCache(fetchResult{url: url, imports: imports, err: err})
- })
- res := resi.(fetchResult)
- return res.url, res.imports, res.err
-}
-
-type fetchResult struct {
- url *urlpkg.URL
- imports []metaImport
- err error
-}
-
-// metaImport represents the parsed tags from HTML files.
-type metaImport struct {
- Prefix, VCS, RepoRoot string
-}
-
-// pathPrefix reports whether sub is a prefix of s,
-// only considering entire path components.
-func pathPrefix(s, sub string) bool {
- // strings.HasPrefix is necessary but not sufficient.
- if !strings.HasPrefix(s, sub) {
- return false
- }
- // The remainder after the prefix must either be empty or start with a slash.
- rem := s[len(sub):]
- return rem == "" || rem[0] == '/'
-}
-
-// A ImportMismatchError is returned where metaImport/s are present
-// but none match our import path.
-type ImportMismatchError struct {
- importPath string
- mismatches []string // the meta imports that were discarded for not matching our importPath
-}
-
-func (m ImportMismatchError) Error() string {
- formattedStrings := make([]string, len(m.mismatches))
- for i, pre := range m.mismatches {
- formattedStrings[i] = fmt.Sprintf("meta tag %s did not match import path %s", pre, m.importPath)
- }
- return strings.Join(formattedStrings, ", ")
-}
-
-// matchGoImport returns the metaImport from imports matching importPath.
-// An error is returned if there are multiple matches.
-// An ImportMismatchError is returned if none match.
-func matchGoImport(imports []metaImport, importPath string) (metaImport, error) {
- match := -1
-
- errImportMismatch := ImportMismatchError{importPath: importPath}
- for i, im := range imports {
- if !pathPrefix(importPath, im.Prefix) {
- errImportMismatch.mismatches = append(errImportMismatch.mismatches, im.Prefix)
- continue
- }
-
- if match >= 0 {
- if imports[match].VCS == "mod" && im.VCS != "mod" {
- // All the mod entries precede all the non-mod entries.
- // We have a mod entry and don't care about the rest,
- // matching or not.
- break
- }
- return metaImport{}, fmt.Errorf("multiple meta tags match import path %q", importPath)
- }
- match = i
- }
-
- if match == -1 {
- return metaImport{}, errImportMismatch
- }
- return imports[match], nil
-}
-
-// expand rewrites s to replace {k} with match[k] for each key k in match.
-func expand(match map[string]string, s string) string {
- // We want to replace each match exactly once, and the result of expansion
- // must not depend on the iteration order through the map.
- // A strings.Replacer has exactly the properties we're looking for.
- oldNew := make([]string, 0, 2*len(match))
- for k, v := range match {
- oldNew = append(oldNew, "{"+k+"}", v)
- }
- return strings.NewReplacer(oldNew...).Replace(s)
-}
-
-// vcsPaths defines the meaning of import paths referring to
-// commonly-used VCS hosting sites (github.com/user/dir)
-// and import paths referring to a fully-qualified importPath
-// containing a VCS type (foo.com/repo.git/dir)
-var vcsPaths = []*vcsPath{
- // Github
- {
- prefix: "github.com/",
- regexp: lazyregexp.New(`^(?Pgithub\.com/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(/[A-Za-z0-9_.\-]+)*$`),
- vcs: "git",
- repo: "https://{root}",
- check: noVCSSuffix,
- },
-
- // Bitbucket
- {
- prefix: "bitbucket.org/",
- regexp: lazyregexp.New(`^(?Pbitbucket\.org/(?P[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`),
- repo: "https://{root}",
- check: bitbucketVCS,
- },
-
- // IBM DevOps Services (JazzHub)
- {
- prefix: "hub.jazz.net/git/",
- regexp: lazyregexp.New(`^(?Phub\.jazz\.net/git/[a-z0-9]+/[A-Za-z0-9_.\-]+)(/[A-Za-z0-9_.\-]+)*$`),
- vcs: "git",
- repo: "https://{root}",
- check: noVCSSuffix,
- },
-
- // Git at Apache
- {
- prefix: "git.apache.org/",
- regexp: lazyregexp.New(`^(?Pgit\.apache\.org/[a-z0-9_.\-]+\.git)(/[A-Za-z0-9_.\-]+)*$`),
- vcs: "git",
- repo: "https://{root}",
- },
-
- // Git at OpenStack
- {
- prefix: "git.openstack.org/",
- regexp: lazyregexp.New(`^(?Pgit\.openstack\.org/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(\.git)?(/[A-Za-z0-9_.\-]+)*$`),
- vcs: "git",
- repo: "https://{root}",
- },
-
- // chiselapp.com for fossil
- {
- prefix: "chiselapp.com/",
- regexp: lazyregexp.New(`^(?Pchiselapp\.com/user/[A-Za-z0-9]+/repository/[A-Za-z0-9_.\-]+)$`),
- vcs: "fossil",
- repo: "https://{root}",
- },
-
- // General syntax for any server.
- // Must be last.
- {
- regexp: lazyregexp.New(`(?P(?P([a-z0-9.\-]+\.)+[a-z0-9.\-]+(:[0-9]+)?(/~?[A-Za-z0-9_.\-]+)+?)\.(?Pbzr|fossil|git|hg|svn))(/~?[A-Za-z0-9_.\-]+)*$`),
- schemelessRepo: true,
- },
-}
-
-// vcsPathsAfterDynamic gives additional vcsPaths entries
-// to try after the dynamic HTML check.
-// This gives those sites a chance to introduce tags
-// as part of a graceful transition away from the hard-coded logic.
-var vcsPathsAfterDynamic = []*vcsPath{
- // Launchpad. See golang.org/issue/11436.
- {
- prefix: "launchpad.net/",
- regexp: lazyregexp.New(`^(?Plaunchpad\.net/((?P[A-Za-z0-9_.\-]+)(?P/[A-Za-z0-9_.\-]+)?|~[A-Za-z0-9_.\-]+/(\+junk|[A-Za-z0-9_.\-]+)/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`),
- vcs: "bzr",
- repo: "https://{root}",
- check: launchpadVCS,
- },
-}
-
-// noVCSSuffix checks that the repository name does not
-// end in .foo for any version control system foo.
-// The usual culprit is ".git".
-func noVCSSuffix(match map[string]string) error {
- repo := match["repo"]
- for _, vcs := range vcsList {
- if strings.HasSuffix(repo, "."+vcs.cmd) {
- return fmt.Errorf("invalid version control suffix in %s path", match["prefix"])
- }
- }
- return nil
-}
-
-// bitbucketVCS determines the version control system for a
-// Bitbucket repository, by using the Bitbucket API.
-func bitbucketVCS(match map[string]string) error {
- if err := noVCSSuffix(match); err != nil {
- return err
- }
-
- var resp struct {
- SCM string `json:"scm"`
- }
- url := &urlpkg.URL{
- Scheme: "https",
- Host: "api.bitbucket.org",
- Path: expand(match, "/2.0/repositories/{bitname}"),
- RawQuery: "fields=scm",
- }
- data, err := web.GetBytes(url)
- if err != nil {
- if httpErr, ok := err.(*web.HTTPError); ok && httpErr.StatusCode == 403 {
- // this may be a private repository. If so, attempt to determine which
- // VCS it uses. See issue 5375.
- root := match["root"]
- for _, vcs := range []string{"git", "hg"} {
- if vcsByCmd(vcs).ping("https", root) == nil {
- resp.SCM = vcs
- break
- }
- }
- }
-
- if resp.SCM == "" {
- return err
- }
- } else {
- if err := json.Unmarshal(data, &resp); err != nil {
- return fmt.Errorf("decoding %s: %v", url, err)
- }
- }
-
- if vcsByCmd(resp.SCM) != nil {
- match["vcs"] = resp.SCM
- if resp.SCM == "git" {
- match["repo"] += ".git"
- }
- return nil
- }
-
- return fmt.Errorf("unable to detect version control system for bitbucket.org/ path")
-}
-
-// launchpadVCS solves the ambiguity for "lp.net/project/foo". In this case,
-// "foo" could be a series name registered in Launchpad with its own branch,
-// and it could also be the name of a directory within the main project
-// branch one level up.
-func launchpadVCS(match map[string]string) error {
- if match["project"] == "" || match["series"] == "" {
- return nil
- }
- url := &urlpkg.URL{
- Scheme: "https",
- Host: "code.launchpad.net",
- Path: expand(match, "/{project}{series}/.bzr/branch-format"),
- }
- _, err := web.GetBytes(url)
- if err != nil {
- match["root"] = expand(match, "launchpad.net/{project}")
- match["repo"] = expand(match, "https://{root}")
- }
- return nil
-}
diff --git a/src/cmd/go/internal/get/vcs_test.go b/src/cmd/go/internal/get/vcs_test.go
deleted file mode 100644
index 195bc231eb..0000000000
--- a/src/cmd/go/internal/get/vcs_test.go
+++ /dev/null
@@ -1,475 +0,0 @@
-// Copyright 2014 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.
-
-package get
-
-import (
- "errors"
- "internal/testenv"
- "io/ioutil"
- "os"
- "path"
- "path/filepath"
- "testing"
-
- "cmd/go/internal/web"
-)
-
-// Test that RepoRootForImportPath determines the correct RepoRoot for a given importPath.
-// TODO(cmang): Add tests for SVN and BZR.
-func TestRepoRootForImportPath(t *testing.T) {
- testenv.MustHaveExternalNetwork(t)
-
- tests := []struct {
- path string
- want *RepoRoot
- }{
- {
- "github.com/golang/groupcache",
- &RepoRoot{
- vcs: vcsGit,
- Repo: "https://github.com/golang/groupcache",
- },
- },
- // Unicode letters in directories are not valid.
- {
- "github.com/user/unicode/испытание",
- nil,
- },
- // IBM DevOps Services tests
- {
- "hub.jazz.net/git/user1/pkgname",
- &RepoRoot{
- vcs: vcsGit,
- Repo: "https://hub.jazz.net/git/user1/pkgname",
- },
- },
- {
- "hub.jazz.net/git/user1/pkgname/submodule/submodule/submodule",
- &RepoRoot{
- vcs: vcsGit,
- Repo: "https://hub.jazz.net/git/user1/pkgname",
- },
- },
- {
- "hub.jazz.net",
- nil,
- },
- {
- "hubajazz.net",
- nil,
- },
- {
- "hub2.jazz.net",
- nil,
- },
- {
- "hub.jazz.net/someotherprefix",
- nil,
- },
- {
- "hub.jazz.net/someotherprefix/user1/pkgname",
- nil,
- },
- // Spaces are not valid in user names or package names
- {
- "hub.jazz.net/git/User 1/pkgname",
- nil,
- },
- {
- "hub.jazz.net/git/user1/pkg name",
- nil,
- },
- // Dots are not valid in user names
- {
- "hub.jazz.net/git/user.1/pkgname",
- nil,
- },
- {
- "hub.jazz.net/git/user/pkg.name",
- &RepoRoot{
- vcs: vcsGit,
- Repo: "https://hub.jazz.net/git/user/pkg.name",
- },
- },
- // User names cannot have uppercase letters
- {
- "hub.jazz.net/git/USER/pkgname",
- nil,
- },
- // OpenStack tests
- {
- "git.openstack.org/openstack/swift",
- &RepoRoot{
- vcs: vcsGit,
- Repo: "https://git.openstack.org/openstack/swift",
- },
- },
- // Trailing .git is less preferred but included for
- // compatibility purposes while the same source needs to
- // be compilable on both old and new go
- {
- "git.openstack.org/openstack/swift.git",
- &RepoRoot{
- vcs: vcsGit,
- Repo: "https://git.openstack.org/openstack/swift.git",
- },
- },
- {
- "git.openstack.org/openstack/swift/go/hummingbird",
- &RepoRoot{
- vcs: vcsGit,
- Repo: "https://git.openstack.org/openstack/swift",
- },
- },
- {
- "git.openstack.org",
- nil,
- },
- {
- "git.openstack.org/openstack",
- nil,
- },
- // Spaces are not valid in package name
- {
- "git.apache.org/package name/path/to/lib",
- nil,
- },
- // Should have ".git" suffix
- {
- "git.apache.org/package-name/path/to/lib",
- nil,
- },
- {
- "gitbapache.org",
- nil,
- },
- {
- "git.apache.org/package-name.git",
- &RepoRoot{
- vcs: vcsGit,
- Repo: "https://git.apache.org/package-name.git",
- },
- },
- {
- "git.apache.org/package-name_2.x.git/path/to/lib",
- &RepoRoot{
- vcs: vcsGit,
- Repo: "https://git.apache.org/package-name_2.x.git",
- },
- },
- {
- "chiselapp.com/user/kyle/repository/fossilgg",
- &RepoRoot{
- vcs: vcsFossil,
- Repo: "https://chiselapp.com/user/kyle/repository/fossilgg",
- },
- },
- {
- // must have a user/$name/repository/$repo path
- "chiselapp.com/kyle/repository/fossilgg",
- nil,
- },
- {
- "chiselapp.com/user/kyle/fossilgg",
- nil,
- },
- }
-
- for _, test := range tests {
- got, err := RepoRootForImportPath(test.path, IgnoreMod, web.SecureOnly)
- want := test.want
-
- if want == nil {
- if err == nil {
- t.Errorf("RepoRootForImportPath(%q): Error expected but not received", test.path)
- }
- continue
- }
- if err != nil {
- t.Errorf("RepoRootForImportPath(%q): %v", test.path, err)
- continue
- }
- if got.vcs.name != want.vcs.name || got.Repo != want.Repo {
- t.Errorf("RepoRootForImportPath(%q) = VCS(%s) Repo(%s), want VCS(%s) Repo(%s)", test.path, got.vcs, got.Repo, want.vcs, want.Repo)
- }
- }
-}
-
-// Test that vcsFromDir correctly inspects a given directory and returns the right VCS and root.
-func TestFromDir(t *testing.T) {
- tempDir, err := ioutil.TempDir("", "vcstest")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tempDir)
-
- for j, vcs := range vcsList {
- dir := filepath.Join(tempDir, "example.com", vcs.name, "."+vcs.cmd)
- if j&1 == 0 {
- err := os.MkdirAll(dir, 0755)
- if err != nil {
- t.Fatal(err)
- }
- } else {
- err := os.MkdirAll(filepath.Dir(dir), 0755)
- if err != nil {
- t.Fatal(err)
- }
- f, err := os.Create(dir)
- if err != nil {
- t.Fatal(err)
- }
- f.Close()
- }
-
- want := RepoRoot{
- vcs: vcs,
- Root: path.Join("example.com", vcs.name),
- }
- var got RepoRoot
- got.vcs, got.Root, err = vcsFromDir(dir, tempDir)
- if err != nil {
- t.Errorf("FromDir(%q, %q): %v", dir, tempDir, err)
- continue
- }
- if got.vcs.name != want.vcs.name || got.Root != want.Root {
- t.Errorf("FromDir(%q, %q) = VCS(%s) Root(%s), want VCS(%s) Root(%s)", dir, tempDir, got.vcs, got.Root, want.vcs, want.Root)
- }
- }
-}
-
-func TestIsSecure(t *testing.T) {
- tests := []struct {
- vcs *vcsCmd
- url string
- secure bool
- }{
- {vcsGit, "http://example.com/foo.git", false},
- {vcsGit, "https://example.com/foo.git", true},
- {vcsBzr, "http://example.com/foo.bzr", false},
- {vcsBzr, "https://example.com/foo.bzr", true},
- {vcsSvn, "http://example.com/svn", false},
- {vcsSvn, "https://example.com/svn", true},
- {vcsHg, "http://example.com/foo.hg", false},
- {vcsHg, "https://example.com/foo.hg", true},
- {vcsGit, "ssh://user@example.com/foo.git", true},
- {vcsGit, "user@server:path/to/repo.git", false},
- {vcsGit, "user@server:", false},
- {vcsGit, "server:repo.git", false},
- {vcsGit, "server:path/to/repo.git", false},
- {vcsGit, "example.com:path/to/repo.git", false},
- {vcsGit, "path/that/contains/a:colon/repo.git", false},
- {vcsHg, "ssh://user@example.com/path/to/repo.hg", true},
- {vcsFossil, "http://example.com/foo", false},
- {vcsFossil, "https://example.com/foo", true},
- }
-
- for _, test := range tests {
- secure := test.vcs.isSecure(test.url)
- if secure != test.secure {
- t.Errorf("%s isSecure(%q) = %t; want %t", test.vcs, test.url, secure, test.secure)
- }
- }
-}
-
-func TestIsSecureGitAllowProtocol(t *testing.T) {
- tests := []struct {
- vcs *vcsCmd
- url string
- secure bool
- }{
- // Same as TestIsSecure to verify same behavior.
- {vcsGit, "http://example.com/foo.git", false},
- {vcsGit, "https://example.com/foo.git", true},
- {vcsBzr, "http://example.com/foo.bzr", false},
- {vcsBzr, "https://example.com/foo.bzr", true},
- {vcsSvn, "http://example.com/svn", false},
- {vcsSvn, "https://example.com/svn", true},
- {vcsHg, "http://example.com/foo.hg", false},
- {vcsHg, "https://example.com/foo.hg", true},
- {vcsGit, "user@server:path/to/repo.git", false},
- {vcsGit, "user@server:", false},
- {vcsGit, "server:repo.git", false},
- {vcsGit, "server:path/to/repo.git", false},
- {vcsGit, "example.com:path/to/repo.git", false},
- {vcsGit, "path/that/contains/a:colon/repo.git", false},
- {vcsHg, "ssh://user@example.com/path/to/repo.hg", true},
- // New behavior.
- {vcsGit, "ssh://user@example.com/foo.git", false},
- {vcsGit, "foo://example.com/bar.git", true},
- {vcsHg, "foo://example.com/bar.hg", false},
- {vcsSvn, "foo://example.com/svn", false},
- {vcsBzr, "foo://example.com/bar.bzr", false},
- }
-
- defer os.Unsetenv("GIT_ALLOW_PROTOCOL")
- os.Setenv("GIT_ALLOW_PROTOCOL", "https:foo")
- for _, test := range tests {
- secure := test.vcs.isSecure(test.url)
- if secure != test.secure {
- t.Errorf("%s isSecure(%q) = %t; want %t", test.vcs, test.url, secure, test.secure)
- }
- }
-}
-
-func TestMatchGoImport(t *testing.T) {
- tests := []struct {
- imports []metaImport
- path string
- mi metaImport
- err error
- }{
- {
- imports: []metaImport{
- {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- },
- path: "example.com/user/foo",
- mi: metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- },
- {
- imports: []metaImport{
- {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- },
- path: "example.com/user/foo/",
- mi: metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- },
- {
- imports: []metaImport{
- {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- {Prefix: "example.com/user/fooa", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- },
- path: "example.com/user/foo",
- mi: metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- },
- {
- imports: []metaImport{
- {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- {Prefix: "example.com/user/fooa", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- },
- path: "example.com/user/fooa",
- mi: metaImport{Prefix: "example.com/user/fooa", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- },
- {
- imports: []metaImport{
- {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- {Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- },
- path: "example.com/user/foo/bar",
- err: errors.New("should not be allowed to create nested repo"),
- },
- {
- imports: []metaImport{
- {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- {Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- },
- path: "example.com/user/foo/bar/baz",
- err: errors.New("should not be allowed to create nested repo"),
- },
- {
- imports: []metaImport{
- {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- {Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- },
- path: "example.com/user/foo/bar/baz/qux",
- err: errors.New("should not be allowed to create nested repo"),
- },
- {
- imports: []metaImport{
- {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- {Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- },
- path: "example.com/user/foo/bar/baz/",
- err: errors.New("should not be allowed to create nested repo"),
- },
- {
- imports: []metaImport{
- {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- {Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- },
- path: "example.com",
- err: errors.New("pathologically short path"),
- },
- {
- imports: []metaImport{
- {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
- },
- path: "different.example.com/user/foo",
- err: errors.New("meta tags do not match import path"),
- },
- {
- imports: []metaImport{
- {Prefix: "myitcv.io/blah2", VCS: "mod", RepoRoot: "https://raw.githubusercontent.com/myitcv/pubx/master"},
- {Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
- },
- path: "myitcv.io/blah2/foo",
- mi: metaImport{Prefix: "myitcv.io/blah2", VCS: "mod", RepoRoot: "https://raw.githubusercontent.com/myitcv/pubx/master"},
- },
- {
- imports: []metaImport{
- {Prefix: "myitcv.io/blah2", VCS: "mod", RepoRoot: "https://raw.githubusercontent.com/myitcv/pubx/master"},
- {Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
- },
- path: "myitcv.io/other",
- mi: metaImport{Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
- },
- }
-
- for _, test := range tests {
- mi, err := matchGoImport(test.imports, test.path)
- if mi != test.mi {
- t.Errorf("unexpected metaImport; got %v, want %v", mi, test.mi)
- }
-
- got := err
- want := test.err
- if (got == nil) != (want == nil) {
- t.Errorf("unexpected error; got %v, want %v", got, want)
- }
- }
-}
-
-func TestValidateRepoRoot(t *testing.T) {
- tests := []struct {
- root string
- ok bool
- }{
- {
- root: "",
- ok: false,
- },
- {
- root: "http://",
- ok: true,
- },
- {
- root: "git+ssh://",
- ok: true,
- },
- {
- root: "http#://",
- ok: false,
- },
- {
- root: "-config",
- ok: false,
- },
- {
- root: "-config://",
- ok: false,
- },
- }
-
- for _, test := range tests {
- err := validateRepoRoot(test.root)
- ok := err == nil
- if ok != test.ok {
- want := "error"
- if test.ok {
- want = "nil"
- }
- t.Errorf("validateRepoRoot(%q) = %q, want %s", test.root, err, want)
- }
- }
-}
diff --git a/src/cmd/go/internal/modfetch/repo.go b/src/cmd/go/internal/modfetch/repo.go
index 34f805d58a..eed4dd4258 100644
--- a/src/cmd/go/internal/modfetch/repo.go
+++ b/src/cmd/go/internal/modfetch/repo.go
@@ -13,9 +13,9 @@ import (
"time"
"cmd/go/internal/cfg"
- "cmd/go/internal/get"
"cmd/go/internal/modfetch/codehost"
"cmd/go/internal/par"
+ "cmd/go/internal/vcs"
web "cmd/go/internal/web"
"golang.org/x/mod/module"
@@ -261,13 +261,13 @@ func lookupDirect(path string) (Repo, error) {
if allowInsecure(path) {
security = web.Insecure
}
- rr, err := get.RepoRootForImportPath(path, get.PreferMod, security)
+ rr, err := vcs.RepoRootForImportPath(path, vcs.PreferMod, security)
if err != nil {
// We don't know where to find code for a module with this path.
return nil, notExistError{err: err}
}
- if rr.VCS == "mod" {
+ if rr.VCS.Name == "mod" {
// Fetch module from proxy with base URL rr.Repo.
return newProxyRepo(rr.Repo, path)
}
@@ -279,8 +279,8 @@ func lookupDirect(path string) (Repo, error) {
return newCodeRepo(code, rr.Root, path)
}
-func lookupCodeRepo(rr *get.RepoRoot) (codehost.Repo, error) {
- code, err := codehost.NewRepo(rr.VCS, rr.Repo)
+func lookupCodeRepo(rr *vcs.RepoRoot) (codehost.Repo, error) {
+ code, err := codehost.NewRepo(rr.VCS.Cmd, rr.Repo)
if err != nil {
if _, ok := err.(*codehost.VCSError); ok {
return nil, err
@@ -306,7 +306,7 @@ func ImportRepoRev(path, rev string) (Repo, *RevInfo, error) {
if allowInsecure(path) {
security = web.Insecure
}
- rr, err := get.RepoRootForImportPath(path, get.IgnoreMod, security)
+ rr, err := vcs.RepoRootForImportPath(path, vcs.IgnoreMod, security)
if err != nil {
return nil, nil, err
}
diff --git a/src/cmd/go/internal/str/str_test.go b/src/cmd/go/internal/str/str_test.go
new file mode 100644
index 0000000000..147ce1a63e
--- /dev/null
+++ b/src/cmd/go/internal/str/str_test.go
@@ -0,0 +1,27 @@
+// Copyright 2020 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.
+
+package str
+
+import "testing"
+
+var foldDupTests = []struct {
+ list []string
+ f1, f2 string
+}{
+ {StringList("math/rand", "math/big"), "", ""},
+ {StringList("math", "strings"), "", ""},
+ {StringList("strings"), "", ""},
+ {StringList("strings", "strings"), "strings", "strings"},
+ {StringList("Rand", "rand", "math", "math/rand", "math/Rand"), "Rand", "rand"},
+}
+
+func TestFoldDup(t *testing.T) {
+ for _, tt := range foldDupTests {
+ f1, f2 := FoldDup(tt.list)
+ if f1 != tt.f1 || f2 != tt.f2 {
+ t.Errorf("foldDup(%q) = %q, %q, want %q, %q", tt.list, f1, f2, tt.f1, tt.f2)
+ }
+ }
+}
diff --git a/src/cmd/go/internal/vcs/discovery.go b/src/cmd/go/internal/vcs/discovery.go
new file mode 100644
index 0000000000..327b44cb9a
--- /dev/null
+++ b/src/cmd/go/internal/vcs/discovery.go
@@ -0,0 +1,97 @@
+// Copyright 2012 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.
+
+package vcs
+
+import (
+ "encoding/xml"
+ "fmt"
+ "io"
+ "strings"
+)
+
+// charsetReader returns a reader that converts from the given charset to UTF-8.
+// Currently it only supports UTF-8 and ASCII. Otherwise, it returns a meaningful
+// error which is printed by go get, so the user can find why the package
+// wasn't downloaded if the encoding is not supported. Note that, in
+// order to reduce potential errors, ASCII is treated as UTF-8 (i.e. characters
+// greater than 0x7f are not rejected).
+func charsetReader(charset string, input io.Reader) (io.Reader, error) {
+ switch strings.ToLower(charset) {
+ case "utf-8", "ascii":
+ return input, nil
+ default:
+ return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
+ }
+}
+
+// parseMetaGoImports returns meta imports from the HTML in r.
+// Parsing ends at the end of the section or the beginning of the .
+func parseMetaGoImports(r io.Reader, mod ModuleMode) ([]metaImport, error) {
+ d := xml.NewDecoder(r)
+ d.CharsetReader = charsetReader
+ d.Strict = false
+ var imports []metaImport
+ for {
+ t, err := d.RawToken()
+ if err != nil {
+ if err != io.EOF && len(imports) == 0 {
+ return nil, err
+ }
+ break
+ }
+ if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
+ break
+ }
+ if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
+ break
+ }
+ e, ok := t.(xml.StartElement)
+ if !ok || !strings.EqualFold(e.Name.Local, "meta") {
+ continue
+ }
+ if attrValue(e.Attr, "name") != "go-import" {
+ continue
+ }
+ if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 {
+ imports = append(imports, metaImport{
+ Prefix: f[0],
+ VCS: f[1],
+ RepoRoot: f[2],
+ })
+ }
+ }
+
+ // Extract mod entries if we are paying attention to them.
+ var list []metaImport
+ var have map[string]bool
+ if mod == PreferMod {
+ have = make(map[string]bool)
+ for _, m := range imports {
+ if m.VCS == "mod" {
+ have[m.Prefix] = true
+ list = append(list, m)
+ }
+ }
+ }
+
+ // Append non-mod entries, ignoring those superseded by a mod entry.
+ for _, m := range imports {
+ if m.VCS != "mod" && !have[m.Prefix] {
+ list = append(list, m)
+ }
+ }
+ return list, nil
+}
+
+// attrValue returns the attribute value for the case-insensitive key
+// `name', or the empty string if nothing is found.
+func attrValue(attrs []xml.Attr, name string) string {
+ for _, a := range attrs {
+ if strings.EqualFold(a.Name.Local, name) {
+ return a.Value
+ }
+ }
+ return ""
+}
diff --git a/src/cmd/go/internal/vcs/discovery_test.go b/src/cmd/go/internal/vcs/discovery_test.go
new file mode 100644
index 0000000000..eb99fdf64c
--- /dev/null
+++ b/src/cmd/go/internal/vcs/discovery_test.go
@@ -0,0 +1,110 @@
+// Copyright 2014 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.
+
+package vcs
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+var parseMetaGoImportsTests = []struct {
+ in string
+ mod ModuleMode
+ out []metaImport
+}{
+ {
+ ``,
+ IgnoreMod,
+ []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}},
+ },
+ {
+ `
+ `,
+ IgnoreMod,
+ []metaImport{
+ {"foo/bar", "git", "https://github.com/rsc/foo/bar"},
+ {"baz/quux", "git", "http://github.com/rsc/baz/quux"},
+ },
+ },
+ {
+ `
+ `,
+ IgnoreMod,
+ []metaImport{
+ {"foo/bar", "git", "https://github.com/rsc/foo/bar"},
+ },
+ },
+ {
+ `
+ `,
+ IgnoreMod,
+ []metaImport{
+ {"foo/bar", "git", "https://github.com/rsc/foo/bar"},
+ },
+ },
+ {
+ `
+ `,
+ PreferMod,
+ []metaImport{
+ {"foo/bar", "mod", "http://github.com/rsc/baz/quux"},
+ },
+ },
+ {
+ `
+
+ `,
+ IgnoreMod,
+ []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}},
+ },
+ {
+ `
+ `,
+ IgnoreMod,
+ []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}},
+ },
+ {
+ ``,
+ IgnoreMod,
+ []metaImport{{"foo/bar", "git", "https://github.com/rsc/foo/bar"}},
+ },
+ {
+ // XML doesn't like .
+ `
Page Not FoundDRAFT
`,
+ IgnoreMod,
+ []metaImport{{"chitin.io/chitin", "git", "https://github.com/chitin-io/chitin"}},
+ },
+ {
+ `
+
+ `,
+ IgnoreMod,
+ []metaImport{{"myitcv.io", "git", "https://github.com/myitcv/x"}},
+ },
+ {
+ `
+
+ `,
+ PreferMod,
+ []metaImport{
+ {"myitcv.io/blah2", "mod", "https://raw.githubusercontent.com/myitcv/pubx/master"},
+ {"myitcv.io", "git", "https://github.com/myitcv/x"},
+ },
+ },
+}
+
+func TestParseMetaGoImports(t *testing.T) {
+ for i, tt := range parseMetaGoImportsTests {
+ out, err := parseMetaGoImports(strings.NewReader(tt.in), tt.mod)
+ if err != nil {
+ t.Errorf("test#%d: %v", i, err)
+ continue
+ }
+ if !reflect.DeepEqual(out, tt.out) {
+ t.Errorf("test#%d:\n\thave %q\n\twant %q", i, out, tt.out)
+ }
+ }
+}
diff --git a/src/cmd/go/internal/vcs/vcs.go b/src/cmd/go/internal/vcs/vcs.go
new file mode 100644
index 0000000000..e535998d89
--- /dev/null
+++ b/src/cmd/go/internal/vcs/vcs.go
@@ -0,0 +1,1187 @@
+// Copyright 2012 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.
+
+package vcs
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "internal/lazyregexp"
+ "internal/singleflight"
+ "log"
+ urlpkg "net/url"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "sync"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/load"
+ "cmd/go/internal/web"
+)
+
+// A vcsCmd describes how to use a version control system
+// like Mercurial, Git, or Subversion.
+type Cmd struct {
+ Name string
+ Cmd string // name of binary to invoke command
+
+ CreateCmd []string // commands to download a fresh copy of a repository
+ DownloadCmd []string // commands to download updates into an existing repository
+
+ TagCmd []tagCmd // commands to list tags
+ TagLookupCmd []tagCmd // commands to lookup tags before running tagSyncCmd
+ TagSyncCmd []string // commands to sync to specific tag
+ TagSyncDefault []string // commands to sync to default tag
+
+ Scheme []string
+ PingCmd string
+
+ RemoteRepo func(v *Cmd, rootDir string) (remoteRepo string, err error)
+ ResolveRepo func(v *Cmd, rootDir, remoteRepo string) (realRepo string, err error)
+}
+
+var defaultSecureScheme = map[string]bool{
+ "https": true,
+ "git+ssh": true,
+ "bzr+ssh": true,
+ "svn+ssh": true,
+ "ssh": true,
+}
+
+func (v *Cmd) IsSecure(repo string) bool {
+ u, err := urlpkg.Parse(repo)
+ if err != nil {
+ // If repo is not a URL, it's not secure.
+ return false
+ }
+ return v.isSecureScheme(u.Scheme)
+}
+
+func (v *Cmd) isSecureScheme(scheme string) bool {
+ switch v.Cmd {
+ case "git":
+ // GIT_ALLOW_PROTOCOL is an environment variable defined by Git. It is a
+ // colon-separated list of schemes that are allowed to be used with git
+ // fetch/clone. Any scheme not mentioned will be considered insecure.
+ if allow := os.Getenv("GIT_ALLOW_PROTOCOL"); allow != "" {
+ for _, s := range strings.Split(allow, ":") {
+ if s == scheme {
+ return true
+ }
+ }
+ return false
+ }
+ }
+ return defaultSecureScheme[scheme]
+}
+
+// A tagCmd describes a command to list available tags
+// that can be passed to tagSyncCmd.
+type tagCmd struct {
+ cmd string // command to list tags
+ pattern string // regexp to extract tags from list
+}
+
+// vcsList lists the known version control systems
+var vcsList = []*Cmd{
+ vcsHg,
+ vcsGit,
+ vcsSvn,
+ vcsBzr,
+ vcsFossil,
+}
+
+// vcsMod is a stub for the "mod" scheme. It's returned by
+// repoRootForImportPathDynamic, but is otherwise not treated as a VCS command.
+var vcsMod = &Cmd{Name: "mod"}
+
+// vcsByCmd returns the version control system for the given
+// command name (hg, git, svn, bzr).
+func vcsByCmd(cmd string) *Cmd {
+ for _, vcs := range vcsList {
+ if vcs.Cmd == cmd {
+ return vcs
+ }
+ }
+ return nil
+}
+
+// vcsHg describes how to use Mercurial.
+var vcsHg = &Cmd{
+ Name: "Mercurial",
+ Cmd: "hg",
+
+ CreateCmd: []string{"clone -U -- {repo} {dir}"},
+ DownloadCmd: []string{"pull"},
+
+ // We allow both tag and branch names as 'tags'
+ // for selecting a version. This lets people have
+ // a go.release.r60 branch and a go1 branch
+ // and make changes in both, without constantly
+ // editing .hgtags.
+ TagCmd: []tagCmd{
+ {"tags", `^(\S+)`},
+ {"branches", `^(\S+)`},
+ },
+ TagSyncCmd: []string{"update -r {tag}"},
+ TagSyncDefault: []string{"update default"},
+
+ Scheme: []string{"https", "http", "ssh"},
+ PingCmd: "identify -- {scheme}://{repo}",
+ RemoteRepo: hgRemoteRepo,
+}
+
+func hgRemoteRepo(vcsHg *Cmd, rootDir string) (remoteRepo string, err error) {
+ out, err := vcsHg.runOutput(rootDir, "paths default")
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSpace(string(out)), nil
+}
+
+// vcsGit describes how to use Git.
+var vcsGit = &Cmd{
+ Name: "Git",
+ Cmd: "git",
+
+ CreateCmd: []string{"clone -- {repo} {dir}", "-go-internal-cd {dir} submodule update --init --recursive"},
+ DownloadCmd: []string{"pull --ff-only", "submodule update --init --recursive"},
+
+ TagCmd: []tagCmd{
+ // tags/xxx matches a git tag named xxx
+ // origin/xxx matches a git branch named xxx on the default remote repository
+ {"show-ref", `(?:tags|origin)/(\S+)$`},
+ },
+ TagLookupCmd: []tagCmd{
+ {"show-ref tags/{tag} origin/{tag}", `((?:tags|origin)/\S+)$`},
+ },
+ TagSyncCmd: []string{"checkout {tag}", "submodule update --init --recursive"},
+ // both createCmd and downloadCmd update the working dir.
+ // No need to do more here. We used to 'checkout master'
+ // but that doesn't work if the default branch is not named master.
+ // DO NOT add 'checkout master' here.
+ // See golang.org/issue/9032.
+ TagSyncDefault: []string{"submodule update --init --recursive"},
+
+ Scheme: []string{"git", "https", "http", "git+ssh", "ssh"},
+
+ // Leave out the '--' separator in the ls-remote command: git 2.7.4 does not
+ // support such a separator for that command, and this use should be safe
+ // without it because the {scheme} value comes from the predefined list above.
+ // See golang.org/issue/33836.
+ PingCmd: "ls-remote {scheme}://{repo}",
+
+ RemoteRepo: gitRemoteRepo,
+}
+
+// scpSyntaxRe matches the SCP-like addresses used by Git to access
+// repositories by SSH.
+var scpSyntaxRe = lazyregexp.New(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)
+
+func gitRemoteRepo(vcsGit *Cmd, rootDir string) (remoteRepo string, err error) {
+ cmd := "config remote.origin.url"
+ errParse := errors.New("unable to parse output of git " + cmd)
+ errRemoteOriginNotFound := errors.New("remote origin not found")
+ outb, err := vcsGit.run1(rootDir, cmd, nil, false)
+ if err != nil {
+ // if it doesn't output any message, it means the config argument is correct,
+ // but the config value itself doesn't exist
+ if outb != nil && len(outb) == 0 {
+ return "", errRemoteOriginNotFound
+ }
+ return "", err
+ }
+ out := strings.TrimSpace(string(outb))
+
+ var repoURL *urlpkg.URL
+ if m := scpSyntaxRe.FindStringSubmatch(out); m != nil {
+ // Match SCP-like syntax and convert it to a URL.
+ // Eg, "git@github.com:user/repo" becomes
+ // "ssh://git@github.com/user/repo".
+ repoURL = &urlpkg.URL{
+ Scheme: "ssh",
+ User: urlpkg.User(m[1]),
+ Host: m[2],
+ Path: m[3],
+ }
+ } else {
+ repoURL, err = urlpkg.Parse(out)
+ if err != nil {
+ return "", err
+ }
+ }
+
+ // Iterate over insecure schemes too, because this function simply
+ // reports the state of the repo. If we can't see insecure schemes then
+ // we can't report the actual repo URL.
+ for _, s := range vcsGit.Scheme {
+ if repoURL.Scheme == s {
+ return repoURL.String(), nil
+ }
+ }
+ return "", errParse
+}
+
+// vcsBzr describes how to use Bazaar.
+var vcsBzr = &Cmd{
+ Name: "Bazaar",
+ Cmd: "bzr",
+
+ CreateCmd: []string{"branch -- {repo} {dir}"},
+
+ // Without --overwrite bzr will not pull tags that changed.
+ // Replace by --overwrite-tags after http://pad.lv/681792 goes in.
+ DownloadCmd: []string{"pull --overwrite"},
+
+ TagCmd: []tagCmd{{"tags", `^(\S+)`}},
+ TagSyncCmd: []string{"update -r {tag}"},
+ TagSyncDefault: []string{"update -r revno:-1"},
+
+ Scheme: []string{"https", "http", "bzr", "bzr+ssh"},
+ PingCmd: "info -- {scheme}://{repo}",
+ RemoteRepo: bzrRemoteRepo,
+ ResolveRepo: bzrResolveRepo,
+}
+
+func bzrRemoteRepo(vcsBzr *Cmd, rootDir string) (remoteRepo string, err error) {
+ outb, err := vcsBzr.runOutput(rootDir, "config parent_location")
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSpace(string(outb)), nil
+}
+
+func bzrResolveRepo(vcsBzr *Cmd, rootDir, remoteRepo string) (realRepo string, err error) {
+ outb, err := vcsBzr.runOutput(rootDir, "info "+remoteRepo)
+ if err != nil {
+ return "", err
+ }
+ out := string(outb)
+
+ // Expect:
+ // ...
+ // (branch root|repository branch):
+ // ...
+
+ found := false
+ for _, prefix := range []string{"\n branch root: ", "\n repository branch: "} {
+ i := strings.Index(out, prefix)
+ if i >= 0 {
+ out = out[i+len(prefix):]
+ found = true
+ break
+ }
+ }
+ if !found {
+ return "", fmt.Errorf("unable to parse output of bzr info")
+ }
+
+ i := strings.Index(out, "\n")
+ if i < 0 {
+ return "", fmt.Errorf("unable to parse output of bzr info")
+ }
+ out = out[:i]
+ return strings.TrimSpace(out), nil
+}
+
+// vcsSvn describes how to use Subversion.
+var vcsSvn = &Cmd{
+ Name: "Subversion",
+ Cmd: "svn",
+
+ CreateCmd: []string{"checkout -- {repo} {dir}"},
+ DownloadCmd: []string{"update"},
+
+ // There is no tag command in subversion.
+ // The branch information is all in the path names.
+
+ Scheme: []string{"https", "http", "svn", "svn+ssh"},
+ PingCmd: "info -- {scheme}://{repo}",
+ RemoteRepo: svnRemoteRepo,
+}
+
+func svnRemoteRepo(vcsSvn *Cmd, rootDir string) (remoteRepo string, err error) {
+ outb, err := vcsSvn.runOutput(rootDir, "info")
+ if err != nil {
+ return "", err
+ }
+ out := string(outb)
+
+ // Expect:
+ //
+ // ...
+ // URL:
+ // ...
+ //
+ // Note that we're not using the Repository Root line,
+ // because svn allows checking out subtrees.
+ // The URL will be the URL of the subtree (what we used with 'svn co')
+ // while the Repository Root may be a much higher parent.
+ i := strings.Index(out, "\nURL: ")
+ if i < 0 {
+ return "", fmt.Errorf("unable to parse output of svn info")
+ }
+ out = out[i+len("\nURL: "):]
+ i = strings.Index(out, "\n")
+ if i < 0 {
+ return "", fmt.Errorf("unable to parse output of svn info")
+ }
+ out = out[:i]
+ return strings.TrimSpace(out), nil
+}
+
+// fossilRepoName is the name go get associates with a fossil repository. In the
+// real world the file can be named anything.
+const fossilRepoName = ".fossil"
+
+// vcsFossil describes how to use Fossil (fossil-scm.org)
+var vcsFossil = &Cmd{
+ Name: "Fossil",
+ Cmd: "fossil",
+
+ CreateCmd: []string{"-go-internal-mkdir {dir} clone -- {repo} " + filepath.Join("{dir}", fossilRepoName), "-go-internal-cd {dir} open .fossil"},
+ DownloadCmd: []string{"up"},
+
+ TagCmd: []tagCmd{{"tag ls", `(.*)`}},
+ TagSyncCmd: []string{"up tag:{tag}"},
+ TagSyncDefault: []string{"up trunk"},
+
+ Scheme: []string{"https", "http"},
+ RemoteRepo: fossilRemoteRepo,
+}
+
+func fossilRemoteRepo(vcsFossil *Cmd, rootDir string) (remoteRepo string, err error) {
+ out, err := vcsFossil.runOutput(rootDir, "remote-url")
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSpace(string(out)), nil
+}
+
+func (v *Cmd) String() string {
+ return v.Name
+}
+
+// run runs the command line cmd in the given directory.
+// keyval is a list of key, value pairs. run expands
+// instances of {key} in cmd into value, but only after
+// splitting cmd into individual arguments.
+// If an error occurs, run prints the command line and the
+// command's combined stdout+stderr to standard error.
+// Otherwise run discards the command's output.
+func (v *Cmd) run(dir string, cmd string, keyval ...string) error {
+ _, err := v.run1(dir, cmd, keyval, true)
+ return err
+}
+
+// runVerboseOnly is like run but only generates error output to standard error in verbose mode.
+func (v *Cmd) runVerboseOnly(dir string, cmd string, keyval ...string) error {
+ _, err := v.run1(dir, cmd, keyval, false)
+ return err
+}
+
+// runOutput is like run but returns the output of the command.
+func (v *Cmd) runOutput(dir string, cmd string, keyval ...string) ([]byte, error) {
+ return v.run1(dir, cmd, keyval, true)
+}
+
+// run1 is the generalized implementation of run and runOutput.
+func (v *Cmd) run1(dir string, cmdline string, keyval []string, verbose bool) ([]byte, error) {
+ m := make(map[string]string)
+ for i := 0; i < len(keyval); i += 2 {
+ m[keyval[i]] = keyval[i+1]
+ }
+ args := strings.Fields(cmdline)
+ for i, arg := range args {
+ args[i] = expand(m, arg)
+ }
+
+ if len(args) >= 2 && args[0] == "-go-internal-mkdir" {
+ var err error
+ if filepath.IsAbs(args[1]) {
+ err = os.Mkdir(args[1], os.ModePerm)
+ } else {
+ err = os.Mkdir(filepath.Join(dir, args[1]), os.ModePerm)
+ }
+ if err != nil {
+ return nil, err
+ }
+ args = args[2:]
+ }
+
+ if len(args) >= 2 && args[0] == "-go-internal-cd" {
+ if filepath.IsAbs(args[1]) {
+ dir = args[1]
+ } else {
+ dir = filepath.Join(dir, args[1])
+ }
+ args = args[2:]
+ }
+
+ _, err := exec.LookPath(v.Cmd)
+ if err != nil {
+ fmt.Fprintf(os.Stderr,
+ "go: missing %s command. See https://golang.org/s/gogetcmd\n",
+ v.Name)
+ return nil, err
+ }
+
+ cmd := exec.Command(v.Cmd, args...)
+ cmd.Dir = dir
+ cmd.Env = base.AppendPWD(os.Environ(), cmd.Dir)
+ if cfg.BuildX {
+ fmt.Fprintf(os.Stderr, "cd %s\n", dir)
+ fmt.Fprintf(os.Stderr, "%s %s\n", v.Cmd, strings.Join(args, " "))
+ }
+ out, err := cmd.Output()
+ if err != nil {
+ if verbose || cfg.BuildV {
+ fmt.Fprintf(os.Stderr, "# cd %s; %s %s\n", dir, v.Cmd, strings.Join(args, " "))
+ if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
+ os.Stderr.Write(ee.Stderr)
+ } else {
+ fmt.Fprintf(os.Stderr, err.Error())
+ }
+ }
+ }
+ return out, err
+}
+
+// Ping pings to determine scheme to use.
+func (v *Cmd) Ping(scheme, repo string) error {
+ return v.runVerboseOnly(".", v.PingCmd, "scheme", scheme, "repo", repo)
+}
+
+// Create creates a new copy of repo in dir.
+// The parent of dir must exist; dir must not.
+func (v *Cmd) Create(dir, repo string) error {
+ for _, cmd := range v.CreateCmd {
+ if err := v.run(".", cmd, "dir", dir, "repo", repo); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Download downloads any new changes for the repo in dir.
+func (v *Cmd) Download(dir string) error {
+ for _, cmd := range v.DownloadCmd {
+ if err := v.run(dir, cmd); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Tags returns the list of available tags for the repo in dir.
+func (v *Cmd) Tags(dir string) ([]string, error) {
+ var tags []string
+ for _, tc := range v.TagCmd {
+ out, err := v.runOutput(dir, tc.cmd)
+ if err != nil {
+ return nil, err
+ }
+ re := regexp.MustCompile(`(?m-s)` + tc.pattern)
+ for _, m := range re.FindAllStringSubmatch(string(out), -1) {
+ tags = append(tags, m[1])
+ }
+ }
+ return tags, nil
+}
+
+// tagSync syncs the repo in dir to the named tag,
+// which either is a tag returned by tags or is v.tagDefault.
+func (v *Cmd) TagSync(dir, tag string) error {
+ if v.TagSyncCmd == nil {
+ return nil
+ }
+ if tag != "" {
+ for _, tc := range v.TagLookupCmd {
+ out, err := v.runOutput(dir, tc.cmd, "tag", tag)
+ if err != nil {
+ return err
+ }
+ re := regexp.MustCompile(`(?m-s)` + tc.pattern)
+ m := re.FindStringSubmatch(string(out))
+ if len(m) > 1 {
+ tag = m[1]
+ break
+ }
+ }
+ }
+
+ if tag == "" && v.TagSyncDefault != nil {
+ for _, cmd := range v.TagSyncDefault {
+ if err := v.run(dir, cmd); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
+ for _, cmd := range v.TagSyncCmd {
+ if err := v.run(dir, cmd, "tag", tag); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// A vcsPath describes how to convert an import path into a
+// version control system and repository name.
+type vcsPath struct {
+ prefix string // prefix this description applies to
+ regexp *lazyregexp.Regexp // compiled pattern for import path
+ repo string // repository to use (expand with match of re)
+ vcs string // version control system to use (expand with match of re)
+ check func(match map[string]string) error // additional checks
+ schemelessRepo bool // if true, the repo pattern lacks a scheme
+}
+
+// FromDir inspects dir and its parents to determine the
+// version control system and code repository to use.
+// On return, root is the import path
+// corresponding to the root of the repository.
+func FromDir(dir, srcRoot string) (vcs *Cmd, root string, err error) {
+ // Clean and double-check that dir is in (a subdirectory of) srcRoot.
+ dir = filepath.Clean(dir)
+ srcRoot = filepath.Clean(srcRoot)
+ if len(dir) <= len(srcRoot) || dir[len(srcRoot)] != filepath.Separator {
+ return nil, "", fmt.Errorf("directory %q is outside source root %q", dir, srcRoot)
+ }
+
+ var vcsRet *Cmd
+ var rootRet string
+
+ origDir := dir
+ for len(dir) > len(srcRoot) {
+ for _, vcs := range vcsList {
+ if _, err := os.Stat(filepath.Join(dir, "."+vcs.Cmd)); err == nil {
+ root := filepath.ToSlash(dir[len(srcRoot)+1:])
+ // Record first VCS we find, but keep looking,
+ // to detect mistakes like one kind of VCS inside another.
+ if vcsRet == nil {
+ vcsRet = vcs
+ rootRet = root
+ continue
+ }
+ // Allow .git inside .git, which can arise due to submodules.
+ if vcsRet == vcs && vcs.Cmd == "git" {
+ continue
+ }
+ // Otherwise, we have one VCS inside a different VCS.
+ return nil, "", fmt.Errorf("directory %q uses %s, but parent %q uses %s",
+ filepath.Join(srcRoot, rootRet), vcsRet.Cmd, filepath.Join(srcRoot, root), vcs.Cmd)
+ }
+ }
+
+ // Move to parent.
+ ndir := filepath.Dir(dir)
+ if len(ndir) >= len(dir) {
+ // Shouldn't happen, but just in case, stop.
+ break
+ }
+ dir = ndir
+ }
+
+ if vcsRet != nil {
+ return vcsRet, rootRet, nil
+ }
+
+ return nil, "", fmt.Errorf("directory %q is not using a known version control system", origDir)
+}
+
+// CheckNested checks for an incorrectly-nested VCS-inside-VCS
+// situation for dir, checking parents up until srcRoot.
+func CheckNested(vcs *Cmd, dir, srcRoot string) error {
+ if len(dir) <= len(srcRoot) || dir[len(srcRoot)] != filepath.Separator {
+ return fmt.Errorf("directory %q is outside source root %q", dir, srcRoot)
+ }
+
+ otherDir := dir
+ for len(otherDir) > len(srcRoot) {
+ for _, otherVCS := range vcsList {
+ if _, err := os.Stat(filepath.Join(otherDir, "."+otherVCS.Cmd)); err == nil {
+ // Allow expected vcs in original dir.
+ if otherDir == dir && otherVCS == vcs {
+ continue
+ }
+ // Allow .git inside .git, which can arise due to submodules.
+ if otherVCS == vcs && vcs.Cmd == "git" {
+ continue
+ }
+ // Otherwise, we have one VCS inside a different VCS.
+ return fmt.Errorf("directory %q uses %s, but parent %q uses %s", dir, vcs.Cmd, otherDir, otherVCS.Cmd)
+ }
+ }
+ // Move to parent.
+ newDir := filepath.Dir(otherDir)
+ if len(newDir) >= len(otherDir) {
+ // Shouldn't happen, but just in case, stop.
+ break
+ }
+ otherDir = newDir
+ }
+
+ return nil
+}
+
+// RepoRoot describes the repository root for a tree of source code.
+type RepoRoot struct {
+ Repo string // repository URL, including scheme
+ Root string // import path corresponding to root of repo
+ IsCustom bool // defined by served tags (as opposed to hard-coded pattern)
+ VCS *Cmd
+}
+
+func httpPrefix(s string) string {
+ for _, prefix := range [...]string{"http:", "https:"} {
+ if strings.HasPrefix(s, prefix) {
+ return prefix
+ }
+ }
+ return ""
+}
+
+// ModuleMode specifies whether to prefer modules when looking up code sources.
+type ModuleMode int
+
+const (
+ IgnoreMod ModuleMode = iota
+ PreferMod
+)
+
+// RepoRootForImportPath analyzes importPath to determine the
+// version control system, and code repository to use.
+func RepoRootForImportPath(importPath string, mod ModuleMode, security web.SecurityMode) (*RepoRoot, error) {
+ rr, err := repoRootFromVCSPaths(importPath, security, vcsPaths)
+ if err == errUnknownSite {
+ rr, err = repoRootForImportDynamic(importPath, mod, security)
+ if err != nil {
+ err = load.ImportErrorf(importPath, "unrecognized import path %q: %v", importPath, err)
+ }
+ }
+ if err != nil {
+ rr1, err1 := repoRootFromVCSPaths(importPath, security, vcsPathsAfterDynamic)
+ if err1 == nil {
+ rr = rr1
+ err = nil
+ }
+ }
+
+ // Should have been taken care of above, but make sure.
+ if err == nil && strings.Contains(importPath, "...") && strings.Contains(rr.Root, "...") {
+ // Do not allow wildcards in the repo root.
+ rr = nil
+ err = load.ImportErrorf(importPath, "cannot expand ... in %q", importPath)
+ }
+ return rr, err
+}
+
+var errUnknownSite = errors.New("dynamic lookup required to find mapping")
+
+// repoRootFromVCSPaths attempts to map importPath to a repoRoot
+// using the mappings defined in vcsPaths.
+func repoRootFromVCSPaths(importPath string, security web.SecurityMode, vcsPaths []*vcsPath) (*RepoRoot, error) {
+ // A common error is to use https://packagepath because that's what
+ // hg and git require. Diagnose this helpfully.
+ if prefix := httpPrefix(importPath); prefix != "" {
+ // The importPath has been cleaned, so has only one slash. The pattern
+ // ignores the slashes; the error message puts them back on the RHS at least.
+ return nil, fmt.Errorf("%q not allowed in import path", prefix+"//")
+ }
+ for _, srv := range vcsPaths {
+ if !strings.HasPrefix(importPath, srv.prefix) {
+ continue
+ }
+ m := srv.regexp.FindStringSubmatch(importPath)
+ if m == nil {
+ if srv.prefix != "" {
+ return nil, load.ImportErrorf(importPath, "invalid %s import path %q", srv.prefix, importPath)
+ }
+ continue
+ }
+
+ // Build map of named subexpression matches for expand.
+ match := map[string]string{
+ "prefix": srv.prefix,
+ "import": importPath,
+ }
+ for i, name := range srv.regexp.SubexpNames() {
+ if name != "" && match[name] == "" {
+ match[name] = m[i]
+ }
+ }
+ if srv.vcs != "" {
+ match["vcs"] = expand(match, srv.vcs)
+ }
+ if srv.repo != "" {
+ match["repo"] = expand(match, srv.repo)
+ }
+ if srv.check != nil {
+ if err := srv.check(match); err != nil {
+ return nil, err
+ }
+ }
+ vcs := vcsByCmd(match["vcs"])
+ if vcs == nil {
+ return nil, fmt.Errorf("unknown version control system %q", match["vcs"])
+ }
+ var repoURL string
+ if !srv.schemelessRepo {
+ repoURL = match["repo"]
+ } else {
+ scheme := vcs.Scheme[0] // default to first scheme
+ repo := match["repo"]
+ if vcs.PingCmd != "" {
+ // If we know how to test schemes, scan to find one.
+ for _, s := range vcs.Scheme {
+ if security == web.SecureOnly && !vcs.isSecureScheme(s) {
+ continue
+ }
+ if vcs.Ping(s, repo) == nil {
+ scheme = s
+ break
+ }
+ }
+ }
+ repoURL = scheme + "://" + repo
+ }
+ rr := &RepoRoot{
+ Repo: repoURL,
+ Root: match["root"],
+ VCS: vcs,
+ }
+ return rr, nil
+ }
+ return nil, errUnknownSite
+}
+
+// urlForImportPath returns a partially-populated URL for the given Go import path.
+//
+// The URL leaves the Scheme field blank so that web.Get will try any scheme
+// allowed by the selected security mode.
+func urlForImportPath(importPath string) (*urlpkg.URL, error) {
+ slash := strings.Index(importPath, "/")
+ if slash < 0 {
+ slash = len(importPath)
+ }
+ host, path := importPath[:slash], importPath[slash:]
+ if !strings.Contains(host, ".") {
+ return nil, errors.New("import path does not begin with hostname")
+ }
+ if len(path) == 0 {
+ path = "/"
+ }
+ return &urlpkg.URL{Host: host, Path: path, RawQuery: "go-get=1"}, nil
+}
+
+// repoRootForImportDynamic finds a *RepoRoot for a custom domain that's not
+// statically known by repoRootForImportPathStatic.
+//
+// This handles custom import paths like "name.tld/pkg/foo" or just "name.tld".
+func repoRootForImportDynamic(importPath string, mod ModuleMode, security web.SecurityMode) (*RepoRoot, error) {
+ url, err := urlForImportPath(importPath)
+ if err != nil {
+ return nil, err
+ }
+ resp, err := web.Get(security, url)
+ if err != nil {
+ msg := "https fetch: %v"
+ if security == web.Insecure {
+ msg = "http/" + msg
+ }
+ return nil, fmt.Errorf(msg, err)
+ }
+ body := resp.Body
+ defer body.Close()
+ imports, err := parseMetaGoImports(body, mod)
+ if len(imports) == 0 {
+ if respErr := resp.Err(); respErr != nil {
+ // If the server's status was not OK, prefer to report that instead of
+ // an XML parse error.
+ return nil, respErr
+ }
+ }
+ if err != nil {
+ return nil, fmt.Errorf("parsing %s: %v", importPath, err)
+ }
+ // Find the matched meta import.
+ mmi, err := matchGoImport(imports, importPath)
+ if err != nil {
+ if _, ok := err.(ImportMismatchError); !ok {
+ return nil, fmt.Errorf("parse %s: %v", url, err)
+ }
+ return nil, fmt.Errorf("parse %s: no go-import meta tags (%s)", resp.URL, err)
+ }
+ if cfg.BuildV {
+ log.Printf("get %q: found meta tag %#v at %s", importPath, mmi, url)
+ }
+ // If the import was "uni.edu/bob/project", which said the
+ // prefix was "uni.edu" and the RepoRoot was "evilroot.com",
+ // make sure we don't trust Bob and check out evilroot.com to
+ // "uni.edu" yet (possibly overwriting/preempting another
+ // non-evil student). Instead, first verify the root and see
+ // if it matches Bob's claim.
+ if mmi.Prefix != importPath {
+ if cfg.BuildV {
+ log.Printf("get %q: verifying non-authoritative meta tag", importPath)
+ }
+ var imports []metaImport
+ url, imports, err = metaImportsForPrefix(mmi.Prefix, mod, security)
+ if err != nil {
+ return nil, err
+ }
+ metaImport2, err := matchGoImport(imports, importPath)
+ if err != nil || mmi != metaImport2 {
+ return nil, fmt.Errorf("%s and %s disagree about go-import for %s", resp.URL, url, mmi.Prefix)
+ }
+ }
+
+ if err := validateRepoRoot(mmi.RepoRoot); err != nil {
+ return nil, fmt.Errorf("%s: invalid repo root %q: %v", resp.URL, mmi.RepoRoot, err)
+ }
+ var vcs *Cmd
+ if mmi.VCS == "mod" {
+ vcs = vcsMod
+ } else {
+ vcs = vcsByCmd(mmi.VCS)
+ if vcs == nil {
+ return nil, fmt.Errorf("%s: unknown vcs %q", resp.URL, mmi.VCS)
+ }
+ }
+
+ rr := &RepoRoot{
+ Repo: mmi.RepoRoot,
+ Root: mmi.Prefix,
+ IsCustom: true,
+ VCS: vcs,
+ }
+ return rr, nil
+}
+
+// validateRepoRoot returns an error if repoRoot does not seem to be
+// a valid URL with scheme.
+func validateRepoRoot(repoRoot string) error {
+ url, err := urlpkg.Parse(repoRoot)
+ if err != nil {
+ return err
+ }
+ if url.Scheme == "" {
+ return errors.New("no scheme")
+ }
+ if url.Scheme == "file" {
+ return errors.New("file scheme disallowed")
+ }
+ return nil
+}
+
+var fetchGroup singleflight.Group
+var (
+ fetchCacheMu sync.Mutex
+ fetchCache = map[string]fetchResult{} // key is metaImportsForPrefix's importPrefix
+)
+
+// metaImportsForPrefix takes a package's root import path as declared in a tag
+// and returns its HTML discovery URL and the parsed metaImport lines
+// found on the page.
+//
+// The importPath is of the form "golang.org/x/tools".
+// It is an error if no imports are found.
+// url will still be valid if err != nil.
+// The returned url will be of the form "https://golang.org/x/tools?go-get=1"
+func metaImportsForPrefix(importPrefix string, mod ModuleMode, security web.SecurityMode) (*urlpkg.URL, []metaImport, error) {
+ setCache := func(res fetchResult) (fetchResult, error) {
+ fetchCacheMu.Lock()
+ defer fetchCacheMu.Unlock()
+ fetchCache[importPrefix] = res
+ return res, nil
+ }
+
+ resi, _, _ := fetchGroup.Do(importPrefix, func() (resi interface{}, err error) {
+ fetchCacheMu.Lock()
+ if res, ok := fetchCache[importPrefix]; ok {
+ fetchCacheMu.Unlock()
+ return res, nil
+ }
+ fetchCacheMu.Unlock()
+
+ url, err := urlForImportPath(importPrefix)
+ if err != nil {
+ return setCache(fetchResult{err: err})
+ }
+ resp, err := web.Get(security, url)
+ if err != nil {
+ return setCache(fetchResult{url: url, err: fmt.Errorf("fetching %s: %v", importPrefix, err)})
+ }
+ body := resp.Body
+ defer body.Close()
+ imports, err := parseMetaGoImports(body, mod)
+ if len(imports) == 0 {
+ if respErr := resp.Err(); respErr != nil {
+ // If the server's status was not OK, prefer to report that instead of
+ // an XML parse error.
+ return setCache(fetchResult{url: url, err: respErr})
+ }
+ }
+ if err != nil {
+ return setCache(fetchResult{url: url, err: fmt.Errorf("parsing %s: %v", resp.URL, err)})
+ }
+ if len(imports) == 0 {
+ err = fmt.Errorf("fetching %s: no go-import meta tag found in %s", importPrefix, resp.URL)
+ }
+ return setCache(fetchResult{url: url, imports: imports, err: err})
+ })
+ res := resi.(fetchResult)
+ return res.url, res.imports, res.err
+}
+
+type fetchResult struct {
+ url *urlpkg.URL
+ imports []metaImport
+ err error
+}
+
+// metaImport represents the parsed tags from HTML files.
+type metaImport struct {
+ Prefix, VCS, RepoRoot string
+}
+
+// pathPrefix reports whether sub is a prefix of s,
+// only considering entire path components.
+func pathPrefix(s, sub string) bool {
+ // strings.HasPrefix is necessary but not sufficient.
+ if !strings.HasPrefix(s, sub) {
+ return false
+ }
+ // The remainder after the prefix must either be empty or start with a slash.
+ rem := s[len(sub):]
+ return rem == "" || rem[0] == '/'
+}
+
+// A ImportMismatchError is returned where metaImport/s are present
+// but none match our import path.
+type ImportMismatchError struct {
+ importPath string
+ mismatches []string // the meta imports that were discarded for not matching our importPath
+}
+
+func (m ImportMismatchError) Error() string {
+ formattedStrings := make([]string, len(m.mismatches))
+ for i, pre := range m.mismatches {
+ formattedStrings[i] = fmt.Sprintf("meta tag %s did not match import path %s", pre, m.importPath)
+ }
+ return strings.Join(formattedStrings, ", ")
+}
+
+// matchGoImport returns the metaImport from imports matching importPath.
+// An error is returned if there are multiple matches.
+// An ImportMismatchError is returned if none match.
+func matchGoImport(imports []metaImport, importPath string) (metaImport, error) {
+ match := -1
+
+ errImportMismatch := ImportMismatchError{importPath: importPath}
+ for i, im := range imports {
+ if !pathPrefix(importPath, im.Prefix) {
+ errImportMismatch.mismatches = append(errImportMismatch.mismatches, im.Prefix)
+ continue
+ }
+
+ if match >= 0 {
+ if imports[match].VCS == "mod" && im.VCS != "mod" {
+ // All the mod entries precede all the non-mod entries.
+ // We have a mod entry and don't care about the rest,
+ // matching or not.
+ break
+ }
+ return metaImport{}, fmt.Errorf("multiple meta tags match import path %q", importPath)
+ }
+ match = i
+ }
+
+ if match == -1 {
+ return metaImport{}, errImportMismatch
+ }
+ return imports[match], nil
+}
+
+// expand rewrites s to replace {k} with match[k] for each key k in match.
+func expand(match map[string]string, s string) string {
+ // We want to replace each match exactly once, and the result of expansion
+ // must not depend on the iteration order through the map.
+ // A strings.Replacer has exactly the properties we're looking for.
+ oldNew := make([]string, 0, 2*len(match))
+ for k, v := range match {
+ oldNew = append(oldNew, "{"+k+"}", v)
+ }
+ return strings.NewReplacer(oldNew...).Replace(s)
+}
+
+// vcsPaths defines the meaning of import paths referring to
+// commonly-used VCS hosting sites (github.com/user/dir)
+// and import paths referring to a fully-qualified importPath
+// containing a VCS type (foo.com/repo.git/dir)
+var vcsPaths = []*vcsPath{
+ // Github
+ {
+ prefix: "github.com/",
+ regexp: lazyregexp.New(`^(?Pgithub\.com/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(/[A-Za-z0-9_.\-]+)*$`),
+ vcs: "git",
+ repo: "https://{root}",
+ check: noVCSSuffix,
+ },
+
+ // Bitbucket
+ {
+ prefix: "bitbucket.org/",
+ regexp: lazyregexp.New(`^(?Pbitbucket\.org/(?P[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`),
+ repo: "https://{root}",
+ check: bitbucketVCS,
+ },
+
+ // IBM DevOps Services (JazzHub)
+ {
+ prefix: "hub.jazz.net/git/",
+ regexp: lazyregexp.New(`^(?Phub\.jazz\.net/git/[a-z0-9]+/[A-Za-z0-9_.\-]+)(/[A-Za-z0-9_.\-]+)*$`),
+ vcs: "git",
+ repo: "https://{root}",
+ check: noVCSSuffix,
+ },
+
+ // Git at Apache
+ {
+ prefix: "git.apache.org/",
+ regexp: lazyregexp.New(`^(?Pgit\.apache\.org/[a-z0-9_.\-]+\.git)(/[A-Za-z0-9_.\-]+)*$`),
+ vcs: "git",
+ repo: "https://{root}",
+ },
+
+ // Git at OpenStack
+ {
+ prefix: "git.openstack.org/",
+ regexp: lazyregexp.New(`^(?Pgit\.openstack\.org/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(\.git)?(/[A-Za-z0-9_.\-]+)*$`),
+ vcs: "git",
+ repo: "https://{root}",
+ },
+
+ // chiselapp.com for fossil
+ {
+ prefix: "chiselapp.com/",
+ regexp: lazyregexp.New(`^(?Pchiselapp\.com/user/[A-Za-z0-9]+/repository/[A-Za-z0-9_.\-]+)$`),
+ vcs: "fossil",
+ repo: "https://{root}",
+ },
+
+ // General syntax for any server.
+ // Must be last.
+ {
+ regexp: lazyregexp.New(`(?P(?P([a-z0-9.\-]+\.)+[a-z0-9.\-]+(:[0-9]+)?(/~?[A-Za-z0-9_.\-]+)+?)\.(?Pbzr|fossil|git|hg|svn))(/~?[A-Za-z0-9_.\-]+)*$`),
+ schemelessRepo: true,
+ },
+}
+
+// vcsPathsAfterDynamic gives additional vcsPaths entries
+// to try after the dynamic HTML check.
+// This gives those sites a chance to introduce tags
+// as part of a graceful transition away from the hard-coded logic.
+var vcsPathsAfterDynamic = []*vcsPath{
+ // Launchpad. See golang.org/issue/11436.
+ {
+ prefix: "launchpad.net/",
+ regexp: lazyregexp.New(`^(?Plaunchpad\.net/((?P[A-Za-z0-9_.\-]+)(?P/[A-Za-z0-9_.\-]+)?|~[A-Za-z0-9_.\-]+/(\+junk|[A-Za-z0-9_.\-]+)/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`),
+ vcs: "bzr",
+ repo: "https://{root}",
+ check: launchpadVCS,
+ },
+}
+
+// noVCSSuffix checks that the repository name does not
+// end in .foo for any version control system foo.
+// The usual culprit is ".git".
+func noVCSSuffix(match map[string]string) error {
+ repo := match["repo"]
+ for _, vcs := range vcsList {
+ if strings.HasSuffix(repo, "."+vcs.Cmd) {
+ return fmt.Errorf("invalid version control suffix in %s path", match["prefix"])
+ }
+ }
+ return nil
+}
+
+// bitbucketVCS determines the version control system for a
+// Bitbucket repository, by using the Bitbucket API.
+func bitbucketVCS(match map[string]string) error {
+ if err := noVCSSuffix(match); err != nil {
+ return err
+ }
+
+ var resp struct {
+ SCM string `json:"scm"`
+ }
+ url := &urlpkg.URL{
+ Scheme: "https",
+ Host: "api.bitbucket.org",
+ Path: expand(match, "/2.0/repositories/{bitname}"),
+ RawQuery: "fields=scm",
+ }
+ data, err := web.GetBytes(url)
+ if err != nil {
+ if httpErr, ok := err.(*web.HTTPError); ok && httpErr.StatusCode == 403 {
+ // this may be a private repository. If so, attempt to determine which
+ // VCS it uses. See issue 5375.
+ root := match["root"]
+ for _, vcs := range []string{"git", "hg"} {
+ if vcsByCmd(vcs).Ping("https", root) == nil {
+ resp.SCM = vcs
+ break
+ }
+ }
+ }
+
+ if resp.SCM == "" {
+ return err
+ }
+ } else {
+ if err := json.Unmarshal(data, &resp); err != nil {
+ return fmt.Errorf("decoding %s: %v", url, err)
+ }
+ }
+
+ if vcsByCmd(resp.SCM) != nil {
+ match["vcs"] = resp.SCM
+ if resp.SCM == "git" {
+ match["repo"] += ".git"
+ }
+ return nil
+ }
+
+ return fmt.Errorf("unable to detect version control system for bitbucket.org/ path")
+}
+
+// launchpadVCS solves the ambiguity for "lp.net/project/foo". In this case,
+// "foo" could be a series name registered in Launchpad with its own branch,
+// and it could also be the name of a directory within the main project
+// branch one level up.
+func launchpadVCS(match map[string]string) error {
+ if match["project"] == "" || match["series"] == "" {
+ return nil
+ }
+ url := &urlpkg.URL{
+ Scheme: "https",
+ Host: "code.launchpad.net",
+ Path: expand(match, "/{project}{series}/.bzr/branch-format"),
+ }
+ _, err := web.GetBytes(url)
+ if err != nil {
+ match["root"] = expand(match, "launchpad.net/{project}")
+ match["repo"] = expand(match, "https://{root}")
+ }
+ return nil
+}
diff --git a/src/cmd/go/internal/vcs/vcs_test.go b/src/cmd/go/internal/vcs/vcs_test.go
new file mode 100644
index 0000000000..5b874204f1
--- /dev/null
+++ b/src/cmd/go/internal/vcs/vcs_test.go
@@ -0,0 +1,475 @@
+// Copyright 2014 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.
+
+package vcs
+
+import (
+ "errors"
+ "internal/testenv"
+ "io/ioutil"
+ "os"
+ "path"
+ "path/filepath"
+ "testing"
+
+ "cmd/go/internal/web"
+)
+
+// Test that RepoRootForImportPath determines the correct RepoRoot for a given importPath.
+// TODO(cmang): Add tests for SVN and BZR.
+func TestRepoRootForImportPath(t *testing.T) {
+ testenv.MustHaveExternalNetwork(t)
+
+ tests := []struct {
+ path string
+ want *RepoRoot
+ }{
+ {
+ "github.com/golang/groupcache",
+ &RepoRoot{
+ VCS: vcsGit,
+ Repo: "https://github.com/golang/groupcache",
+ },
+ },
+ // Unicode letters in directories are not valid.
+ {
+ "github.com/user/unicode/испытание",
+ nil,
+ },
+ // IBM DevOps Services tests
+ {
+ "hub.jazz.net/git/user1/pkgname",
+ &RepoRoot{
+ VCS: vcsGit,
+ Repo: "https://hub.jazz.net/git/user1/pkgname",
+ },
+ },
+ {
+ "hub.jazz.net/git/user1/pkgname/submodule/submodule/submodule",
+ &RepoRoot{
+ VCS: vcsGit,
+ Repo: "https://hub.jazz.net/git/user1/pkgname",
+ },
+ },
+ {
+ "hub.jazz.net",
+ nil,
+ },
+ {
+ "hubajazz.net",
+ nil,
+ },
+ {
+ "hub2.jazz.net",
+ nil,
+ },
+ {
+ "hub.jazz.net/someotherprefix",
+ nil,
+ },
+ {
+ "hub.jazz.net/someotherprefix/user1/pkgname",
+ nil,
+ },
+ // Spaces are not valid in user names or package names
+ {
+ "hub.jazz.net/git/User 1/pkgname",
+ nil,
+ },
+ {
+ "hub.jazz.net/git/user1/pkg name",
+ nil,
+ },
+ // Dots are not valid in user names
+ {
+ "hub.jazz.net/git/user.1/pkgname",
+ nil,
+ },
+ {
+ "hub.jazz.net/git/user/pkg.name",
+ &RepoRoot{
+ VCS: vcsGit,
+ Repo: "https://hub.jazz.net/git/user/pkg.name",
+ },
+ },
+ // User names cannot have uppercase letters
+ {
+ "hub.jazz.net/git/USER/pkgname",
+ nil,
+ },
+ // OpenStack tests
+ {
+ "git.openstack.org/openstack/swift",
+ &RepoRoot{
+ VCS: vcsGit,
+ Repo: "https://git.openstack.org/openstack/swift",
+ },
+ },
+ // Trailing .git is less preferred but included for
+ // compatibility purposes while the same source needs to
+ // be compilable on both old and new go
+ {
+ "git.openstack.org/openstack/swift.git",
+ &RepoRoot{
+ VCS: vcsGit,
+ Repo: "https://git.openstack.org/openstack/swift.git",
+ },
+ },
+ {
+ "git.openstack.org/openstack/swift/go/hummingbird",
+ &RepoRoot{
+ VCS: vcsGit,
+ Repo: "https://git.openstack.org/openstack/swift",
+ },
+ },
+ {
+ "git.openstack.org",
+ nil,
+ },
+ {
+ "git.openstack.org/openstack",
+ nil,
+ },
+ // Spaces are not valid in package name
+ {
+ "git.apache.org/package name/path/to/lib",
+ nil,
+ },
+ // Should have ".git" suffix
+ {
+ "git.apache.org/package-name/path/to/lib",
+ nil,
+ },
+ {
+ "gitbapache.org",
+ nil,
+ },
+ {
+ "git.apache.org/package-name.git",
+ &RepoRoot{
+ VCS: vcsGit,
+ Repo: "https://git.apache.org/package-name.git",
+ },
+ },
+ {
+ "git.apache.org/package-name_2.x.git/path/to/lib",
+ &RepoRoot{
+ VCS: vcsGit,
+ Repo: "https://git.apache.org/package-name_2.x.git",
+ },
+ },
+ {
+ "chiselapp.com/user/kyle/repository/fossilgg",
+ &RepoRoot{
+ VCS: vcsFossil,
+ Repo: "https://chiselapp.com/user/kyle/repository/fossilgg",
+ },
+ },
+ {
+ // must have a user/$name/repository/$repo path
+ "chiselapp.com/kyle/repository/fossilgg",
+ nil,
+ },
+ {
+ "chiselapp.com/user/kyle/fossilgg",
+ nil,
+ },
+ }
+
+ for _, test := range tests {
+ got, err := RepoRootForImportPath(test.path, IgnoreMod, web.SecureOnly)
+ want := test.want
+
+ if want == nil {
+ if err == nil {
+ t.Errorf("RepoRootForImportPath(%q): Error expected but not received", test.path)
+ }
+ continue
+ }
+ if err != nil {
+ t.Errorf("RepoRootForImportPath(%q): %v", test.path, err)
+ continue
+ }
+ if got.VCS.Name != want.VCS.Name || got.Repo != want.Repo {
+ t.Errorf("RepoRootForImportPath(%q) = VCS(%s) Repo(%s), want VCS(%s) Repo(%s)", test.path, got.VCS, got.Repo, want.VCS, want.Repo)
+ }
+ }
+}
+
+// Test that vcsFromDir correctly inspects a given directory and returns the right VCS and root.
+func TestFromDir(t *testing.T) {
+ tempDir, err := ioutil.TempDir("", "vcstest")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tempDir)
+
+ for j, vcs := range vcsList {
+ dir := filepath.Join(tempDir, "example.com", vcs.Name, "."+vcs.Cmd)
+ if j&1 == 0 {
+ err := os.MkdirAll(dir, 0755)
+ if err != nil {
+ t.Fatal(err)
+ }
+ } else {
+ err := os.MkdirAll(filepath.Dir(dir), 0755)
+ if err != nil {
+ t.Fatal(err)
+ }
+ f, err := os.Create(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ f.Close()
+ }
+
+ want := RepoRoot{
+ VCS: vcs,
+ Root: path.Join("example.com", vcs.Name),
+ }
+ var got RepoRoot
+ got.VCS, got.Root, err = FromDir(dir, tempDir)
+ if err != nil {
+ t.Errorf("FromDir(%q, %q): %v", dir, tempDir, err)
+ continue
+ }
+ if got.VCS.Name != want.VCS.Name || got.Root != want.Root {
+ t.Errorf("FromDir(%q, %q) = VCS(%s) Root(%s), want VCS(%s) Root(%s)", dir, tempDir, got.VCS, got.Root, want.VCS, want.Root)
+ }
+ }
+}
+
+func TestIsSecure(t *testing.T) {
+ tests := []struct {
+ vcs *Cmd
+ url string
+ secure bool
+ }{
+ {vcsGit, "http://example.com/foo.git", false},
+ {vcsGit, "https://example.com/foo.git", true},
+ {vcsBzr, "http://example.com/foo.bzr", false},
+ {vcsBzr, "https://example.com/foo.bzr", true},
+ {vcsSvn, "http://example.com/svn", false},
+ {vcsSvn, "https://example.com/svn", true},
+ {vcsHg, "http://example.com/foo.hg", false},
+ {vcsHg, "https://example.com/foo.hg", true},
+ {vcsGit, "ssh://user@example.com/foo.git", true},
+ {vcsGit, "user@server:path/to/repo.git", false},
+ {vcsGit, "user@server:", false},
+ {vcsGit, "server:repo.git", false},
+ {vcsGit, "server:path/to/repo.git", false},
+ {vcsGit, "example.com:path/to/repo.git", false},
+ {vcsGit, "path/that/contains/a:colon/repo.git", false},
+ {vcsHg, "ssh://user@example.com/path/to/repo.hg", true},
+ {vcsFossil, "http://example.com/foo", false},
+ {vcsFossil, "https://example.com/foo", true},
+ }
+
+ for _, test := range tests {
+ secure := test.vcs.IsSecure(test.url)
+ if secure != test.secure {
+ t.Errorf("%s isSecure(%q) = %t; want %t", test.vcs, test.url, secure, test.secure)
+ }
+ }
+}
+
+func TestIsSecureGitAllowProtocol(t *testing.T) {
+ tests := []struct {
+ vcs *Cmd
+ url string
+ secure bool
+ }{
+ // Same as TestIsSecure to verify same behavior.
+ {vcsGit, "http://example.com/foo.git", false},
+ {vcsGit, "https://example.com/foo.git", true},
+ {vcsBzr, "http://example.com/foo.bzr", false},
+ {vcsBzr, "https://example.com/foo.bzr", true},
+ {vcsSvn, "http://example.com/svn", false},
+ {vcsSvn, "https://example.com/svn", true},
+ {vcsHg, "http://example.com/foo.hg", false},
+ {vcsHg, "https://example.com/foo.hg", true},
+ {vcsGit, "user@server:path/to/repo.git", false},
+ {vcsGit, "user@server:", false},
+ {vcsGit, "server:repo.git", false},
+ {vcsGit, "server:path/to/repo.git", false},
+ {vcsGit, "example.com:path/to/repo.git", false},
+ {vcsGit, "path/that/contains/a:colon/repo.git", false},
+ {vcsHg, "ssh://user@example.com/path/to/repo.hg", true},
+ // New behavior.
+ {vcsGit, "ssh://user@example.com/foo.git", false},
+ {vcsGit, "foo://example.com/bar.git", true},
+ {vcsHg, "foo://example.com/bar.hg", false},
+ {vcsSvn, "foo://example.com/svn", false},
+ {vcsBzr, "foo://example.com/bar.bzr", false},
+ }
+
+ defer os.Unsetenv("GIT_ALLOW_PROTOCOL")
+ os.Setenv("GIT_ALLOW_PROTOCOL", "https:foo")
+ for _, test := range tests {
+ secure := test.vcs.IsSecure(test.url)
+ if secure != test.secure {
+ t.Errorf("%s isSecure(%q) = %t; want %t", test.vcs, test.url, secure, test.secure)
+ }
+ }
+}
+
+func TestMatchGoImport(t *testing.T) {
+ tests := []struct {
+ imports []metaImport
+ path string
+ mi metaImport
+ err error
+ }{
+ {
+ imports: []metaImport{
+ {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ },
+ path: "example.com/user/foo",
+ mi: metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ },
+ {
+ imports: []metaImport{
+ {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ },
+ path: "example.com/user/foo/",
+ mi: metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ },
+ {
+ imports: []metaImport{
+ {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ {Prefix: "example.com/user/fooa", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ },
+ path: "example.com/user/foo",
+ mi: metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ },
+ {
+ imports: []metaImport{
+ {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ {Prefix: "example.com/user/fooa", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ },
+ path: "example.com/user/fooa",
+ mi: metaImport{Prefix: "example.com/user/fooa", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ },
+ {
+ imports: []metaImport{
+ {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ {Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ },
+ path: "example.com/user/foo/bar",
+ err: errors.New("should not be allowed to create nested repo"),
+ },
+ {
+ imports: []metaImport{
+ {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ {Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ },
+ path: "example.com/user/foo/bar/baz",
+ err: errors.New("should not be allowed to create nested repo"),
+ },
+ {
+ imports: []metaImport{
+ {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ {Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ },
+ path: "example.com/user/foo/bar/baz/qux",
+ err: errors.New("should not be allowed to create nested repo"),
+ },
+ {
+ imports: []metaImport{
+ {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ {Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ },
+ path: "example.com/user/foo/bar/baz/",
+ err: errors.New("should not be allowed to create nested repo"),
+ },
+ {
+ imports: []metaImport{
+ {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ {Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ },
+ path: "example.com",
+ err: errors.New("pathologically short path"),
+ },
+ {
+ imports: []metaImport{
+ {Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
+ },
+ path: "different.example.com/user/foo",
+ err: errors.New("meta tags do not match import path"),
+ },
+ {
+ imports: []metaImport{
+ {Prefix: "myitcv.io/blah2", VCS: "mod", RepoRoot: "https://raw.githubusercontent.com/myitcv/pubx/master"},
+ {Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
+ },
+ path: "myitcv.io/blah2/foo",
+ mi: metaImport{Prefix: "myitcv.io/blah2", VCS: "mod", RepoRoot: "https://raw.githubusercontent.com/myitcv/pubx/master"},
+ },
+ {
+ imports: []metaImport{
+ {Prefix: "myitcv.io/blah2", VCS: "mod", RepoRoot: "https://raw.githubusercontent.com/myitcv/pubx/master"},
+ {Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
+ },
+ path: "myitcv.io/other",
+ mi: metaImport{Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
+ },
+ }
+
+ for _, test := range tests {
+ mi, err := matchGoImport(test.imports, test.path)
+ if mi != test.mi {
+ t.Errorf("unexpected metaImport; got %v, want %v", mi, test.mi)
+ }
+
+ got := err
+ want := test.err
+ if (got == nil) != (want == nil) {
+ t.Errorf("unexpected error; got %v, want %v", got, want)
+ }
+ }
+}
+
+func TestValidateRepoRoot(t *testing.T) {
+ tests := []struct {
+ root string
+ ok bool
+ }{
+ {
+ root: "",
+ ok: false,
+ },
+ {
+ root: "http://",
+ ok: true,
+ },
+ {
+ root: "git+ssh://",
+ ok: true,
+ },
+ {
+ root: "http#://",
+ ok: false,
+ },
+ {
+ root: "-config",
+ ok: false,
+ },
+ {
+ root: "-config://",
+ ok: false,
+ },
+ }
+
+ for _, test := range tests {
+ err := validateRepoRoot(test.root)
+ ok := err == nil
+ if ok != test.ok {
+ want := "error"
+ if test.ok {
+ want = "nil"
+ }
+ t.Errorf("validateRepoRoot(%q) = %q, want %s", test.root, err, want)
+ }
+ }
+}
--
cgit v1.3
From 2c95e3a6a8377ca9c72608c25b4cf2506baf782f Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Wed, 9 Sep 2020 11:09:01 +0700
Subject: cmd/compile: use clearer error message for stuct literal
This CL changes "T literal.M" error message to "T{...}.M". It's clearer
expression and focusing user on actual issue.
Updates #38745
Change-Id: I84b455a86742f37e0bde5bf390aa02984eecc3c9
Reviewed-on: https://go-review.googlesource.com/c/go/+/253677
Run-TryBot: Cuong Manh Le
TryBot-Result: Gobot Gobot
Reviewed-by: Matthew Dempsky
---
src/cmd/compile/internal/gc/fmt.go | 11 ++-
test/alias2.go | 10 +--
test/ddd1.go | 2 +-
test/escape2.go | 56 ++++++-------
test/escape2n.go | 56 ++++++-------
test/escape_calls.go | 2 +-
test/escape_field.go | 6 +-
test/escape_iface.go | 50 ++++++------
test/escape_indir.go | 34 ++++----
test/escape_map.go | 26 +++---
test/escape_param.go | 60 +++++++-------
test/escape_slice.go | 14 ++--
test/escape_struct_param1.go | 158 ++++++++++++++++++-------------------
test/escape_struct_param2.go | 158 ++++++++++++++++++-------------------
test/fixedbugs/issue12006.go | 4 +-
test/fixedbugs/issue13799.go | 4 +-
test/fixedbugs/issue17645.go | 2 +-
test/fixedbugs/issue21709.go | 4 +-
test/fixedbugs/issue23732.go | 6 +-
test/fixedbugs/issue26855.go | 4 +-
test/fixedbugs/issue30898.go | 2 +-
test/fixedbugs/issue31573.go | 24 +++---
test/fixedbugs/issue38745.go | 19 +++++
test/fixedbugs/issue39292.go | 6 +-
test/fixedbugs/issue41247.go | 2 +-
test/fixedbugs/issue7921.go | 10 +--
test/inline_variadic.go | 2 +-
27 files changed, 379 insertions(+), 353 deletions(-)
create mode 100644 test/fixedbugs/issue38745.go
(limited to 'src/cmd')
diff --git a/src/cmd/compile/internal/gc/fmt.go b/src/cmd/compile/internal/gc/fmt.go
index 866cd0a714..43e501deaf 100644
--- a/src/cmd/compile/internal/gc/fmt.go
+++ b/src/cmd/compile/internal/gc/fmt.go
@@ -1407,7 +1407,7 @@ func (n *Node) exprfmt(s fmt.State, prec int, mode fmtMode) {
return
}
if n.Right != nil {
- mode.Fprintf(s, "%v literal", n.Right)
+ mode.Fprintf(s, "%v{%s}", n.Right, ellipsisIf(n.List.Len() != 0))
return
}
@@ -1421,7 +1421,7 @@ func (n *Node) exprfmt(s fmt.State, prec int, mode fmtMode) {
case OSTRUCTLIT, OARRAYLIT, OSLICELIT, OMAPLIT:
if mode == FErr {
- mode.Fprintf(s, "%v literal", n.Type)
+ mode.Fprintf(s, "%v{%s}", n.Type, ellipsisIf(n.List.Len() != 0))
return
}
mode.Fprintf(s, "(%v{ %.v })", n.Type, n.List)
@@ -1934,3 +1934,10 @@ func indent(s fmt.State) {
fmt.Fprint(s, ". ")
}
}
+
+func ellipsisIf(b bool) string {
+ if b {
+ return "..."
+ }
+ return ""
+}
diff --git a/test/alias2.go b/test/alias2.go
index 7ea1b2908d..1c141ac490 100644
--- a/test/alias2.go
+++ b/test/alias2.go
@@ -46,8 +46,8 @@ var _ A0 = T0{}
var _ T0 = A0{}
// But aliases and original types cannot be used with new types based on them.
-var _ N0 = T0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment|incompatible type"
-var _ N0 = A0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment|incompatible type"
+var _ N0 = T0{} // ERROR "cannot use T0{} \(type T0\) as type N0 in assignment|incompatible type"
+var _ N0 = A0{} // ERROR "cannot use T0{} \(type T0\) as type N0 in assignment|incompatible type"
var _ A5 = Value{}
@@ -82,10 +82,10 @@ func _() {
var _ A0 = T0{}
var _ T0 = A0{}
- var _ N0 = T0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment|incompatible type"
- var _ N0 = A0{} // ERROR "cannot use T0 literal \(type T0\) as type N0 in assignment|incompatible type"
+ var _ N0 = T0{} // ERROR "cannot use T0{} \(type T0\) as type N0 in assignment|incompatible type"
+ var _ N0 = A0{} // ERROR "cannot use T0{} \(type T0\) as type N0 in assignment|incompatible type"
- var _ A5 = Value{} // ERROR "cannot use reflect\.Value literal \(type reflect.Value\) as type A5 in assignment|incompatible type"
+ var _ A5 = Value{} // ERROR "cannot use reflect\.Value{} \(type reflect.Value\) as type A5 in assignment|incompatible type"
}
// Invalid type alias declarations.
diff --git a/test/ddd1.go b/test/ddd1.go
index b582f221b7..2c7e83e374 100644
--- a/test/ddd1.go
+++ b/test/ddd1.go
@@ -19,7 +19,7 @@ var (
_ = sum(1.0, 2.0)
_ = sum(1.5) // ERROR "integer"
_ = sum("hello") // ERROR ".hello. .type untyped string. as type int|incompatible"
- _ = sum([]int{1}) // ERROR "\[\]int literal.*as type int|incompatible"
+ _ = sum([]int{1}) // ERROR "\[\]int{...}.*as type int|incompatible"
)
func sum3(int, int, int) int { return 0 }
diff --git a/test/escape2.go b/test/escape2.go
index cf24f4bebc..5c6eb559fa 100644
--- a/test/escape2.go
+++ b/test/escape2.go
@@ -118,15 +118,15 @@ type Bar struct {
}
func NewBar() *Bar {
- return &Bar{42, nil} // ERROR "&Bar literal escapes to heap$"
+ return &Bar{42, nil} // ERROR "&Bar{...} escapes to heap$"
}
func NewBarp(x *int) *Bar { // ERROR "leaking param: x$"
- return &Bar{42, x} // ERROR "&Bar literal escapes to heap$"
+ return &Bar{42, x} // ERROR "&Bar{...} escapes to heap$"
}
func NewBarp2(x *int) *Bar { // ERROR "x does not escape$"
- return &Bar{*x, nil} // ERROR "&Bar literal escapes to heap$"
+ return &Bar{*x, nil} // ERROR "&Bar{...} escapes to heap$"
}
func (b *Bar) NoLeak() int { // ERROR "b does not escape$"
@@ -173,7 +173,7 @@ type Bar2 struct {
}
func NewBar2() *Bar2 {
- return &Bar2{[12]int{42}, nil} // ERROR "&Bar2 literal escapes to heap$"
+ return &Bar2{[12]int{42}, nil} // ERROR "&Bar2{...} escapes to heap$"
}
func (b *Bar2) NoLeak() int { // ERROR "b does not escape$"
@@ -539,7 +539,7 @@ func foo72b() [10]*int {
// issue 2145
func foo73() {
- s := []int{3, 2, 1} // ERROR "\[\]int literal does not escape$"
+ s := []int{3, 2, 1} // ERROR "\[\]int{...} does not escape$"
for _, v := range s {
vv := v
// actually just escapes its scope
@@ -550,7 +550,7 @@ func foo73() {
}
func foo731() {
- s := []int{3, 2, 1} // ERROR "\[\]int literal does not escape$"
+ s := []int{3, 2, 1} // ERROR "\[\]int{...} does not escape$"
for _, v := range s {
vv := v // ERROR "moved to heap: vv$"
// actually just escapes its scope
@@ -562,7 +562,7 @@ func foo731() {
}
func foo74() {
- s := []int{3, 2, 1} // ERROR "\[\]int literal does not escape$"
+ s := []int{3, 2, 1} // ERROR "\[\]int{...} does not escape$"
for _, v := range s {
vv := v
// actually just escapes its scope
@@ -574,7 +574,7 @@ func foo74() {
}
func foo74a() {
- s := []int{3, 2, 1} // ERROR "\[\]int literal does not escape$"
+ s := []int{3, 2, 1} // ERROR "\[\]int{...} does not escape$"
for _, v := range s {
vv := v // ERROR "moved to heap: vv$"
// actually just escapes its scope
@@ -589,7 +589,7 @@ func foo74a() {
// issue 3975
func foo74b() {
var array [3]func()
- s := []int{3, 2, 1} // ERROR "\[\]int literal does not escape$"
+ s := []int{3, 2, 1} // ERROR "\[\]int{...} does not escape$"
for i, v := range s {
vv := v
// actually just escapes its scope
@@ -601,7 +601,7 @@ func foo74b() {
func foo74c() {
var array [3]func()
- s := []int{3, 2, 1} // ERROR "\[\]int literal does not escape$"
+ s := []int{3, 2, 1} // ERROR "\[\]int{...} does not escape$"
for i, v := range s {
vv := v // ERROR "moved to heap: vv$"
// actually just escapes its scope
@@ -759,15 +759,15 @@ type LimitedFooer struct {
}
func LimitFooer(r Fooer, n int64) Fooer { // ERROR "leaking param: r$"
- return &LimitedFooer{r, n} // ERROR "&LimitedFooer literal escapes to heap$"
+ return &LimitedFooer{r, n} // ERROR "&LimitedFooer{...} escapes to heap$"
}
func foo90(x *int) map[*int]*int { // ERROR "leaking param: x$"
- return map[*int]*int{nil: x} // ERROR "map\[\*int\]\*int literal escapes to heap$"
+ return map[*int]*int{nil: x} // ERROR "map\[\*int\]\*int{...} escapes to heap$"
}
func foo91(x *int) map[*int]*int { // ERROR "leaking param: x$"
- return map[*int]*int{x: nil} // ERROR "map\[\*int\]\*int literal escapes to heap$"
+ return map[*int]*int{x: nil} // ERROR "map\[\*int\]\*int{...} escapes to heap$"
}
func foo92(x *int) [2]*int { // ERROR "leaking param: x to result ~r1 level=0$"
@@ -870,15 +870,15 @@ func foo106(x *int) { // ERROR "leaking param: x$"
}
func foo107(x *int) map[*int]*int { // ERROR "leaking param: x$"
- return map[*int]*int{x: nil} // ERROR "map\[\*int\]\*int literal escapes to heap$"
+ return map[*int]*int{x: nil} // ERROR "map\[\*int\]\*int{...} escapes to heap$"
}
func foo108(x *int) map[*int]*int { // ERROR "leaking param: x$"
- return map[*int]*int{nil: x} // ERROR "map\[\*int\]\*int literal escapes to heap$"
+ return map[*int]*int{nil: x} // ERROR "map\[\*int\]\*int{...} escapes to heap$"
}
func foo109(x *int) *int { // ERROR "leaking param: x$"
- m := map[*int]*int{x: nil} // ERROR "map\[\*int\]\*int literal does not escape$"
+ m := map[*int]*int{x: nil} // ERROR "map\[\*int\]\*int{...} does not escape$"
for k, _ := range m {
return k
}
@@ -886,12 +886,12 @@ func foo109(x *int) *int { // ERROR "leaking param: x$"
}
func foo110(x *int) *int { // ERROR "leaking param: x$"
- m := map[*int]*int{nil: x} // ERROR "map\[\*int\]\*int literal does not escape$"
+ m := map[*int]*int{nil: x} // ERROR "map\[\*int\]\*int{...} does not escape$"
return m[nil]
}
func foo111(x *int) *int { // ERROR "leaking param: x to result ~r1 level=0"
- m := []*int{x} // ERROR "\[\]\*int literal does not escape$"
+ m := []*int{x} // ERROR "\[\]\*int{...} does not escape$"
return m[0]
}
@@ -906,7 +906,7 @@ func foo113(x *int) *int { // ERROR "leaking param: x to result ~r1 level=0$"
}
func foo114(x *int) *int { // ERROR "leaking param: x to result ~r1 level=0$"
- m := &Bar{ii: x} // ERROR "&Bar literal does not escape$"
+ m := &Bar{ii: x} // ERROR "&Bar{...} does not escape$"
return m.ii
}
@@ -1343,8 +1343,8 @@ func foo140() interface{} {
X string
T *T
}
- t := &T{} // ERROR "&T literal escapes to heap$"
- return U{ // ERROR "U literal escapes to heap$"
+ t := &T{} // ERROR "&T{} escapes to heap$"
+ return U{ // ERROR "U{...} escapes to heap$"
X: t.X,
T: t,
}
@@ -1530,7 +1530,7 @@ type V struct {
}
func NewV(u U) *V { // ERROR "leaking param: u$"
- return &V{u.String()} // ERROR "&V literal escapes to heap$"
+ return &V{u.String()} // ERROR "&V{...} escapes to heap$"
}
func foo152() {
@@ -1571,21 +1571,21 @@ type Lit struct {
func ptrlitNoescape() {
// Both literal and element do not escape.
i := 0
- x := &Lit{&i} // ERROR "&Lit literal does not escape$"
+ x := &Lit{&i} // ERROR "&Lit{...} does not escape$"
_ = x
}
func ptrlitNoEscape2() {
// Literal does not escape, but element does.
i := 0 // ERROR "moved to heap: i$"
- x := &Lit{&i} // ERROR "&Lit literal does not escape$"
+ x := &Lit{&i} // ERROR "&Lit{...} does not escape$"
sink = *x
}
func ptrlitEscape() {
// Both literal and element escape.
i := 0 // ERROR "moved to heap: i$"
- x := &Lit{&i} // ERROR "&Lit literal escapes to heap$"
+ x := &Lit{&i} // ERROR "&Lit{...} escapes to heap$"
sink = x
}
@@ -1760,18 +1760,18 @@ func stringtoslicerune2() {
}
func slicerunetostring0() {
- r := []rune{1, 2, 3} // ERROR "\[\]rune literal does not escape$"
+ r := []rune{1, 2, 3} // ERROR "\[\]rune{...} does not escape$"
s := string(r) // ERROR "string\(r\) does not escape$"
_ = s
}
func slicerunetostring1() string {
- r := []rune{1, 2, 3} // ERROR "\[\]rune literal does not escape$"
+ r := []rune{1, 2, 3} // ERROR "\[\]rune{...} does not escape$"
return string(r) // ERROR "string\(r\) escapes to heap$"
}
func slicerunetostring2() {
- r := []rune{1, 2, 3} // ERROR "\[\]rune literal does not escape$"
+ r := []rune{1, 2, 3} // ERROR "\[\]rune{...} does not escape$"
sink = string(r) // ERROR "string\(r\) escapes to heap$"
}
diff --git a/test/escape2n.go b/test/escape2n.go
index f771e0aef2..46e58f8566 100644
--- a/test/escape2n.go
+++ b/test/escape2n.go
@@ -118,15 +118,15 @@ type Bar struct {
}
func NewBar() *Bar {
- return &Bar{42, nil} // ERROR "&Bar literal escapes to heap$"
+ return &Bar{42, nil} // ERROR "&Bar{...} escapes to heap$"
}
func NewBarp(x *int) *Bar { // ERROR "leaking param: x$"
- return &Bar{42, x} // ERROR "&Bar literal escapes to heap$"
+ return &Bar{42, x} // ERROR "&Bar{...} escapes to heap$"
}
func NewBarp2(x *int) *Bar { // ERROR "x does not escape$"
- return &Bar{*x, nil} // ERROR "&Bar literal escapes to heap$"
+ return &Bar{*x, nil} // ERROR "&Bar{...} escapes to heap$"
}
func (b *Bar) NoLeak() int { // ERROR "b does not escape$"
@@ -173,7 +173,7 @@ type Bar2 struct {
}
func NewBar2() *Bar2 {
- return &Bar2{[12]int{42}, nil} // ERROR "&Bar2 literal escapes to heap$"
+ return &Bar2{[12]int{42}, nil} // ERROR "&Bar2{...} escapes to heap$"
}
func (b *Bar2) NoLeak() int { // ERROR "b does not escape$"
@@ -539,7 +539,7 @@ func foo72b() [10]*int {
// issue 2145
func foo73() {
- s := []int{3, 2, 1} // ERROR "\[\]int literal does not escape$"
+ s := []int{3, 2, 1} // ERROR "\[\]int{...} does not escape$"
for _, v := range s {
vv := v
// actually just escapes its scope
@@ -550,7 +550,7 @@ func foo73() {
}
func foo731() {
- s := []int{3, 2, 1} // ERROR "\[\]int literal does not escape$"
+ s := []int{3, 2, 1} // ERROR "\[\]int{...} does not escape$"
for _, v := range s {
vv := v // ERROR "moved to heap: vv$"
// actually just escapes its scope
@@ -562,7 +562,7 @@ func foo731() {
}
func foo74() {
- s := []int{3, 2, 1} // ERROR "\[\]int literal does not escape$"
+ s := []int{3, 2, 1} // ERROR "\[\]int{...} does not escape$"
for _, v := range s {
vv := v
// actually just escapes its scope
@@ -574,7 +574,7 @@ func foo74() {
}
func foo74a() {
- s := []int{3, 2, 1} // ERROR "\[\]int literal does not escape$"
+ s := []int{3, 2, 1} // ERROR "\[\]int{...} does not escape$"
for _, v := range s {
vv := v // ERROR "moved to heap: vv$"
// actually just escapes its scope
@@ -589,7 +589,7 @@ func foo74a() {
// issue 3975
func foo74b() {
var array [3]func()
- s := []int{3, 2, 1} // ERROR "\[\]int literal does not escape$"
+ s := []int{3, 2, 1} // ERROR "\[\]int{...} does not escape$"
for i, v := range s {
vv := v
// actually just escapes its scope
@@ -601,7 +601,7 @@ func foo74b() {
func foo74c() {
var array [3]func()
- s := []int{3, 2, 1} // ERROR "\[\]int literal does not escape$"
+ s := []int{3, 2, 1} // ERROR "\[\]int{...} does not escape$"
for i, v := range s {
vv := v // ERROR "moved to heap: vv$"
// actually just escapes its scope
@@ -759,15 +759,15 @@ type LimitedFooer struct {
}
func LimitFooer(r Fooer, n int64) Fooer { // ERROR "leaking param: r$"
- return &LimitedFooer{r, n} // ERROR "&LimitedFooer literal escapes to heap$"
+ return &LimitedFooer{r, n} // ERROR "&LimitedFooer{...} escapes to heap$"
}
func foo90(x *int) map[*int]*int { // ERROR "leaking param: x$"
- return map[*int]*int{nil: x} // ERROR "map\[\*int\]\*int literal escapes to heap$"
+ return map[*int]*int{nil: x} // ERROR "map\[\*int\]\*int{...} escapes to heap$"
}
func foo91(x *int) map[*int]*int { // ERROR "leaking param: x$"
- return map[*int]*int{x: nil} // ERROR "map\[\*int\]\*int literal escapes to heap$"
+ return map[*int]*int{x: nil} // ERROR "map\[\*int\]\*int{...} escapes to heap$"
}
func foo92(x *int) [2]*int { // ERROR "leaking param: x to result ~r1 level=0$"
@@ -870,15 +870,15 @@ func foo106(x *int) { // ERROR "leaking param: x$"
}
func foo107(x *int) map[*int]*int { // ERROR "leaking param: x$"
- return map[*int]*int{x: nil} // ERROR "map\[\*int\]\*int literal escapes to heap$"
+ return map[*int]*int{x: nil} // ERROR "map\[\*int\]\*int{...} escapes to heap$"
}
func foo108(x *int) map[*int]*int { // ERROR "leaking param: x$"
- return map[*int]*int{nil: x} // ERROR "map\[\*int\]\*int literal escapes to heap$"
+ return map[*int]*int{nil: x} // ERROR "map\[\*int\]\*int{...} escapes to heap$"
}
func foo109(x *int) *int { // ERROR "leaking param: x$"
- m := map[*int]*int{x: nil} // ERROR "map\[\*int\]\*int literal does not escape$"
+ m := map[*int]*int{x: nil} // ERROR "map\[\*int\]\*int{...} does not escape$"
for k, _ := range m {
return k
}
@@ -886,12 +886,12 @@ func foo109(x *int) *int { // ERROR "leaking param: x$"
}
func foo110(x *int) *int { // ERROR "leaking param: x$"
- m := map[*int]*int{nil: x} // ERROR "map\[\*int\]\*int literal does not escape$"
+ m := map[*int]*int{nil: x} // ERROR "map\[\*int\]\*int{...} does not escape$"
return m[nil]
}
func foo111(x *int) *int { // ERROR "leaking param: x to result ~r1 level=0"
- m := []*int{x} // ERROR "\[\]\*int literal does not escape$"
+ m := []*int{x} // ERROR "\[\]\*int{...} does not escape$"
return m[0]
}
@@ -906,7 +906,7 @@ func foo113(x *int) *int { // ERROR "leaking param: x to result ~r1 level=0$"
}
func foo114(x *int) *int { // ERROR "leaking param: x to result ~r1 level=0$"
- m := &Bar{ii: x} // ERROR "&Bar literal does not escape$"
+ m := &Bar{ii: x} // ERROR "&Bar{...} does not escape$"
return m.ii
}
@@ -1343,8 +1343,8 @@ func foo140() interface{} {
X string
T *T
}
- t := &T{} // ERROR "&T literal escapes to heap$"
- return U{ // ERROR "U literal escapes to heap$"
+ t := &T{} // ERROR "&T{} escapes to heap$"
+ return U{ // ERROR "U{...} escapes to heap$"
X: t.X,
T: t,
}
@@ -1530,7 +1530,7 @@ type V struct {
}
func NewV(u U) *V { // ERROR "leaking param: u$"
- return &V{u.String()} // ERROR "&V literal escapes to heap$"
+ return &V{u.String()} // ERROR "&V{...} escapes to heap$"
}
func foo152() {
@@ -1571,21 +1571,21 @@ type Lit struct {
func ptrlitNoescape() {
// Both literal and element do not escape.
i := 0
- x := &Lit{&i} // ERROR "&Lit literal does not escape$"
+ x := &Lit{&i} // ERROR "&Lit{...} does not escape$"
_ = x
}
func ptrlitNoEscape2() {
// Literal does not escape, but element does.
i := 0 // ERROR "moved to heap: i$"
- x := &Lit{&i} // ERROR "&Lit literal does not escape$"
+ x := &Lit{&i} // ERROR "&Lit{...} does not escape$"
sink = *x
}
func ptrlitEscape() {
// Both literal and element escape.
i := 0 // ERROR "moved to heap: i$"
- x := &Lit{&i} // ERROR "&Lit literal escapes to heap$"
+ x := &Lit{&i} // ERROR "&Lit{...} escapes to heap$"
sink = x
}
@@ -1760,18 +1760,18 @@ func stringtoslicerune2() {
}
func slicerunetostring0() {
- r := []rune{1, 2, 3} // ERROR "\[\]rune literal does not escape$"
+ r := []rune{1, 2, 3} // ERROR "\[\]rune{...} does not escape$"
s := string(r) // ERROR "string\(r\) does not escape$"
_ = s
}
func slicerunetostring1() string {
- r := []rune{1, 2, 3} // ERROR "\[\]rune literal does not escape$"
+ r := []rune{1, 2, 3} // ERROR "\[\]rune{...} does not escape$"
return string(r) // ERROR "string\(r\) escapes to heap$"
}
func slicerunetostring2() {
- r := []rune{1, 2, 3} // ERROR "\[\]rune literal does not escape$"
+ r := []rune{1, 2, 3} // ERROR "\[\]rune{...} does not escape$"
sink = string(r) // ERROR "string\(r\) escapes to heap$"
}
diff --git a/test/escape_calls.go b/test/escape_calls.go
index 2dbfee1558..9e1db5426e 100644
--- a/test/escape_calls.go
+++ b/test/escape_calls.go
@@ -50,5 +50,5 @@ func bar() {
f := prototype
f = func(ss []string) { got = append(got, ss) } // ERROR "leaking param: ss" "func literal does not escape"
s := "string"
- f([]string{s}) // ERROR "\[\]string literal escapes to heap"
+ f([]string{s}) // ERROR "\[\]string{...} escapes to heap"
}
diff --git a/test/escape_field.go b/test/escape_field.go
index bf1dfb18ff..95d0784d91 100644
--- a/test/escape_field.go
+++ b/test/escape_field.go
@@ -127,20 +127,20 @@ func field12() {
func field13() {
i := 0 // ERROR "moved to heap: i$"
- x := &X{p1: &i} // ERROR "&X literal does not escape$"
+ x := &X{p1: &i} // ERROR "&X{...} does not escape$"
sink = x.p1
}
func field14() {
i := 0 // ERROR "moved to heap: i$"
// BAD: &i should not escape
- x := &X{p1: &i} // ERROR "&X literal does not escape$"
+ x := &X{p1: &i} // ERROR "&X{...} does not escape$"
sink = x.p2
}
func field15() {
i := 0 // ERROR "moved to heap: i$"
- x := &X{p1: &i} // ERROR "&X literal escapes to heap$"
+ x := &X{p1: &i} // ERROR "&X{...} escapes to heap$"
sink = x
}
diff --git a/test/escape_iface.go b/test/escape_iface.go
index 118ed3c56f..7b0914cadb 100644
--- a/test/escape_iface.go
+++ b/test/escape_iface.go
@@ -37,7 +37,7 @@ func efaceEscape0() {
_ = x
}
{
- i := 0 // ERROR "moved to heap: i"
+ i := 0 // ERROR "moved to heap: i"
v := M0{&i}
var x M = v
sink = x
@@ -50,7 +50,7 @@ func efaceEscape0() {
_ = v1
}
{
- i := 0 // ERROR "moved to heap: i"
+ i := 0 // ERROR "moved to heap: i"
v := M0{&i}
// BAD: v does not escape to heap here
var x M = v
@@ -58,14 +58,14 @@ func efaceEscape0() {
sink = v1
}
{
- i := 0 // ERROR "moved to heap: i"
+ i := 0 // ERROR "moved to heap: i"
v := M0{&i}
// BAD: v does not escape to heap here
var x M = v
x.M()
}
{
- i := 0 // ERROR "moved to heap: i"
+ i := 0 // ERROR "moved to heap: i"
v := M0{&i}
var x M = v
mescapes(x)
@@ -91,46 +91,46 @@ func efaceEscape1() {
{
i := 0
v := M1{&i, 0}
- var x M = v // ERROR "v does not escape"
+ var x M = v // ERROR "v does not escape"
_ = x
}
{
- i := 0 // ERROR "moved to heap: i"
+ i := 0 // ERROR "moved to heap: i"
v := M1{&i, 0}
- var x M = v // ERROR "v escapes to heap"
+ var x M = v // ERROR "v escapes to heap"
sink = x
}
{
i := 0
v := M1{&i, 0}
- var x M = v // ERROR "v does not escape"
+ var x M = v // ERROR "v does not escape"
v1 := x.(M1)
_ = v1
}
{
- i := 0 // ERROR "moved to heap: i"
+ i := 0 // ERROR "moved to heap: i"
v := M1{&i, 0}
var x M = v // ERROR "v does not escape"
v1 := x.(M1)
sink = v1 // ERROR "v1 escapes to heap"
}
{
- i := 0 // ERROR "moved to heap: i"
+ i := 0 // ERROR "moved to heap: i"
v := M1{&i, 0}
// BAD: v does not escape to heap here
var x M = v // ERROR "v escapes to heap"
x.M()
}
{
- i := 0 // ERROR "moved to heap: i"
+ i := 0 // ERROR "moved to heap: i"
v := M1{&i, 0}
- var x M = v // ERROR "v escapes to heap"
+ var x M = v // ERROR "v escapes to heap"
mescapes(x)
}
{
i := 0
v := M1{&i, 0}
- var x M = v // ERROR "v does not escape"
+ var x M = v // ERROR "v does not escape"
mdoesnotescape(x)
}
}
@@ -146,26 +146,26 @@ func (*M2) M() {
func efaceEscape2() {
{
i := 0
- v := &M2{&i} // ERROR "&M2 literal does not escape"
+ v := &M2{&i} // ERROR "&M2{...} does not escape"
var x M = v
_ = x
}
{
i := 0 // ERROR "moved to heap: i"
- v := &M2{&i} // ERROR "&M2 literal escapes to heap"
+ v := &M2{&i} // ERROR "&M2{...} escapes to heap"
var x M = v
sink = x
}
{
i := 0
- v := &M2{&i} // ERROR "&M2 literal does not escape"
+ v := &M2{&i} // ERROR "&M2{...} does not escape"
var x M = v
v1 := x.(*M2)
_ = v1
}
{
i := 0 // ERROR "moved to heap: i"
- v := &M2{&i} // ERROR "&M2 literal escapes to heap"
+ v := &M2{&i} // ERROR "&M2{...} escapes to heap"
// BAD: v does not escape to heap here
var x M = v
v1 := x.(*M2)
@@ -173,7 +173,7 @@ func efaceEscape2() {
}
{
i := 0 // ERROR "moved to heap: i"
- v := &M2{&i} // ERROR "&M2 literal does not escape"
+ v := &M2{&i} // ERROR "&M2{...} does not escape"
// BAD: v does not escape to heap here
var x M = v
v1 := x.(*M2)
@@ -181,7 +181,7 @@ func efaceEscape2() {
}
{
i := 0 // ERROR "moved to heap: i"
- v := &M2{&i} // ERROR "&M2 literal does not escape"
+ v := &M2{&i} // ERROR "&M2{...} does not escape"
// BAD: v does not escape to heap here
var x M = v
v1, ok := x.(*M2)
@@ -190,20 +190,20 @@ func efaceEscape2() {
}
{
i := 0 // ERROR "moved to heap: i"
- v := &M2{&i} // ERROR "&M2 literal escapes to heap"
+ v := &M2{&i} // ERROR "&M2{...} escapes to heap"
// BAD: v does not escape to heap here
var x M = v
x.M()
}
{
i := 0 // ERROR "moved to heap: i"
- v := &M2{&i} // ERROR "&M2 literal escapes to heap"
+ v := &M2{&i} // ERROR "&M2{...} escapes to heap"
var x M = v
mescapes(x)
}
{
i := 0
- v := &M2{&i} // ERROR "&M2 literal does not escape"
+ v := &M2{&i} // ERROR "&M2{...} does not escape"
var x M = v
mdoesnotescape(x)
}
@@ -219,8 +219,8 @@ type T2 struct {
func dotTypeEscape() *T2 { // #11931
var x interface{}
- x = &T1{p: new(int)} // ERROR "new\(int\) escapes to heap" "&T1 literal does not escape"
- return &T2{ // ERROR "&T2 literal escapes to heap"
+ x = &T1{p: new(int)} // ERROR "new\(int\) escapes to heap" "&T1{...} does not escape"
+ return &T2{ // ERROR "&T2{...} escapes to heap"
T1: *(x.(*T1)),
}
}
@@ -244,7 +244,7 @@ func dotTypeEscape2() { // #13805, #15796
var x interface{} = i // ERROR "i does not escape"
var y interface{} = j // ERROR "j does not escape"
- sink = x.(int) // ERROR "x.\(int\) escapes to heap"
+ sink = x.(int) // ERROR "x.\(int\) escapes to heap"
sink, *(&ok) = y.(int)
}
{
diff --git a/test/escape_indir.go b/test/escape_indir.go
index 19889f259f..12005e35f9 100644
--- a/test/escape_indir.go
+++ b/test/escape_indir.go
@@ -23,7 +23,7 @@ type ConstPtr2 struct {
func constptr0() {
i := 0 // ERROR "moved to heap: i"
- x := &ConstPtr{} // ERROR "&ConstPtr literal does not escape"
+ x := &ConstPtr{} // ERROR "&ConstPtr{} does not escape"
// BAD: i should not escape here
x.p = &i
_ = x
@@ -31,55 +31,55 @@ func constptr0() {
func constptr01() *ConstPtr {
i := 0 // ERROR "moved to heap: i"
- x := &ConstPtr{} // ERROR "&ConstPtr literal escapes to heap"
+ x := &ConstPtr{} // ERROR "&ConstPtr{} escapes to heap"
x.p = &i
return x
}
func constptr02() ConstPtr {
i := 0 // ERROR "moved to heap: i"
- x := &ConstPtr{} // ERROR "&ConstPtr literal does not escape"
+ x := &ConstPtr{} // ERROR "&ConstPtr{} does not escape"
x.p = &i
return *x
}
func constptr03() **ConstPtr {
i := 0 // ERROR "moved to heap: i"
- x := &ConstPtr{} // ERROR "&ConstPtr literal escapes to heap" "moved to heap: x"
+ x := &ConstPtr{} // ERROR "&ConstPtr{} escapes to heap" "moved to heap: x"
x.p = &i
return &x
}
func constptr1() {
i := 0 // ERROR "moved to heap: i"
- x := &ConstPtr{} // ERROR "&ConstPtr literal escapes to heap"
+ x := &ConstPtr{} // ERROR "&ConstPtr{} escapes to heap"
x.p = &i
sink = x
}
func constptr2() {
i := 0 // ERROR "moved to heap: i"
- x := &ConstPtr{} // ERROR "&ConstPtr literal does not escape"
+ x := &ConstPtr{} // ERROR "&ConstPtr{} does not escape"
x.p = &i
- sink = *x // ERROR "\*x escapes to heap"
+ sink = *x // ERROR "\*x escapes to heap"
}
func constptr4() *ConstPtr {
p := new(ConstPtr) // ERROR "new\(ConstPtr\) escapes to heap"
- *p = *&ConstPtr{} // ERROR "&ConstPtr literal does not escape"
+ *p = *&ConstPtr{} // ERROR "&ConstPtr{} does not escape"
return p
}
func constptr5() *ConstPtr {
p := new(ConstPtr) // ERROR "new\(ConstPtr\) escapes to heap"
- p1 := &ConstPtr{} // ERROR "&ConstPtr literal does not escape"
+ p1 := &ConstPtr{} // ERROR "&ConstPtr{} does not escape"
*p = *p1
return p
}
// BAD: p should not escape here
func constptr6(p *ConstPtr) { // ERROR "leaking param content: p"
- p1 := &ConstPtr{} // ERROR "&ConstPtr literal does not escape"
+ p1 := &ConstPtr{} // ERROR "&ConstPtr{} does not escape"
*p1 = *p
_ = p1
}
@@ -102,17 +102,17 @@ func constptr8() *ConstPtr {
func constptr9() ConstPtr {
p := new(ConstPtr) // ERROR "new\(ConstPtr\) does not escape"
var p1 ConstPtr2
- i := 0 // ERROR "moved to heap: i"
+ i := 0 // ERROR "moved to heap: i"
p1.p = &i
p.c = p1
return *p
}
func constptr10() ConstPtr {
- x := &ConstPtr{} // ERROR "moved to heap: x" "&ConstPtr literal escapes to heap"
+ x := &ConstPtr{} // ERROR "moved to heap: x" "&ConstPtr{} escapes to heap"
i := 0 // ERROR "moved to heap: i"
var p *ConstPtr
- p = &ConstPtr{p: &i, x: &x} // ERROR "&ConstPtr literal does not escape"
+ p = &ConstPtr{p: &i, x: &x} // ERROR "&ConstPtr{...} does not escape"
var pp **ConstPtr
pp = &p
return **pp
@@ -121,7 +121,7 @@ func constptr10() ConstPtr {
func constptr11() *ConstPtr {
i := 0 // ERROR "moved to heap: i"
p := new(ConstPtr) // ERROR "new\(ConstPtr\) escapes to heap"
- p1 := &ConstPtr{} // ERROR "&ConstPtr literal does not escape"
+ p1 := &ConstPtr{} // ERROR "&ConstPtr{} does not escape"
p1.p = &i
*p = *p1
return p
@@ -134,7 +134,7 @@ func foo(p **int) { // ERROR "p does not escape"
}
func foo1(p *int) { // ERROR "p does not escape"
- i := 0 // ERROR "moved to heap: i"
+ i := 0 // ERROR "moved to heap: i"
y := &p
*y = &i
}
@@ -148,13 +148,13 @@ func foo2() {
var z Z
z.f = &x
p := z.f
- i := 0 // ERROR "moved to heap: i"
+ i := 0 // ERROR "moved to heap: i"
*p = &i
}
var global *byte
func f() {
- var x byte // ERROR "moved to heap: x"
+ var x byte // ERROR "moved to heap: x"
global = &*&x
}
diff --git a/test/escape_map.go b/test/escape_map.go
index 0e9896a9fc..23abaa1e0c 100644
--- a/test/escape_map.go
+++ b/test/escape_map.go
@@ -15,7 +15,7 @@ func map0() {
// BAD: i should not escape
i := 0 // ERROR "moved to heap: i"
// BAD: j should not escape
- j := 0 // ERROR "moved to heap: j"
+ j := 0 // ERROR "moved to heap: j"
m[&i] = &j
_ = m
}
@@ -23,8 +23,8 @@ func map0() {
func map1() *int {
m := make(map[*int]*int) // ERROR "make\(map\[\*int\]\*int\) does not escape"
// BAD: i should not escape
- i := 0 // ERROR "moved to heap: i"
- j := 0 // ERROR "moved to heap: j"
+ i := 0 // ERROR "moved to heap: i"
+ j := 0 // ERROR "moved to heap: j"
m[&i] = &j
return m[&i]
}
@@ -41,7 +41,7 @@ func map3() []*int {
m := make(map[*int]*int) // ERROR "make\(map\[\*int\]\*int\) does not escape"
i := 0 // ERROR "moved to heap: i"
// BAD: j should not escape
- j := 0 // ERROR "moved to heap: j"
+ j := 0 // ERROR "moved to heap: j"
m[&i] = &j
var r []*int
for k := range m {
@@ -53,8 +53,8 @@ func map3() []*int {
func map4() []*int {
m := make(map[*int]*int) // ERROR "make\(map\[\*int\]\*int\) does not escape"
// BAD: i should not escape
- i := 0 // ERROR "moved to heap: i"
- j := 0 // ERROR "moved to heap: j"
+ i := 0 // ERROR "moved to heap: i"
+ j := 0 // ERROR "moved to heap: j"
m[&i] = &j
var r []*int
for k, v := range m {
@@ -68,8 +68,8 @@ func map4() []*int {
}
func map5(m map[*int]*int) { // ERROR "m does not escape"
- i := 0 // ERROR "moved to heap: i"
- j := 0 // ERROR "moved to heap: j"
+ i := 0 // ERROR "moved to heap: i"
+ j := 0 // ERROR "moved to heap: j"
m[&i] = &j
}
@@ -77,8 +77,8 @@ func map6(m map[*int]*int) { // ERROR "m does not escape"
if m != nil {
m = make(map[*int]*int) // ERROR "make\(map\[\*int\]\*int\) does not escape"
}
- i := 0 // ERROR "moved to heap: i"
- j := 0 // ERROR "moved to heap: j"
+ i := 0 // ERROR "moved to heap: i"
+ j := 0 // ERROR "moved to heap: j"
m[&i] = &j
}
@@ -87,14 +87,14 @@ func map7() {
i := 0 // ERROR "moved to heap: i"
// BAD: j should not escape
j := 0 // ERROR "moved to heap: j"
- m := map[*int]*int{&i: &j} // ERROR "literal does not escape"
+ m := map[*int]*int{&i: &j} // ERROR "map\[\*int\]\*int{...} does not escape"
_ = m
}
func map8() {
i := 0 // ERROR "moved to heap: i"
j := 0 // ERROR "moved to heap: j"
- m := map[*int]*int{&i: &j} // ERROR "literal escapes to heap"
+ m := map[*int]*int{&i: &j} // ERROR "map\[\*int\]\*int{...} escapes to heap"
sink = m
}
@@ -102,6 +102,6 @@ func map9() *int {
// BAD: i should not escape
i := 0 // ERROR "moved to heap: i"
j := 0 // ERROR "moved to heap: j"
- m := map[*int]*int{&i: &j} // ERROR "literal does not escape"
+ m := map[*int]*int{&i: &j} // ERROR "map\[\*int\]\*int{...} does not escape"
return m[nil]
}
diff --git a/test/escape_param.go b/test/escape_param.go
index d8fafc53f8..993e914e1d 100644
--- a/test/escape_param.go
+++ b/test/escape_param.go
@@ -26,7 +26,7 @@ func caller0a() {
}
func caller0b() {
- i := 0 // ERROR "moved to heap: i$"
+ i := 0 // ERROR "moved to heap: i$"
sink = param0(&i)
}
@@ -150,11 +150,11 @@ func caller3a() {
}
func caller3b() {
- i := 0 // ERROR "moved to heap: i$"
- j := 0 // ERROR "moved to heap: j$"
+ i := 0 // ERROR "moved to heap: i$"
+ j := 0 // ERROR "moved to heap: j$"
p := Pair{&i, &j}
param3(&p)
- sink = p // ERROR "p escapes to heap$"
+ sink = p // ERROR "p escapes to heap$"
}
// in -> rcvr
@@ -173,7 +173,7 @@ func caller4b() {
i := 0 // ERROR "moved to heap: i$"
p := Pair{}
p.param4(&i)
- sink = p // ERROR "p escapes to heap$"
+ sink = p // ERROR "p escapes to heap$"
}
// in -> heap
@@ -182,7 +182,7 @@ func param5(i *int) { // ERROR "leaking param: i$"
}
func caller5() {
- i := 0 // ERROR "moved to heap: i$"
+ i := 0 // ERROR "moved to heap: i$"
param5(&i)
}
@@ -192,8 +192,8 @@ func param6(i ***int) { // ERROR "leaking param content: i$"
}
func caller6a() {
- i := 0 // ERROR "moved to heap: i$"
- p := &i // ERROR "moved to heap: p$"
+ i := 0 // ERROR "moved to heap: i$"
+ p := &i // ERROR "moved to heap: p$"
p2 := &p
param6(&p2)
}
@@ -204,7 +204,7 @@ func param7(i ***int) { // ERROR "leaking param content: i$"
}
func caller7() {
- i := 0 // ERROR "moved to heap: i$"
+ i := 0 // ERROR "moved to heap: i$"
p := &i
p2 := &p
param7(&p2)
@@ -234,8 +234,8 @@ func caller9a() {
}
func caller9b() {
- i := 0 // ERROR "moved to heap: i$"
- p := &i // ERROR "moved to heap: p$"
+ i := 0 // ERROR "moved to heap: i$"
+ p := &i // ERROR "moved to heap: p$"
p2 := &p
sink = param9(&p2)
}
@@ -253,7 +253,7 @@ func caller10a() {
}
func caller10b() {
- i := 0 // ERROR "moved to heap: i$"
+ i := 0 // ERROR "moved to heap: i$"
p := &i
p2 := &p
sink = param10(&p2)
@@ -265,26 +265,26 @@ func param11(i **int) ***int { // ERROR "moved to heap: i$"
}
func caller11a() {
- i := 0 // ERROR "moved to heap: i"
- p := &i // ERROR "moved to heap: p"
+ i := 0 // ERROR "moved to heap: i"
+ p := &i // ERROR "moved to heap: p"
_ = param11(&p)
}
func caller11b() {
- i := 0 // ERROR "moved to heap: i$"
- p := &i // ERROR "moved to heap: p$"
+ i := 0 // ERROR "moved to heap: i$"
+ p := &i // ERROR "moved to heap: p$"
sink = param11(&p)
}
func caller11c() { // GOOD
- i := 0 // ERROR "moved to heap: i$"
- p := &i // ERROR "moved to heap: p"
+ i := 0 // ERROR "moved to heap: i$"
+ p := &i // ERROR "moved to heap: p"
sink = *param11(&p)
}
func caller11d() {
- i := 0 // ERROR "moved to heap: i$"
- p := &i // ERROR "moved to heap: p"
+ i := 0 // ERROR "moved to heap: i$"
+ p := &i // ERROR "moved to heap: p"
p2 := &p
sink = param11(p2)
}
@@ -309,7 +309,7 @@ func caller12a() {
func caller12b() {
i := 0 // ERROR "moved to heap: i$"
p := &i // ERROR "moved to heap: p$"
- r := &Indir{} // ERROR "&Indir literal does not escape$"
+ r := &Indir{} // ERROR "&Indir{} does not escape$"
r.param12(&p)
_ = r
}
@@ -359,7 +359,7 @@ func caller13b() {
func caller13c() {
i := 0 // ERROR "moved to heap: i$"
var p *int
- v := &Val{&p} // ERROR "&Val literal does not escape$"
+ v := &Val{&p} // ERROR "&Val{...} does not escape$"
v.param13(&i)
_ = v
}
@@ -374,8 +374,8 @@ func caller13d() {
}
func caller13e() {
- i := 0 // ERROR "moved to heap: i$"
- var p *int // ERROR "moved to heap: p$"
+ i := 0 // ERROR "moved to heap: i$"
+ var p *int // ERROR "moved to heap: p$"
v := Val{&p}
v.param13(&i)
sink = v
@@ -384,7 +384,7 @@ func caller13e() {
func caller13f() {
i := 0 // ERROR "moved to heap: i$"
var p *int // ERROR "moved to heap: p$"
- v := &Val{&p} // ERROR "&Val literal escapes to heap$"
+ v := &Val{&p} // ERROR "&Val{...} escapes to heap$"
v.param13(&i)
sink = v
}
@@ -400,9 +400,9 @@ func caller13g() {
func caller13h() {
i := 0 // ERROR "moved to heap: i$"
var p *int
- v := &Val{&p} // ERROR "&Val literal does not escape$"
+ v := &Val{&p} // ERROR "&Val{...} does not escape$"
v.param13(&i)
- sink = **v.p // ERROR "\* \(\*v\.p\) escapes to heap"
+ sink = **v.p // ERROR "\* \(\*v\.p\) escapes to heap"
}
type Node struct {
@@ -412,15 +412,15 @@ type Node struct {
var Sink *Node
func f(x *Node) { // ERROR "leaking param content: x"
- Sink = &Node{x.p} // ERROR "&Node literal escapes to heap"
+ Sink = &Node{x.p} // ERROR "&Node{...} escapes to heap"
}
func g(x *Node) *Node { // ERROR "leaking param content: x"
- return &Node{x.p} // ERROR "&Node literal escapes to heap"
+ return &Node{x.p} // ERROR "&Node{...} escapes to heap"
}
func h(x *Node) { // ERROR "leaking param: x"
- y := &Node{x} // ERROR "&Node literal does not escape"
+ y := &Node{x} // ERROR "&Node{...} does not escape"
Sink = g(y)
f(y)
}
diff --git a/test/escape_slice.go b/test/escape_slice.go
index d2cdaa6a01..6ce852e9c5 100644
--- a/test/escape_slice.go
+++ b/test/escape_slice.go
@@ -77,19 +77,19 @@ func slice7() *int {
func slice8() {
i := 0
- s := []*int{&i} // ERROR "literal does not escape"
+ s := []*int{&i} // ERROR "\[\]\*int{...} does not escape"
_ = s
}
func slice9() *int {
i := 0 // ERROR "moved to heap: i"
- s := []*int{&i} // ERROR "literal does not escape"
+ s := []*int{&i} // ERROR "\[\]\*int{...} does not escape"
return s[0]
}
func slice10() []*int {
i := 0 // ERROR "moved to heap: i"
- s := []*int{&i} // ERROR "literal escapes to heap"
+ s := []*int{&i} // ERROR "\[\]\*int{...} escapes to heap"
return s
}
@@ -103,7 +103,7 @@ func slice11() {
func envForDir(dir string) []string { // ERROR "dir does not escape"
env := os.Environ()
- return mergeEnvLists([]string{"PWD=" + dir}, env) // ERROR ".PWD=. \+ dir escapes to heap" "\[\]string literal does not escape"
+ return mergeEnvLists([]string{"PWD=" + dir}, env) // ERROR ".PWD=. \+ dir escapes to heap" "\[\]string{...} does not escape"
}
func mergeEnvLists(in, out []string) []string { // ERROR "leaking param content: in" "leaking param content: out" "leaking param: out to result ~r2 level=0"
@@ -160,14 +160,14 @@ var resolveIPAddrTests = []resolveIPAddrTest{
func setupTestData() {
resolveIPAddrTests = append(resolveIPAddrTests,
- []resolveIPAddrTest{ // ERROR "\[\]resolveIPAddrTest literal does not escape"
+ []resolveIPAddrTest{ // ERROR "\[\]resolveIPAddrTest{...} does not escape"
{"ip",
"localhost",
- &IPAddr{IP: IPv4(127, 0, 0, 1)}, // ERROR "&IPAddr literal escapes to heap"
+ &IPAddr{IP: IPv4(127, 0, 0, 1)}, // ERROR "&IPAddr{...} escapes to heap"
nil},
{"ip4",
"localhost",
- &IPAddr{IP: IPv4(127, 0, 0, 1)}, // ERROR "&IPAddr literal escapes to heap"
+ &IPAddr{IP: IPv4(127, 0, 0, 1)}, // ERROR "&IPAddr{...} escapes to heap"
nil},
}...)
}
diff --git a/test/escape_struct_param1.go b/test/escape_struct_param1.go
index 70b36191ab..496172c166 100644
--- a/test/escape_struct_param1.go
+++ b/test/escape_struct_param1.go
@@ -35,27 +35,27 @@ func (u *U) SPPi() *string { // ERROR "leaking param: u to result ~r0 level=2$"
}
func tSPPi() {
- s := "cat" // ERROR "moved to heap: s$"
+ s := "cat" // ERROR "moved to heap: s$"
ps := &s
pps := &ps
- pu := &U{ps, pps} // ERROR "&U literal does not escape$"
+ pu := &U{ps, pps} // ERROR "&U{...} does not escape$"
Ssink = pu.SPPi()
}
func tiSPP() {
- s := "cat" // ERROR "moved to heap: s$"
+ s := "cat" // ERROR "moved to heap: s$"
ps := &s
pps := &ps
- pu := &U{ps, pps} // ERROR "&U literal does not escape$"
+ pu := &U{ps, pps} // ERROR "&U{...} does not escape$"
Ssink = *pu.SPP()
}
// BAD: need fine-grained (field-sensitive) analysis to avoid spurious escape of ps
func tSP() {
- s := "cat" // ERROR "moved to heap: s$"
- ps := &s // ERROR "moved to heap: ps$"
+ s := "cat" // ERROR "moved to heap: s$"
+ ps := &s // ERROR "moved to heap: ps$"
pps := &ps
- pu := &U{ps, pps} // ERROR "&U literal does not escape$"
+ pu := &U{ps, pps} // ERROR "&U{...} does not escape$"
Ssink = pu.SP()
}
@@ -114,72 +114,72 @@ func (v *V) UPiSPd() *string { // ERROR "leaking param: v to result ~r0 level=2$
// BAD: need fine-grained (field-sensitive) analysis to avoid spurious escape of all but &s3
func tUPiSPa() {
s1 := "ant"
- s2 := "bat" // ERROR "moved to heap: s2$"
- s3 := "cat" // ERROR "moved to heap: s3$"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s2 := "bat" // ERROR "moved to heap: s2$"
+ s3 := "cat" // ERROR "moved to heap: s3$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
- ps4 := &s4 // ERROR "moved to heap: ps4$"
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps4 := &s4 // ERROR "moved to heap: ps4$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal escapes to heap$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} escapes to heap$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPa() // Ssink = &s3 (only &s3 really escapes)
}
// BAD: need fine-grained (field-sensitive) analysis to avoid spurious escape of all but &s3
func tUPiSPb() {
s1 := "ant"
- s2 := "bat" // ERROR "moved to heap: s2$"
- s3 := "cat" // ERROR "moved to heap: s3$"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s2 := "bat" // ERROR "moved to heap: s2$"
+ s3 := "cat" // ERROR "moved to heap: s3$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
- ps4 := &s4 // ERROR "moved to heap: ps4$"
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps4 := &s4 // ERROR "moved to heap: ps4$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal escapes to heap$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} escapes to heap$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPb() // Ssink = &s3 (only &s3 really escapes)
}
// BAD: need fine-grained (field-sensitive) analysis to avoid spurious escape of all but &s3
func tUPiSPc() {
s1 := "ant"
- s2 := "bat" // ERROR "moved to heap: s2$"
- s3 := "cat" // ERROR "moved to heap: s3$"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s2 := "bat" // ERROR "moved to heap: s2$"
+ s3 := "cat" // ERROR "moved to heap: s3$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
- ps4 := &s4 // ERROR "moved to heap: ps4$"
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps4 := &s4 // ERROR "moved to heap: ps4$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal escapes to heap$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} escapes to heap$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPc() // Ssink = &s3 (only &s3 really escapes)
}
// BAD: need fine-grained (field-sensitive) analysis to avoid spurious escape of all but &s3
func tUPiSPd() {
s1 := "ant"
- s2 := "bat" // ERROR "moved to heap: s2$"
- s3 := "cat" // ERROR "moved to heap: s3$"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s2 := "bat" // ERROR "moved to heap: s2$"
+ s3 := "cat" // ERROR "moved to heap: s3$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
- ps4 := &s4 // ERROR "moved to heap: ps4$"
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps4 := &s4 // ERROR "moved to heap: ps4$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal escapes to heap$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} escapes to heap$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPd() // Ssink = &s3 (only &s3 really escapes)
}
@@ -204,16 +204,16 @@ func tUPiSPPia() {
s1 := "ant"
s2 := "bat"
s3 := "cat"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
ps4 := &s4
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal does not escape$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} does not escape$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPPia() // Ssink = *&ps4 = &s4 (only &s4 really escapes)
}
@@ -222,16 +222,16 @@ func tUPiSPPib() {
s1 := "ant"
s2 := "bat"
s3 := "cat"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
ps4 := &s4
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal does not escape$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} does not escape$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPPib() // Ssink = *&ps4 = &s4 (only &s4 really escapes)
}
@@ -240,16 +240,16 @@ func tUPiSPPic() {
s1 := "ant"
s2 := "bat"
s3 := "cat"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
ps4 := &s4
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal does not escape$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} does not escape$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPPic() // Ssink = *&ps4 = &s4 (only &s4 really escapes)
}
@@ -258,16 +258,16 @@ func tUPiSPPid() {
s1 := "ant"
s2 := "bat"
s3 := "cat"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
ps4 := &s4
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal does not escape$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} does not escape$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPPid() // Ssink = *&ps4 = &s4 (only &s4 really escapes)
}
@@ -286,13 +286,13 @@ func tUPPiSPPia() {
s3 := "cat"
s4 := "dog"
s5 := "emu"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
ps4 := &s4
ps6 := &s6
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal does not escape$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} does not escape$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPPiSPPia() // Ssink = *&ps6 = &s6 (only &s6 really escapes)
}
diff --git a/test/escape_struct_param2.go b/test/escape_struct_param2.go
index e42be79793..946397ea9f 100644
--- a/test/escape_struct_param2.go
+++ b/test/escape_struct_param2.go
@@ -35,27 +35,27 @@ func (u U) SPPi() *string { // ERROR "leaking param: u to result ~r0 level=1$"
}
func tSPPi() {
- s := "cat" // ERROR "moved to heap: s$"
+ s := "cat" // ERROR "moved to heap: s$"
ps := &s
pps := &ps
- pu := &U{ps, pps} // ERROR "&U literal does not escape$"
+ pu := &U{ps, pps} // ERROR "&U{...} does not escape$"
Ssink = pu.SPPi()
}
func tiSPP() {
- s := "cat" // ERROR "moved to heap: s$"
+ s := "cat" // ERROR "moved to heap: s$"
ps := &s
pps := &ps
- pu := &U{ps, pps} // ERROR "&U literal does not escape$"
+ pu := &U{ps, pps} // ERROR "&U{...} does not escape$"
Ssink = *pu.SPP()
}
// BAD: need fine-grained analysis to avoid spurious escape of ps
func tSP() {
- s := "cat" // ERROR "moved to heap: s$"
- ps := &s // ERROR "moved to heap: ps$"
+ s := "cat" // ERROR "moved to heap: s$"
+ ps := &s // ERROR "moved to heap: ps$"
pps := &ps
- pu := &U{ps, pps} // ERROR "&U literal does not escape$"
+ pu := &U{ps, pps} // ERROR "&U{...} does not escape$"
Ssink = pu.SP()
}
@@ -114,72 +114,72 @@ func (v V) UPiSPd() *string { // ERROR "leaking param: v to result ~r0 level=1$"
// BAD: need fine-grained (field-sensitive) analysis to avoid spurious escape of all but &s3
func tUPiSPa() {
s1 := "ant"
- s2 := "bat" // ERROR "moved to heap: s2$"
- s3 := "cat" // ERROR "moved to heap: s3$"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s2 := "bat" // ERROR "moved to heap: s2$"
+ s3 := "cat" // ERROR "moved to heap: s3$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
- ps4 := &s4 // ERROR "moved to heap: ps4$"
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps4 := &s4 // ERROR "moved to heap: ps4$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal escapes to heap$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} escapes to heap$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPa() // Ssink = &s3 (only &s3 really escapes)
}
// BAD: need fine-grained (field-sensitive) analysis to avoid spurious escape of all but &s3
func tUPiSPb() {
s1 := "ant"
- s2 := "bat" // ERROR "moved to heap: s2$"
- s3 := "cat" // ERROR "moved to heap: s3$"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s2 := "bat" // ERROR "moved to heap: s2$"
+ s3 := "cat" // ERROR "moved to heap: s3$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
- ps4 := &s4 // ERROR "moved to heap: ps4$"
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps4 := &s4 // ERROR "moved to heap: ps4$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal escapes to heap$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} escapes to heap$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPb() // Ssink = &s3 (only &s3 really escapes)
}
// BAD: need fine-grained (field-sensitive) analysis to avoid spurious escape of all but &s3
func tUPiSPc() {
s1 := "ant"
- s2 := "bat" // ERROR "moved to heap: s2$"
- s3 := "cat" // ERROR "moved to heap: s3$"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s2 := "bat" // ERROR "moved to heap: s2$"
+ s3 := "cat" // ERROR "moved to heap: s3$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
- ps4 := &s4 // ERROR "moved to heap: ps4$"
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps4 := &s4 // ERROR "moved to heap: ps4$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal escapes to heap$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} escapes to heap$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPc() // Ssink = &s3 (only &s3 really escapes)
}
// BAD: need fine-grained (field-sensitive) analysis to avoid spurious escape of all but &s3
func tUPiSPd() {
s1 := "ant"
- s2 := "bat" // ERROR "moved to heap: s2$"
- s3 := "cat" // ERROR "moved to heap: s3$"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s2 := "bat" // ERROR "moved to heap: s2$"
+ s3 := "cat" // ERROR "moved to heap: s3$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
- ps4 := &s4 // ERROR "moved to heap: ps4$"
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps4 := &s4 // ERROR "moved to heap: ps4$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal escapes to heap$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} escapes to heap$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPd() // Ssink = &s3 (only &s3 really escapes)
}
@@ -204,16 +204,16 @@ func tUPiSPPia() {
s1 := "ant"
s2 := "bat"
s3 := "cat"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
ps4 := &s4
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal does not escape$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} does not escape$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPPia() // Ssink = *&ps4 = &s4 (only &s4 really escapes)
}
@@ -222,16 +222,16 @@ func tUPiSPPib() {
s1 := "ant"
s2 := "bat"
s3 := "cat"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
ps4 := &s4
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal does not escape$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} does not escape$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPPib() // Ssink = *&ps4 = &s4 (only &s4 really escapes)
}
@@ -240,16 +240,16 @@ func tUPiSPPic() {
s1 := "ant"
s2 := "bat"
s3 := "cat"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
ps4 := &s4
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal does not escape$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} does not escape$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPPic() // Ssink = *&ps4 = &s4 (only &s4 really escapes)
}
@@ -258,16 +258,16 @@ func tUPiSPPid() {
s1 := "ant"
s2 := "bat"
s3 := "cat"
- s4 := "dog" // ERROR "moved to heap: s4$"
- s5 := "emu" // ERROR "moved to heap: s5$"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s4 := "dog" // ERROR "moved to heap: s4$"
+ s5 := "emu" // ERROR "moved to heap: s5$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
ps4 := &s4
- ps6 := &s6 // ERROR "moved to heap: ps6$"
+ ps6 := &s6 // ERROR "moved to heap: ps6$"
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal does not escape$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} does not escape$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPiSPPid() // Ssink = *&ps4 = &s4 (only &s4 really escapes)
}
@@ -286,13 +286,13 @@ func tUPPiSPPia() { // This test is sensitive to the level cap in function summa
s3 := "cat"
s4 := "dog"
s5 := "emu"
- s6 := "fox" // ERROR "moved to heap: s6$"
+ s6 := "fox" // ERROR "moved to heap: s6$"
ps2 := &s2
ps4 := &s4
ps6 := &s6
u1 := U{&s1, &ps2}
- u2 := &U{&s3, &ps4} // ERROR "&U literal does not escape$"
- u3 := &U{&s5, &ps6} // ERROR "&U literal does not escape$"
- v := &V{u1, u2, &u3} // ERROR "&V literal does not escape$"
+ u2 := &U{&s3, &ps4} // ERROR "&U{...} does not escape$"
+ u3 := &U{&s5, &ps6} // ERROR "&U{...} does not escape$"
+ v := &V{u1, u2, &u3} // ERROR "&V{...} does not escape$"
Ssink = v.UPPiSPPia() // Ssink = *&ps6 = &s6 (only &s6 really escapes)
}
diff --git a/test/fixedbugs/issue12006.go b/test/fixedbugs/issue12006.go
index c44f2e5547..0a2ef8dad0 100644
--- a/test/fixedbugs/issue12006.go
+++ b/test/fixedbugs/issue12006.go
@@ -144,7 +144,7 @@ func TFooK2() {
a := int32(1) // ERROR "moved to heap: a"
b := "cat"
c := &a
- fs := fakeSlice{3, &[4]interface{}{a, b, c, nil}} // ERROR "a escapes to heap" "b escapes to heap" "&\[4\]interface {} literal does not escape"
+ fs := fakeSlice{3, &[4]interface{}{a, b, c, nil}} // ERROR "a escapes to heap" "b escapes to heap" "&\[4\]interface {}{...} does not escape"
isink = FooK(fs)
}
@@ -169,6 +169,6 @@ func TFooL2() {
a := int32(1) // ERROR "moved to heap: a"
b := "cat"
c := &a
- s := []interface{}{a, b, c} // ERROR "a escapes to heap" "b escapes to heap" "\[\]interface {} literal does not escape"
+ s := []interface{}{a, b, c} // ERROR "a escapes to heap" "b escapes to heap" "\[\]interface {}{...} does not escape"
isink = FooL(s)
}
diff --git a/test/fixedbugs/issue13799.go b/test/fixedbugs/issue13799.go
index 5c57494777..fbdd4c32bc 100644
--- a/test/fixedbugs/issue13799.go
+++ b/test/fixedbugs/issue13799.go
@@ -162,7 +162,7 @@ func test5(iter int) {
var fn *str
for i := 0; i < maxI; i++ {
// var fn *str // this makes it work, because fn stays off heap
- fn = &str{m} // ERROR "&str literal escapes to heap"
+ fn = &str{m} // ERROR "&str{...} escapes to heap"
recur1(0, fn)
}
@@ -180,7 +180,7 @@ func test6(iter int) {
// var fn *str
for i := 0; i < maxI; i++ {
var fn *str // this makes it work, because fn stays off heap
- fn = &str{m} // ERROR "&str literal does not escape"
+ fn = &str{m} // ERROR "&str{...} does not escape"
recur1(0, fn)
}
diff --git a/test/fixedbugs/issue17645.go b/test/fixedbugs/issue17645.go
index af785eae2a..95fcecd1e0 100644
--- a/test/fixedbugs/issue17645.go
+++ b/test/fixedbugs/issue17645.go
@@ -12,5 +12,5 @@ type Foo struct {
func main() {
var s []int
- var _ string = append(s, Foo{""}) // ERROR "cannot use .. \(type untyped string\) as type int in field value" "cannot use Foo literal \(type Foo\) as type int in append" "cannot use append\(s\, Foo literal\) \(type \[\]int\) as type string in assignment"
+ var _ string = append(s, Foo{""}) // ERROR "cannot use .. \(type untyped string\) as type int in field value" "cannot use Foo{...} \(type Foo\) as type int in append" "cannot use append\(s\, Foo{...}\) \(type \[\]int\) as type string in assignment"
}
diff --git a/test/fixedbugs/issue21709.go b/test/fixedbugs/issue21709.go
index cc5896ab53..20be10e792 100644
--- a/test/fixedbugs/issue21709.go
+++ b/test/fixedbugs/issue21709.go
@@ -16,7 +16,7 @@ var N int
func F1() {
var s S
for i := 0; i < N; i++ {
- fs := []func(){ // ERROR "\[\]func\(\) literal does not escape"
+ fs := []func(){ // ERROR "\[\]func\(\){...} does not escape"
s.Inc, // ERROR "s.Inc does not escape"
}
for _, f := range fs {
@@ -28,7 +28,7 @@ func F1() {
func F2() {
var s S
for i := 0; i < N; i++ {
- for _, f := range []func(){ // ERROR "\[\]func\(\) literal does not escape"
+ for _, f := range []func(){ // ERROR "\[\]func\(\){...} does not escape"
s.Inc, // ERROR "s.Inc does not escape"
} {
f()
diff --git a/test/fixedbugs/issue23732.go b/test/fixedbugs/issue23732.go
index be17bf4f61..5e63eb2074 100644
--- a/test/fixedbugs/issue23732.go
+++ b/test/fixedbugs/issue23732.go
@@ -24,19 +24,19 @@ func main() {
_ = Foo{
1,
2,
- 3, // ERROR "too few values in Foo literal"
+ 3, // ERROR "too few values in Foo{...}"
}
_ = Foo{
1,
2,
3,
- Bar{"A", "B"}, // ERROR "too many values in Bar literal"
+ Bar{"A", "B"}, // ERROR "too many values in Bar{...}"
}
_ = Foo{
1,
2,
- Bar{"A", "B"}, // ERROR "too many values in Bar literal" "too few values in Foo literal"
+ Bar{"A", "B"}, // ERROR "too many values in Bar{...}" "too few values in Foo{...}"
}
}
diff --git a/test/fixedbugs/issue26855.go b/test/fixedbugs/issue26855.go
index d5b95ddbf1..144e4415f7 100644
--- a/test/fixedbugs/issue26855.go
+++ b/test/fixedbugs/issue26855.go
@@ -20,9 +20,9 @@ type P struct {
type T struct{}
var _ = S{
- f: &T{}, // ERROR "cannot use &T literal"
+ f: &T{}, // ERROR "cannot use &T{}"
}
var _ = P{
- f: T{}, // ERROR "cannot use T literal"
+ f: T{}, // ERROR "cannot use T{}"
}
diff --git a/test/fixedbugs/issue30898.go b/test/fixedbugs/issue30898.go
index 012d5a2634..b6376d3f9e 100644
--- a/test/fixedbugs/issue30898.go
+++ b/test/fixedbugs/issue30898.go
@@ -15,5 +15,5 @@ func debugf(format string, args ...interface{}) { // ERROR "can inline debugf" "
func bar() { // ERROR "can inline bar"
value := 10
- debugf("value is %d", value) // ERROR "inlining call to debugf" "value does not escape" "\[\]interface {} literal does not escape"
+ debugf("value is %d", value) // ERROR "inlining call to debugf" "value does not escape" "\[\]interface {}{...} does not escape"
}
diff --git a/test/fixedbugs/issue31573.go b/test/fixedbugs/issue31573.go
index c9ea84bbae..005910e00d 100644
--- a/test/fixedbugs/issue31573.go
+++ b/test/fixedbugs/issue31573.go
@@ -14,18 +14,18 @@ func g() {
defer f(new(int), new(int)) // ERROR "... argument does not escape$" "new\(int\) does not escape$"
defer f(nil...)
- defer f([]*int{}...) // ERROR "\[\]\*int literal does not escape$"
- defer f([]*int{new(int)}...) // ERROR "\[\]\*int literal does not escape$" "new\(int\) does not escape$"
- defer f([]*int{new(int), new(int)}...) // ERROR "\[\]\*int literal does not escape$" "new\(int\) does not escape$"
+ defer f([]*int{}...) // ERROR "\[\]\*int{} does not escape$"
+ defer f([]*int{new(int)}...) // ERROR "\[\]\*int{...} does not escape$" "new\(int\) does not escape$"
+ defer f([]*int{new(int), new(int)}...) // ERROR "\[\]\*int{...} does not escape$" "new\(int\) does not escape$"
go f()
go f(new(int)) // ERROR "... argument escapes to heap$" "new\(int\) escapes to heap$"
go f(new(int), new(int)) // ERROR "... argument escapes to heap$" "new\(int\) escapes to heap$"
go f(nil...)
- go f([]*int{}...) // ERROR "\[\]\*int literal escapes to heap$"
- go f([]*int{new(int)}...) // ERROR "\[\]\*int literal escapes to heap$" "new\(int\) escapes to heap$"
- go f([]*int{new(int), new(int)}...) // ERROR "\[\]\*int literal escapes to heap$" "new\(int\) escapes to heap$"
+ go f([]*int{}...) // ERROR "\[\]\*int{} escapes to heap$"
+ go f([]*int{new(int)}...) // ERROR "\[\]\*int{...} escapes to heap$" "new\(int\) escapes to heap$"
+ go f([]*int{new(int), new(int)}...) // ERROR "\[\]\*int{...} escapes to heap$" "new\(int\) escapes to heap$"
for {
defer f()
@@ -33,17 +33,17 @@ func g() {
defer f(new(int), new(int)) // ERROR "... argument escapes to heap$" "new\(int\) escapes to heap$"
defer f(nil...)
- defer f([]*int{}...) // ERROR "\[\]\*int literal escapes to heap$"
- defer f([]*int{new(int)}...) // ERROR "\[\]\*int literal escapes to heap$" "new\(int\) escapes to heap$"
- defer f([]*int{new(int), new(int)}...) // ERROR "\[\]\*int literal escapes to heap$" "new\(int\) escapes to heap$"
+ defer f([]*int{}...) // ERROR "\[\]\*int{} escapes to heap$"
+ defer f([]*int{new(int)}...) // ERROR "\[\]\*int{...} escapes to heap$" "new\(int\) escapes to heap$"
+ defer f([]*int{new(int), new(int)}...) // ERROR "\[\]\*int{...} escapes to heap$" "new\(int\) escapes to heap$"
go f()
go f(new(int)) // ERROR "... argument escapes to heap$" "new\(int\) escapes to heap$"
go f(new(int), new(int)) // ERROR "... argument escapes to heap$" "new\(int\) escapes to heap$"
go f(nil...)
- go f([]*int{}...) // ERROR "\[\]\*int literal escapes to heap$"
- go f([]*int{new(int)}...) // ERROR "\[\]\*int literal escapes to heap$" "new\(int\) escapes to heap$"
- go f([]*int{new(int), new(int)}...) // ERROR "\[\]\*int literal escapes to heap$" "new\(int\) escapes to heap$"
+ go f([]*int{}...) // ERROR "\[\]\*int{} escapes to heap$"
+ go f([]*int{new(int)}...) // ERROR "\[\]\*int{...} escapes to heap$" "new\(int\) escapes to heap$"
+ go f([]*int{new(int), new(int)}...) // ERROR "\[\]\*int{...} escapes to heap$" "new\(int\) escapes to heap$"
}
}
diff --git a/test/fixedbugs/issue38745.go b/test/fixedbugs/issue38745.go
new file mode 100644
index 0000000000..21bd1ff3a7
--- /dev/null
+++ b/test/fixedbugs/issue38745.go
@@ -0,0 +1,19 @@
+// errorcheck
+
+// Copyright 2020 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.
+
+package p
+
+type t struct{ x int }
+
+func f1() {
+ t{}.M() // ERROR "t{}.M undefined \(type t has no field or method M\)"
+ t{x: 1}.M() // ERROR "t{...}.M undefined \(type t has no field or method M\)"
+}
+
+func f2() (*t, error) {
+ // BAD: should report undefined error only.
+ return t{}.M() // ERROR "t{}.M undefined \(type t has no field or method M\)" "not enough arguments to return"
+}
diff --git a/test/fixedbugs/issue39292.go b/test/fixedbugs/issue39292.go
index 1be88653e9..7dac2e5fc6 100644
--- a/test/fixedbugs/issue39292.go
+++ b/test/fixedbugs/issue39292.go
@@ -12,18 +12,18 @@ func (t) f() {
}
func x() {
- x := t{}.f // ERROR "t literal.f escapes to heap"
+ x := t{}.f // ERROR "t{}.f escapes to heap"
x()
}
func y() {
var i int // ERROR "moved to heap: i"
- y := (&t{&i}).f // ERROR "\(&t literal\).f escapes to heap" "&t literal escapes to heap"
+ y := (&t{&i}).f // ERROR "\(&t{...}\).f escapes to heap" "&t{...} escapes to heap"
y()
}
func z() {
var i int // ERROR "moved to heap: i"
- z := t{&i}.f // ERROR "t literal.f escapes to heap"
+ z := t{&i}.f // ERROR "t{...}.f escapes to heap"
z()
}
diff --git a/test/fixedbugs/issue41247.go b/test/fixedbugs/issue41247.go
index 2df919c9e6..b8bd81274f 100644
--- a/test/fixedbugs/issue41247.go
+++ b/test/fixedbugs/issue41247.go
@@ -7,5 +7,5 @@
package p
func f() [2]int {
- return [...]int{2: 0} // ERROR "cannot use \[\.\.\.\]int literal \(type \[3\]int\)"
+ return [...]int{2: 0} // ERROR "cannot use \[\.\.\.\]int{...} \(type \[3\]int\)"
}
diff --git a/test/fixedbugs/issue7921.go b/test/fixedbugs/issue7921.go
index a8efc8dd9e..5dce557ca3 100644
--- a/test/fixedbugs/issue7921.go
+++ b/test/fixedbugs/issue7921.go
@@ -18,12 +18,12 @@ func bufferNotEscape() string {
// can be stack-allocated.
var b bytes.Buffer
b.WriteString("123")
- b.Write([]byte{'4'}) // ERROR "\[\]byte literal does not escape$"
+ b.Write([]byte{'4'}) // ERROR "\[\]byte{...} does not escape$"
return b.String() // ERROR "inlining call to bytes.\(\*Buffer\).String$" "string\(bytes.b.buf\[bytes.b.off:\]\) escapes to heap$"
}
func bufferNoEscape2(xs []string) int { // ERROR "xs does not escape$"
- b := bytes.NewBuffer(make([]byte, 0, 64)) // ERROR "&bytes.Buffer literal does not escape$" "make\(\[\]byte, 0, 64\) does not escape$" "inlining call to bytes.NewBuffer$"
+ b := bytes.NewBuffer(make([]byte, 0, 64)) // ERROR "&bytes.Buffer{...} does not escape$" "make\(\[\]byte, 0, 64\) does not escape$" "inlining call to bytes.NewBuffer$"
for _, x := range xs {
b.WriteString(x)
}
@@ -31,7 +31,7 @@ func bufferNoEscape2(xs []string) int { // ERROR "xs does not escape$"
}
func bufferNoEscape3(xs []string) string { // ERROR "xs does not escape$"
- b := bytes.NewBuffer(make([]byte, 0, 64)) // ERROR "&bytes.Buffer literal does not escape$" "make\(\[\]byte, 0, 64\) does not escape$" "inlining call to bytes.NewBuffer$"
+ b := bytes.NewBuffer(make([]byte, 0, 64)) // ERROR "&bytes.Buffer{...} does not escape$" "make\(\[\]byte, 0, 64\) does not escape$" "inlining call to bytes.NewBuffer$"
for _, x := range xs {
b.WriteString(x)
b.WriteByte(',')
@@ -41,13 +41,13 @@ func bufferNoEscape3(xs []string) string { // ERROR "xs does not escape$"
func bufferNoEscape4() []byte {
var b bytes.Buffer
- b.Grow(64) // ERROR "bufferNoEscape4 ignoring self-assignment in bytes.b.buf = bytes.b.buf\[:bytes.m·3\]$" "inlining call to bytes.\(\*Buffer\).Grow$"
+ b.Grow(64) // ERROR "bufferNoEscape4 ignoring self-assignment in bytes.b.buf = bytes.b.buf\[:bytes.m·3\]$" "inlining call to bytes.\(\*Buffer\).Grow$"
useBuffer(&b)
return b.Bytes() // ERROR "inlining call to bytes.\(\*Buffer\).Bytes$"
}
func bufferNoEscape5() { // ERROR "can inline bufferNoEscape5$"
- b := bytes.NewBuffer(make([]byte, 0, 128)) // ERROR "&bytes.Buffer literal does not escape$" "make\(\[\]byte, 0, 128\) does not escape$" "inlining call to bytes.NewBuffer$"
+ b := bytes.NewBuffer(make([]byte, 0, 128)) // ERROR "&bytes.Buffer{...} does not escape$" "make\(\[\]byte, 0, 128\) does not escape$" "inlining call to bytes.NewBuffer$"
useBuffer(b)
}
diff --git a/test/inline_variadic.go b/test/inline_variadic.go
index fcc1cff1e8..687048a192 100644
--- a/test/inline_variadic.go
+++ b/test/inline_variadic.go
@@ -14,6 +14,6 @@ func head(xs ...string) string { // ERROR "can inline head" "leaking param: xs t
}
func f() string { // ERROR "can inline f"
- x := head("hello", "world") // ERROR "inlining call to head" "\[\]string literal does not escape"
+ x := head("hello", "world") // ERROR "inlining call to head" "\[\]string{...} does not escape"
return x
}
--
cgit v1.3
From 806f478499b57c5167fb5301101961b7563903d2 Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Wed, 9 Sep 2020 16:25:48 +0700
Subject: cmd/compile: don't report not enough args error if call is undefined
Fixes #38745
Change-Id: I2fbd8b512a8cf911b81a087162c74416116efea5
Reviewed-on: https://go-review.googlesource.com/c/go/+/253678
Run-TryBot: Cuong Manh Le
TryBot-Result: Gobot Gobot
Reviewed-by: Matthew Dempsky
---
src/cmd/compile/internal/gc/typecheck.go | 2 +-
test/ddd1.go | 2 +-
test/fixedbugs/issue38745.go | 3 +--
3 files changed, 3 insertions(+), 4 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go
index dec4b96fc4..fb169cfec8 100644
--- a/src/cmd/compile/internal/gc/typecheck.go
+++ b/src/cmd/compile/internal/gc/typecheck.go
@@ -2667,7 +2667,7 @@ func typecheckaste(op Op, call *Node, isddd bool, tstruct *types.Type, nl Nodes,
return
notenough:
- if n == nil || !n.Diag() {
+ if n == nil || (!n.Diag() && n.Type != nil) {
details := errorDetails(nl, tstruct, isddd)
if call != nil {
// call is the expression being called, not the overall call.
diff --git a/test/ddd1.go b/test/ddd1.go
index 2c7e83e374..9857814648 100644
--- a/test/ddd1.go
+++ b/test/ddd1.go
@@ -29,7 +29,7 @@ var (
_ = sum(tuple())
_ = sum(tuple()...) // ERROR "multiple-value"
_ = sum3(tuple())
- _ = sum3(tuple()...) // ERROR "multiple-value" "not enough"
+ _ = sum3(tuple()...) // ERROR "multiple-value"
)
type T []T
diff --git a/test/fixedbugs/issue38745.go b/test/fixedbugs/issue38745.go
index 21bd1ff3a7..83a3bc6fad 100644
--- a/test/fixedbugs/issue38745.go
+++ b/test/fixedbugs/issue38745.go
@@ -14,6 +14,5 @@ func f1() {
}
func f2() (*t, error) {
- // BAD: should report undefined error only.
- return t{}.M() // ERROR "t{}.M undefined \(type t has no field or method M\)" "not enough arguments to return"
+ return t{}.M() // ERROR "t{}.M undefined \(type t has no field or method M\)"
}
--
cgit v1.3
From 92b2b8860dcc28461198c6125fbae2383161d2e5 Mon Sep 17 00:00:00 2001
From: Daniel Martí
Date: Sat, 15 Aug 2020 16:20:50 +0200
Subject: cmd/go: avoid flag.FlagSet.VisitAll at init time
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
We want to error early if GOFLAGS contains any flag that isn't known to
any cmd/go command. Thus, at init time we would recursively use VisitAll
on each of the flagsets to populate a map of all registered flags.
This was unfortunate, as populating said map constituted a whole 5% of
the run-time of 'go env GOARCH'. This is because VisitAll is pretty
expensive; it copies all the maps from the flagset's map to a slice,
sorts the slice, then does one callback per flag.
First, this was a bit wasteful. We only ever needed to query the
knownFlag map if GOFLAGS wasn't empty. If it's empty, there's no work to
do, thus we can skip the map populating work.
Second and most important, we don't actually need the map at all. A
flag.FlagSet already has a Lookup method, so we can simply recursively
call those methods for each flag in GOFLAGS. Add a hasFlag func to make
that evident.
This mechanism is different; its upfront cost is none, but it will
likely mean a handful of map lookups for each flag in GOFLAGS. However,
that tradeoff is worth it; we don't expect GOFLAGS to contain thousands
of flags. The most likely scenario is less than a dozen flags, in which
case constructing a "unified" map is not at all a net win.
One possible reason the previous mechanism was that way could be
AddKnownFlag. Thankfully, the one and only use of that API was removed
last year when Bryan cleaned up flag parsing in cmd/go.
The wins for the existing benchmark with an empty GOFLAGS are
significant:
name old time/op new time/op delta
ExecGoEnv-8 575µs ± 1% 549µs ± 2% -4.44% (p=0.000 n=7+8)
name old sys-time/op new sys-time/op delta
ExecGoEnv-8 1.69ms ± 1% 1.68ms ± 2% ~ (p=0.281 n=7+8)
name old user-time/op new user-time/op delta
ExecGoEnv-8 1.80ms ± 1% 1.66ms ± 2% -8.09% (p=0.000 n=7+8)
To prove that a relatively large number of GOFLAGS isn't getting
noticeably slower, we measured that as well, via benchcmd and GOFLAGS
containing 50 valid flags:
GOFLAGS=$(yes -- -race | sed 50q) benchcmd -n 500 GoEnvGOFLAGS go env GOARCH
And the result, while noisy, shows no noticeable difference (note that
it measures 3ms instead of 0.6ms since it's sequential):
name old time/op new time/op delta
GoEnvGOFLAGS 3.04ms ±32% 3.03ms ±35% ~ (p=0.156 n=487+481)
Finally, we've improved the existing Go benchmark. Now it's parallel,
and it also reports sys-time and user-time, which are useful metrics.
Change-Id: I9b4551415cedf2f819eb184a02324b8bd919e2bd
Reviewed-on: https://go-review.googlesource.com/c/go/+/248757
Reviewed-by: Bryan C. Mills
Run-TryBot: Bryan C. Mills
TryBot-Result: Gobot Gobot
---
src/cmd/go/init_test.go | 26 +++++++++++++++++---------
src/cmd/go/internal/base/base.go | 14 ++++++++++++++
src/cmd/go/internal/base/goflags.go | 37 +++++++------------------------------
3 files changed, 38 insertions(+), 39 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/go/init_test.go b/src/cmd/go/init_test.go
index ed90a77841..5a5cbe5293 100644
--- a/src/cmd/go/init_test.go
+++ b/src/cmd/go/init_test.go
@@ -7,6 +7,7 @@ package main_test
import (
"internal/testenv"
"os/exec"
+ "sync/atomic"
"testing"
)
@@ -15,20 +16,27 @@ import (
// the benchmark if any changes were done.
func BenchmarkExecGoEnv(b *testing.B) {
testenv.MustHaveExec(b)
- b.StopTimer()
gotool, err := testenv.GoTool()
if err != nil {
b.Fatal(err)
}
- for i := 0; i < b.N; i++ {
- cmd := exec.Command(gotool, "env", "GOARCH")
- b.StartTimer()
- err := cmd.Run()
- b.StopTimer()
+ // We collect extra metrics.
+ var n, userTime, systemTime int64
- if err != nil {
- b.Fatal(err)
+ b.ResetTimer()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ cmd := exec.Command(gotool, "env", "GOARCH")
+
+ if err := cmd.Run(); err != nil {
+ b.Fatal(err)
+ }
+ atomic.AddInt64(&n, 1)
+ atomic.AddInt64(&userTime, int64(cmd.ProcessState.UserTime()))
+ atomic.AddInt64(&systemTime, int64(cmd.ProcessState.SystemTime()))
}
- }
+ })
+ b.ReportMetric(float64(userTime)/float64(n), "user-ns/op")
+ b.ReportMetric(float64(systemTime)/float64(n), "sys-ns/op")
}
diff --git a/src/cmd/go/internal/base/base.go b/src/cmd/go/internal/base/base.go
index db3ebef933..004588c732 100644
--- a/src/cmd/go/internal/base/base.go
+++ b/src/cmd/go/internal/base/base.go
@@ -56,6 +56,20 @@ var Go = &Command{
// Commands initialized in package main
}
+// hasFlag reports whether a command or any of its subcommands contain the given
+// flag.
+func hasFlag(c *Command, name string) bool {
+ if f := c.Flag.Lookup(name); f != nil {
+ return true
+ }
+ for _, sub := range c.Commands {
+ if hasFlag(sub, name) {
+ return true
+ }
+ }
+ return false
+}
+
// LongName returns the command's long name: all the words in the usage line between "go" and a flag or argument,
func (c *Command) LongName() string {
name := c.UsageLine
diff --git a/src/cmd/go/internal/base/goflags.go b/src/cmd/go/internal/base/goflags.go
index 34766134b0..f11f9a5d33 100644
--- a/src/cmd/go/internal/base/goflags.go
+++ b/src/cmd/go/internal/base/goflags.go
@@ -13,15 +13,7 @@ import (
"cmd/go/internal/cfg"
)
-var (
- goflags []string // cached $GOFLAGS list; can be -x or --x form
- knownFlag = make(map[string]bool) // flags allowed to appear in $GOFLAGS; no leading dashes
-)
-
-// AddKnownFlag adds name to the list of known flags for use in $GOFLAGS.
-func AddKnownFlag(name string) {
- knownFlag[name] = true
-}
+var goflags []string // cached $GOFLAGS list; can be -x or --x form
// GOFLAGS returns the flags from $GOFLAGS.
// The list can be assumed to contain one string per flag,
@@ -38,22 +30,12 @@ func InitGOFLAGS() {
return
}
- // Build list of all flags for all commands.
- // If no command has that flag, then we report the problem.
- // This catches typos while still letting users record flags in GOFLAGS
- // that only apply to a subset of go commands.
- // Commands using CustomFlags can report their flag names
- // by calling AddKnownFlag instead.
- var walkFlags func(*Command)
- walkFlags = func(cmd *Command) {
- for _, sub := range cmd.Commands {
- walkFlags(sub)
- }
- cmd.Flag.VisitAll(func(f *flag.Flag) {
- knownFlag[f.Name] = true
- })
+ goflags = strings.Fields(cfg.Getenv("GOFLAGS"))
+ if len(goflags) == 0 {
+ // nothing to do; avoid work on later InitGOFLAGS call
+ goflags = []string{}
+ return
}
- walkFlags(Go)
// Ignore bad flag in go env and go bug, because
// they are what people reach for when debugging
@@ -61,11 +43,6 @@ func InitGOFLAGS() {
// (Both will show the GOFLAGS setting if let succeed.)
hideErrors := cfg.CmdName == "env" || cfg.CmdName == "bug"
- goflags = strings.Fields(cfg.Getenv("GOFLAGS"))
- if goflags == nil {
- goflags = []string{} // avoid work on later InitGOFLAGS call
- }
-
// Each of the words returned by strings.Fields must be its own flag.
// To set flag arguments use -x=value instead of -x value.
// For boolean flags, -x is fine instead of -x=true.
@@ -85,7 +62,7 @@ func InitGOFLAGS() {
if i := strings.Index(name, "="); i >= 0 {
name = name[:i]
}
- if !knownFlag[name] {
+ if !hasFlag(Go, name) {
if hideErrors {
continue
}
--
cgit v1.3
From 1f4521669416a2e14fb0b84481447f4a93f19878 Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Sat, 12 Sep 2020 01:57:27 +0700
Subject: cmd/compile: attach OVARLIVE nodes to OCALLxxx
So we can insert theses OVARLIVE nodes right after OpStaticCall in SSA.
This helps fixing issue that unsafe-uintptr arguments are not kept alive
during return statement, or can be kept alive longer than expected.
Fixes #24491
Change-Id: Ic04a5d1bbb5c90dcfae65bd95cdd1da393a66800
Reviewed-on: https://go-review.googlesource.com/c/go/+/254397
Run-TryBot: Cuong Manh Le
TryBot-Result: Gobot Gobot
Reviewed-by: Matthew Dempsky
---
src/cmd/compile/internal/gc/order.go | 14 +++------
src/cmd/compile/internal/gc/ssa.go | 2 ++
src/cmd/compile/internal/gc/syntax.go | 4 +--
test/fixedbugs/issue24491.go | 45 -----------------------------
test/fixedbugs/issue24491a.go | 54 +++++++++++++++++++++++++++++++++++
test/fixedbugs/issue24491b.go | 46 +++++++++++++++++++++++++++++
6 files changed, 107 insertions(+), 58 deletions(-)
delete mode 100644 test/fixedbugs/issue24491.go
create mode 100644 test/fixedbugs/issue24491a.go
create mode 100644 test/fixedbugs/issue24491b.go
(limited to 'src/cmd')
diff --git a/src/cmd/compile/internal/gc/order.go b/src/cmd/compile/internal/gc/order.go
index 412f073a8d..341f4ee66f 100644
--- a/src/cmd/compile/internal/gc/order.go
+++ b/src/cmd/compile/internal/gc/order.go
@@ -288,20 +288,13 @@ func (o *Order) popTemp(mark ordermarker) {
o.temp = o.temp[:mark]
}
-// cleanTempNoPop emits VARKILL and if needed VARLIVE instructions
-// to *out for each temporary above the mark on the temporary stack.
+// cleanTempNoPop emits VARKILL instructions to *out
+// for each temporary above the mark on the temporary stack.
// It does not pop the temporaries from the stack.
func (o *Order) cleanTempNoPop(mark ordermarker) []*Node {
var out []*Node
for i := len(o.temp) - 1; i >= int(mark); i-- {
n := o.temp[i]
- if n.Name.Keepalive() {
- n.Name.SetKeepalive(false)
- n.Name.SetAddrtaken(true) // ensure SSA keeps the n variable
- live := nod(OVARLIVE, n, nil)
- live = typecheck(live, ctxStmt)
- out = append(out, live)
- }
kill := nod(OVARKILL, n, nil)
kill = typecheck(kill, ctxStmt)
out = append(out, kill)
@@ -500,8 +493,9 @@ func (o *Order) call(n *Node) {
// still alive when we pop the temp stack.
if arg.Op == OCONVNOP && arg.Left.Type.IsUnsafePtr() {
x := o.copyExpr(arg.Left, arg.Left.Type, false)
- x.Name.SetKeepalive(true)
arg.Left = x
+ x.Name.SetAddrtaken(true) // ensure SSA keeps the x variable
+ n.Nbody.Append(typecheck(nod(OVARLIVE, x, nil), ctxStmt))
n.SetNeedsWrapper(true)
}
}
diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go
index 89644cd3f2..3bdb5b0b9f 100644
--- a/src/cmd/compile/internal/gc/ssa.go
+++ b/src/cmd/compile/internal/gc/ssa.go
@@ -4498,6 +4498,8 @@ func (s *state) call(n *Node, k callKind) *ssa.Value {
call.AuxInt = stksize // Call operations carry the argsize of the callee along with them
}
s.vars[&memVar] = call
+ // Insert OVARLIVE nodes
+ s.stmtList(n.Nbody)
// Finish block for defers
if k == callDefer || k == callDeferStack {
diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go
index 5580f789c5..9592b7484c 100644
--- a/src/cmd/compile/internal/gc/syntax.go
+++ b/src/cmd/compile/internal/gc/syntax.go
@@ -374,7 +374,6 @@ const (
nameReadonly
nameByval // is the variable captured by value or by reference
nameNeedzero // if it contains pointers, needs to be zeroed on function entry
- nameKeepalive // mark value live across unknown assembly call
nameAutoTemp // is the variable a temporary (implies no dwarf info. reset if escapes to heap)
nameUsed // for variable declared and not used error
nameIsClosureVar // PAUTOHEAP closure pseudo-variable; original at n.Name.Defn
@@ -391,7 +390,6 @@ func (n *Name) Captured() bool { return n.flags&nameCaptured != 0 }
func (n *Name) Readonly() bool { return n.flags&nameReadonly != 0 }
func (n *Name) Byval() bool { return n.flags&nameByval != 0 }
func (n *Name) Needzero() bool { return n.flags&nameNeedzero != 0 }
-func (n *Name) Keepalive() bool { return n.flags&nameKeepalive != 0 }
func (n *Name) AutoTemp() bool { return n.flags&nameAutoTemp != 0 }
func (n *Name) Used() bool { return n.flags&nameUsed != 0 }
func (n *Name) IsClosureVar() bool { return n.flags&nameIsClosureVar != 0 }
@@ -407,7 +405,6 @@ func (n *Name) SetCaptured(b bool) { n.flags.set(nameCaptured, b) }
func (n *Name) SetReadonly(b bool) { n.flags.set(nameReadonly, b) }
func (n *Name) SetByval(b bool) { n.flags.set(nameByval, b) }
func (n *Name) SetNeedzero(b bool) { n.flags.set(nameNeedzero, b) }
-func (n *Name) SetKeepalive(b bool) { n.flags.set(nameKeepalive, b) }
func (n *Name) SetAutoTemp(b bool) { n.flags.set(nameAutoTemp, b) }
func (n *Name) SetUsed(b bool) { n.flags.set(nameUsed, b) }
func (n *Name) SetIsClosureVar(b bool) { n.flags.set(nameIsClosureVar, b) }
@@ -707,6 +704,7 @@ const (
// Prior to walk, they are: Left(List), where List is all regular arguments.
// After walk, List is a series of assignments to temporaries,
// and Rlist is an updated set of arguments.
+ // Nbody is all OVARLIVE nodes that are attached to OCALLxxx.
// TODO(josharian/khr): Use Ninit instead of List for the assignments to temporaries. See CL 114797.
OCALLFUNC // Left(List/Rlist) (function call f(args))
OCALLMETH // Left(List/Rlist) (direct method call x.Method(args))
diff --git a/test/fixedbugs/issue24491.go b/test/fixedbugs/issue24491.go
deleted file mode 100644
index 4703368793..0000000000
--- a/test/fixedbugs/issue24491.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// run
-
-// Copyright 2020 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.
-
-// This test makes sure unsafe-uintptr arguments are handled correctly.
-
-package main
-
-import (
- "runtime"
- "unsafe"
-)
-
-var done = make(chan bool, 1)
-
-func setup() unsafe.Pointer {
- s := "ok"
- runtime.SetFinalizer(&s, func(p *string) { *p = "FAIL" })
- return unsafe.Pointer(&s)
-}
-
-//go:noinline
-//go:uintptrescapes
-func test(s string, p uintptr) {
- runtime.GC()
- if *(*string)(unsafe.Pointer(p)) != "ok" {
- panic(s + " return unexpected result")
- }
- done <- true
-}
-
-func main() {
- test("normal", uintptr(setup()))
- <-done
-
- go test("go", uintptr(setup()))
- <-done
-
- func() {
- defer test("defer", uintptr(setup()))
- }()
- <-done
-}
diff --git a/test/fixedbugs/issue24491a.go b/test/fixedbugs/issue24491a.go
new file mode 100644
index 0000000000..148134d187
--- /dev/null
+++ b/test/fixedbugs/issue24491a.go
@@ -0,0 +1,54 @@
+// run
+
+// Copyright 2020 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.
+
+// This test makes sure unsafe-uintptr arguments are handled correctly.
+
+package main
+
+import (
+ "runtime"
+ "unsafe"
+)
+
+var done = make(chan bool, 1)
+
+func setup() unsafe.Pointer {
+ s := "ok"
+ runtime.SetFinalizer(&s, func(p *string) { *p = "FAIL" })
+ return unsafe.Pointer(&s)
+}
+
+//go:noinline
+//go:uintptrescapes
+func test(s string, p uintptr) int {
+ runtime.GC()
+ if *(*string)(unsafe.Pointer(p)) != "ok" {
+ panic(s + " return unexpected result")
+ }
+ done <- true
+ return 0
+}
+
+//go:noinline
+func f() int {
+ return test("return", uintptr(setup()))
+}
+
+func main() {
+ test("normal", uintptr(setup()))
+ <-done
+
+ go test("go", uintptr(setup()))
+ <-done
+
+ func() {
+ defer test("defer", uintptr(setup()))
+ }()
+ <-done
+
+ f()
+ <-done
+}
diff --git a/test/fixedbugs/issue24491b.go b/test/fixedbugs/issue24491b.go
new file mode 100644
index 0000000000..5f4a2f233e
--- /dev/null
+++ b/test/fixedbugs/issue24491b.go
@@ -0,0 +1,46 @@
+// run
+
+// Copyright 2020 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.
+
+// This test makes sure unsafe-uintptr arguments are not
+// kept alive longer than expected.
+
+package main
+
+import (
+ "runtime"
+ "sync/atomic"
+ "unsafe"
+)
+
+var done uint32
+
+func setup() unsafe.Pointer {
+ s := "ok"
+ runtime.SetFinalizer(&s, func(p *string) { atomic.StoreUint32(&done, 1) })
+ return unsafe.Pointer(&s)
+}
+
+//go:noinline
+//go:uintptrescapes
+func before(p uintptr) int {
+ runtime.GC()
+ if atomic.LoadUint32(&done) != 0 {
+ panic("GC early")
+ }
+ return 0
+}
+
+func after() int {
+ runtime.GC()
+ if atomic.LoadUint32(&done) == 0 {
+ panic("GC late")
+ }
+ return 0
+}
+
+func main() {
+ _ = before(uintptr(setup())) + after()
+}
--
cgit v1.3
From 5f1b12bfbeb04ca6dbecbf064f5e5a42d8ba4b5a Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Sat, 12 Sep 2020 07:19:22 +0700
Subject: cmd/compile: remove nodeNeedsWrapper flag
CL 254397 attached OVARLIVE nodes to OCALLxxx nodes Nbody.
The NeedsWrapper flag is now redundant with n.Nbody.Len() > 0
condition, so use that condition instead and remove the flag.
Passes toolstash-check.
Change-Id: Iebc3e674d3c0040a876ca4be05025943d2b4fb31
Reviewed-on: https://go-review.googlesource.com/c/go/+/254398
Run-TryBot: Cuong Manh Le
TryBot-Result: Gobot Gobot
Reviewed-by: Matthew Dempsky
---
src/cmd/compile/internal/gc/order.go | 1 -
src/cmd/compile/internal/gc/syntax.go | 41 +++++++++++------------------------
src/cmd/compile/internal/gc/walk.go | 7 ++++--
3 files changed, 18 insertions(+), 31 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/compile/internal/gc/order.go b/src/cmd/compile/internal/gc/order.go
index 341f4ee66f..75da154fe2 100644
--- a/src/cmd/compile/internal/gc/order.go
+++ b/src/cmd/compile/internal/gc/order.go
@@ -496,7 +496,6 @@ func (o *Order) call(n *Node) {
arg.Left = x
x.Name.SetAddrtaken(true) // ensure SSA keeps the x variable
n.Nbody.Append(typecheck(nod(OVARLIVE, x, nil), ctxStmt))
- n.SetNeedsWrapper(true)
}
}
diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go
index 9592b7484c..14d2710da4 100644
--- a/src/cmd/compile/internal/gc/syntax.go
+++ b/src/cmd/compile/internal/gc/syntax.go
@@ -141,20 +141,19 @@ const (
nodeInitorder, _ // tracks state during init1; two bits
_, _ // second nodeInitorder bit
_, nodeHasBreak
- _, nodeNoInline // used internally by inliner to indicate that a function call should not be inlined; set for OCALLFUNC and OCALLMETH only
- _, nodeImplicit // implicit OADDR or ODEREF; ++/-- statement represented as OASOP; or ANDNOT lowered to OAND
- _, nodeIsDDD // is the argument variadic
- _, nodeDiag // already printed error about this
- _, nodeColas // OAS resulting from :=
- _, nodeNonNil // guaranteed to be non-nil
- _, nodeTransient // storage can be reused immediately after this statement
- _, nodeBounded // bounds check unnecessary
- _, nodeHasCall // expression contains a function call
- _, nodeLikely // if statement condition likely
- _, nodeHasVal // node.E contains a Val
- _, nodeHasOpt // node.E contains an Opt
- _, nodeEmbedded // ODCLFIELD embedded type
- _, nodeNeedsWrapper // OCALLxxx node that needs to be wrapped
+ _, nodeNoInline // used internally by inliner to indicate that a function call should not be inlined; set for OCALLFUNC and OCALLMETH only
+ _, nodeImplicit // implicit OADDR or ODEREF; ++/-- statement represented as OASOP; or ANDNOT lowered to OAND
+ _, nodeIsDDD // is the argument variadic
+ _, nodeDiag // already printed error about this
+ _, nodeColas // OAS resulting from :=
+ _, nodeNonNil // guaranteed to be non-nil
+ _, nodeTransient // storage can be reused immediately after this statement
+ _, nodeBounded // bounds check unnecessary
+ _, nodeHasCall // expression contains a function call
+ _, nodeLikely // if statement condition likely
+ _, nodeHasVal // node.E contains a Val
+ _, nodeHasOpt // node.E contains an Opt
+ _, nodeEmbedded // ODCLFIELD embedded type
)
func (n *Node) Class() Class { return Class(n.flags.get3(nodeClass)) }
@@ -287,20 +286,6 @@ func (n *Node) SetIota(x int64) {
n.Xoffset = x
}
-func (n *Node) NeedsWrapper() bool {
- return n.flags&nodeNeedsWrapper != 0
-}
-
-// SetNeedsWrapper indicates that OCALLxxx node needs to be wrapped by a closure.
-func (n *Node) SetNeedsWrapper(b bool) {
- switch n.Op {
- case OCALLFUNC, OCALLMETH, OCALLINTER:
- default:
- Fatalf("Node.SetNeedsWrapper %v", n.Op)
- }
- n.flags.set(nodeNeedsWrapper, b)
-}
-
// mayBeShared reports whether n may occur in multiple places in the AST.
// Extra care must be taken when mutating such a node.
func (n *Node) mayBeShared() bool {
diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go
index 361de7e0f3..2d29366880 100644
--- a/src/cmd/compile/internal/gc/walk.go
+++ b/src/cmd/compile/internal/gc/walk.go
@@ -231,12 +231,15 @@ func walkstmt(n *Node) *Node {
case OCOPY:
n.Left = copyany(n.Left, &n.Ninit, true)
- default:
- if n.Left.NeedsWrapper() {
+ case OCALLFUNC, OCALLMETH, OCALLINTER:
+ if n.Left.Nbody.Len() > 0 {
n.Left = wrapCall(n.Left, &n.Ninit)
} else {
n.Left = walkexpr(n.Left, &n.Ninit)
}
+
+ default:
+ n.Left = walkexpr(n.Left, &n.Ninit)
}
case OFOR, OFORUNTIL:
--
cgit v1.3
From 57646534297a9bd193e6aaa4239c98984f371b97 Mon Sep 17 00:00:00 2001
From: "Bryan C. Mills"
Date: Mon, 14 Sep 2020 10:19:47 -0400
Subject: cmd/api: omit outside dependencies when listing the packages in "std"
As of CL 251159, when 'go list -deps std' is run within GOROOT/src, it
treats the vendored external dependencies as real module dependencies,
not standard-library "vendor/" packages (which still exist in that
case, but are treated as distinct packages outside the "std" module).
Fixes #41358
Updates #30241
Change-Id: Ic23eae9829d90e74a340d49ca9052e9191597410
Reviewed-on: https://go-review.googlesource.com/c/go/+/254738
Run-TryBot: Bryan C. Mills
Trust: Bryan C. Mills
TryBot-Result: Go Bot
Reviewed-by: Michael Matloob
Reviewed-by: Jay Conrod
---
src/cmd/api/goapi.go | 17 +++++++++++++----
src/cmd/api/goapi_test.go | 13 +++++++++++++
src/cmd/go/internal/modload/load.go | 8 ++++++--
3 files changed, 32 insertions(+), 6 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/api/goapi.go b/src/cmd/api/goapi.go
index 01b17b8839..6a80ed269b 100644
--- a/src/cmd/api/goapi.go
+++ b/src/cmd/api/goapi.go
@@ -87,7 +87,10 @@ var contexts = []*build.Context{
func contextName(c *build.Context) string {
s := c.GOOS + "-" + c.GOARCH
if c.CgoEnabled {
- return s + "-cgo"
+ s += "-cgo"
+ }
+ if c.Dir != "" {
+ s += fmt.Sprintf(" [%s]", c.Dir)
}
return s
}
@@ -478,6 +481,9 @@ func (w *Walker) loadImports() {
cmd := exec.Command(goCmd(), "list", "-e", "-deps", "-json", "std")
cmd.Env = listEnv(w.context)
+ if w.context.Dir != "" {
+ cmd.Dir = w.context.Dir
+ }
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("loading imports: %v\n%s", err, out)
@@ -491,6 +497,7 @@ func (w *Walker) loadImports() {
var pkg struct {
ImportPath, Dir string
ImportMap map[string]string
+ Standard bool
}
err := dec.Decode(&pkg)
if err == io.EOF {
@@ -503,11 +510,13 @@ func (w *Walker) loadImports() {
// - Package "unsafe" contains special signatures requiring
// extra care when printing them - ignore since it is not
// going to change w/o a language change.
- // - internal and vendored packages do not contribute to our
- // API surface.
+ // - Internal and vendored packages do not contribute to our
+ // API surface. (If we are running within the "std" module,
+ // vendored dependencies appear as themselves instead of
+ // their "vendor/" standard-library copies.)
// - 'go list std' does not include commands, which cannot be
// imported anyway.
- if ip := pkg.ImportPath; ip != "unsafe" && !strings.HasPrefix(ip, "vendor/") && !internalPkg.MatchString(ip) {
+ if ip := pkg.ImportPath; pkg.Standard && ip != "unsafe" && !strings.HasPrefix(ip, "vendor/") && !internalPkg.MatchString(ip) {
stdPackages = append(stdPackages, ip)
}
importDir[pkg.ImportPath] = pkg.Dir
diff --git a/src/cmd/api/goapi_test.go b/src/cmd/api/goapi_test.go
index eaccc5ceb5..24620a94af 100644
--- a/src/cmd/api/goapi_test.go
+++ b/src/cmd/api/goapi_test.go
@@ -216,3 +216,16 @@ func TestIssue29837(t *testing.T) {
}
}
}
+
+func TestIssue41358(t *testing.T) {
+ context := new(build.Context)
+ *context = build.Default
+ context.Dir = filepath.Join(context.GOROOT, "src")
+
+ w := NewWalker(context, context.Dir)
+ for _, pkg := range w.stdPackages {
+ if strings.HasPrefix(pkg, "vendor/") || strings.HasPrefix(pkg, "golang.org/x/") {
+ t.Fatalf("stdPackages contains unexpected package %s", pkg)
+ }
+ }
+}
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index 1664d8c5be..2fe68e6f88 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -1106,8 +1106,12 @@ func (ld *loader) stdVendor(parentPath, path string) string {
// Do the same for importers beginning with the prefix 'vendor/' even if we
// are *inside* of the 'std' module: the 'vendor/' packages that resolve
// globally from GOROOT/src/vendor (and are listed as part of 'go list std')
- // are distinct from the real module dependencies, and cannot import internal
- // packages from the real module.
+ // are distinct from the real module dependencies, and cannot import
+ // internal packages from the real module.
+ //
+ // (Note that although the 'vendor/' packages match the 'std' *package*
+ // pattern, they are not part of the std *module*, and do not affect
+ // 'go mod tidy' and similar module commands when working within std.)
vendorPath := pathpkg.Join("vendor", path)
if _, err := os.Stat(filepath.Join(cfg.GOROOTsrc, filepath.FromSlash(vendorPath))); err == nil {
return vendorPath
--
cgit v1.3
From a408139bb0166f6e0a5d9fd17fc934da960c354e Mon Sep 17 00:00:00 2001
From: Changkun Ou
Date: Mon, 14 Sep 2020 10:24:59 +0200
Subject: testing: fix panicking tests hang if Cleanup calls FailNow
Previously, it was impossible to call FailNow in a Cleanup.
Because it can terminate a panicking goroutine and cause its
parent hangs on t.signal channel. This CL sends the signal
in a deferred call to prevent the hang.
Fixes #41355
Change-Id: I4552d3a7ea763ef86817bf9b50c0e37fb34bf20f
Reviewed-on: https://go-review.googlesource.com/c/go/+/254637
Reviewed-by: Ian Lance Taylor
Reviewed-by: Emmanuel Odeke
Run-TryBot: Ian Lance Taylor
TryBot-Result: Go Bot
Trust: Emmanuel Odeke
---
.../go/testdata/script/test_cleanup_failnow.txt | 33 ++++++++++++++++++++++
src/testing/testing.go | 12 +++++++-
2 files changed, 44 insertions(+), 1 deletion(-)
create mode 100644 src/cmd/go/testdata/script/test_cleanup_failnow.txt
(limited to 'src/cmd')
diff --git a/src/cmd/go/testdata/script/test_cleanup_failnow.txt b/src/cmd/go/testdata/script/test_cleanup_failnow.txt
new file mode 100644
index 0000000000..5ad4185fc1
--- /dev/null
+++ b/src/cmd/go/testdata/script/test_cleanup_failnow.txt
@@ -0,0 +1,33 @@
+# For issue 41355
+[short] skip
+
+! go test -v cleanup_failnow/panic_nocleanup_test.go
+stdout '(?s)panic: die \[recovered\].*panic: die'
+! stdout '(?s)panic: die \[recovered\].*panic: die.*panic: die'
+
+! go test -v cleanup_failnow/panic_withcleanup_test.go
+stdout '(?s)panic: die \[recovered\].*panic: die'
+! stdout '(?s)panic: die \[recovered\].*panic: die.*panic: die'
+
+-- cleanup_failnow/panic_nocleanup_test.go --
+package panic_nocleanup_test
+import "testing"
+func TestX(t *testing.T) {
+ t.Run("x", func(t *testing.T) {
+ panic("die")
+ })
+}
+
+-- cleanup_failnow/panic_withcleanup_test.go --
+package panic_withcleanup_test
+import "testing"
+func TestCleanupWithFailNow(t *testing.T) {
+ t.Cleanup(func() {
+ t.FailNow()
+ })
+ t.Run("x", func(t *testing.T) {
+ t.Run("y", func(t *testing.T) {
+ panic("die")
+ })
+ })
+}
\ No newline at end of file
diff --git a/src/testing/testing.go b/src/testing/testing.go
index 66f296234a..d86354093a 100644
--- a/src/testing/testing.go
+++ b/src/testing/testing.go
@@ -1075,6 +1075,7 @@ func tRunner(t *T, fn func(t *T)) {
// If the test panicked, print any test output before dying.
err := recover()
signal := true
+
if !t.finished && err == nil {
err = errNilPanicOrGoexit
for p := t.parent; p != nil; p = p.parent {
@@ -1086,6 +1087,15 @@ func tRunner(t *T, fn func(t *T)) {
}
}
}
+ // Use a deferred call to ensure that we report that the test is
+ // complete even if a cleanup function calls t.FailNow. See issue 41355.
+ didPanic := false
+ defer func() {
+ t.signal <- signal
+ if err != nil && !didPanic {
+ panic(err)
+ }
+ }()
doPanic := func(err interface{}) {
t.Fail()
@@ -1103,6 +1113,7 @@ func tRunner(t *T, fn func(t *T)) {
fmt.Fprintf(root.parent.w, "cleanup panicked with %v", r)
}
}
+ didPanic = true
panic(err)
}
if err != nil {
@@ -1144,7 +1155,6 @@ func tRunner(t *T, fn func(t *T)) {
if t.parent != nil && atomic.LoadInt32(&t.hasSub) == 0 {
t.setRan()
}
- t.signal <- signal
}()
defer func() {
if len(t.sub) == 0 {
--
cgit v1.3
From 237410547bb81ae3c58e9c5bf0cf59edc989e243 Mon Sep 17 00:00:00 2001
From: Matthew Dempsky
Date: Mon, 14 Sep 2020 12:53:36 -0700
Subject: cmd/compile: better dclcontext handling in func{hdr,body}
funchdr and funcbody currently assume that either (1) Curfn == nil &&
dclcontext == PEXTERN, or (2) Curfn != nil && dclcontext == PAUTO.
This is a reasonable assumption during parsing. However, these
functions end up getting used in other contexts, and not all callers
are so disciplined about Curfn/dclcontext handling.
This CL changes them to save/restore arbitrary Curfn/dclcontext pairs
instead. This is necessary for the followup CL, which pushes fninit
earlier. Otherwise, Curfn/dclcontext fall out of sync, and funchdr
panics.
Passes toolstash-check.
Updates #33485.
Change-Id: I19b1be23db1bad6475345ae5c81bbdc66291a3a7
Reviewed-on: https://go-review.googlesource.com/c/go/+/254838
Run-TryBot: Matthew Dempsky
TryBot-Result: Go Bot
Reviewed-by: Keith Randall
Trust: Matthew Dempsky
---
src/cmd/compile/internal/gc/dcl.go | 28 +++++++++++++---------------
src/cmd/compile/internal/gc/main.go | 3 +++
2 files changed, 16 insertions(+), 15 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/compile/internal/gc/dcl.go b/src/cmd/compile/internal/gc/dcl.go
index 69eb13f607..a362d1a643 100644
--- a/src/cmd/compile/internal/gc/dcl.go
+++ b/src/cmd/compile/internal/gc/dcl.go
@@ -382,14 +382,11 @@ func ifacedcl(n *Node) {
// returns in auto-declaration context.
func funchdr(n *Node) {
// change the declaration context from extern to auto
- if Curfn == nil && dclcontext != PEXTERN {
- Fatalf("funchdr: dclcontext = %d", dclcontext)
- }
-
+ funcStack = append(funcStack, funcStackEnt{Curfn, dclcontext})
+ Curfn = n
dclcontext = PAUTO
+
types.Markdcl()
- funcstack = append(funcstack, Curfn)
- Curfn = n
if n.Func.Nname != nil {
funcargs(n.Func.Nname.Name.Param.Ntype)
@@ -497,21 +494,22 @@ func funcarg2(f *types.Field, ctxt Class) {
declare(n, ctxt)
}
-var funcstack []*Node // stack of previous values of Curfn
+var funcStack []funcStackEnt // stack of previous values of Curfn/dclcontext
+
+type funcStackEnt struct {
+ curfn *Node
+ dclcontext Class
+}
// finish the body.
// called in auto-declaration context.
// returns in extern-declaration context.
func funcbody() {
- // change the declaration context from auto to extern
- if dclcontext != PAUTO {
- Fatalf("funcbody: unexpected dclcontext %d", dclcontext)
- }
+ // change the declaration context from auto to previous context
types.Popdcl()
- funcstack, Curfn = funcstack[:len(funcstack)-1], funcstack[len(funcstack)-1]
- if Curfn == nil {
- dclcontext = PEXTERN
- }
+ var e funcStackEnt
+ funcStack, e = funcStack[:len(funcStack)-1], funcStack[len(funcStack)-1]
+ Curfn, dclcontext = e.curfn, e.dclcontext
}
// structs, functions, and methods.
diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go
index eedfc4bb25..9bce6cf8cb 100644
--- a/src/cmd/compile/internal/gc/main.go
+++ b/src/cmd/compile/internal/gc/main.go
@@ -809,6 +809,9 @@ func Main(archInit func(*Arch)) {
}
}
+ if len(funcStack) != 0 {
+ Fatalf("funcStack is non-empty: %v", len(funcStack))
+ }
if len(compilequeue) != 0 {
Fatalf("%d uncompiled functions", len(compilequeue))
}
--
cgit v1.3
From f4936d09fd5a1fff890d63ee2ab9543243dc4da6 Mon Sep 17 00:00:00 2001
From: Matthew Dempsky
Date: Mon, 14 Sep 2020 12:56:37 -0700
Subject: cmd/compile: call fninit earlier
This allows the global initializers function to go through normal
mid-end optimizations (e.g., inlining, escape analysis) like any other
function.
Updates #33485.
Change-Id: I9bcfe98b8628d1aca09b4c238d8d3b74c69010a5
Reviewed-on: https://go-review.googlesource.com/c/go/+/254839
Reviewed-by: Keith Randall
Trust: Matthew Dempsky
---
src/cmd/compile/internal/gc/init.go | 8 +++-----
src/cmd/compile/internal/gc/main.go | 6 ++----
test/inline.go | 2 +-
3 files changed, 6 insertions(+), 10 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/compile/internal/gc/init.go b/src/cmd/compile/internal/gc/init.go
index 94cbcf9846..ec9cc4bddc 100644
--- a/src/cmd/compile/internal/gc/init.go
+++ b/src/cmd/compile/internal/gc/init.go
@@ -59,7 +59,7 @@ func fninit(n []*Node) {
Curfn = fn
typecheckslice(nf, ctxStmt)
Curfn = nil
- funccompile(fn)
+ xtop = append(xtop, fn)
fns = append(fns, initializers.Linksym())
}
if dummyInitFn.Func.Dcl != nil {
@@ -68,16 +68,14 @@ func fninit(n []*Node) {
// something's weird if we get here.
Fatalf("dummyInitFn still has declarations")
}
+ dummyInitFn = nil
// Record user init functions.
for i := 0; i < renameinitgen; i++ {
s := lookupN("init.", i)
fn := asNode(s.Def).Name.Defn
// Skip init functions with empty bodies.
- // noder.go doesn't allow external init functions, and
- // order.go has already removed any OEMPTY nodes, so
- // checking Len() == 0 is sufficient here.
- if fn.Nbody.Len() == 0 {
+ if fn.Nbody.Len() == 1 && fn.Nbody.First().Op == OEMPTY {
continue
}
fns = append(fns, s.Linksym())
diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go
index 9bce6cf8cb..8783cb4e46 100644
--- a/src/cmd/compile/internal/gc/main.go
+++ b/src/cmd/compile/internal/gc/main.go
@@ -642,6 +642,8 @@ func Main(archInit func(*Arch)) {
errorexit()
}
+ fninit(xtop)
+
// Phase 4: Decide how to capture closed variables.
// This needs to run before escape analysis,
// because variables captured by value do not escape.
@@ -751,10 +753,6 @@ func Main(archInit func(*Arch)) {
}
timings.AddEvent(fcount, "funcs")
- if nsavederrors+nerrors == 0 {
- fninit(xtop)
- }
-
compileFunctions()
if nowritebarrierrecCheck != nil {
diff --git a/test/inline.go b/test/inline.go
index 0b3ad55d46..1c5c1bc8d3 100644
--- a/test/inline.go
+++ b/test/inline.go
@@ -50,7 +50,7 @@ func j(x int) int { // ERROR "can inline j"
}
}
-var somethingWrong error = errors.New("something went wrong")
+var somethingWrong error = errors.New("something went wrong") // ERROR "can inline init" "inlining call to errors.New" "errors.errorString.* escapes to heap"
// local closures can be inlined
func l(x, y int) (int, int, error) {
--
cgit v1.3
From d20298e1c7d1df794a11ce7768e027c6759df2a4 Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Mon, 14 Sep 2020 10:38:45 +0700
Subject: cmd/compile: make funccompile non-reentrant
Currently, there's awkward reentrancy issue with funccompile:
funccompile -> compile -> dtypesym -> geneq/genhash/genwrapper -> funccompile
Though it's not a problem at this moment, some attempts by @mdempsky to
move order/walk/instrument into buildssa was failed, due to SSA cache
corruption.
This commit fixes that reentrancy issue, by making generated functions
to be pumped through the same compile workqueue that normal functions
are compiled. We do this by adding them to xtop, instead of calling
funccompile directly in geneq/genhash/genwrapper. In dumpdata, we look
for uncompiled functions in xtop instead of compilequeue, then finish
compiling them.
Updates #38463
Fixes #33485
Change-Id: Ic9f0ce45b56ae2ff3862f17fd979253ddc144bb5
Reviewed-on: https://go-review.googlesource.com/c/go/+/254617
Run-TryBot: Cuong Manh Le
Reviewed-by: Matthew Dempsky
TryBot-Result: Go Bot
Trust: Keith Randall
Trust: Cuong Manh Le
---
src/cmd/compile/internal/gc/alg.go | 4 ++--
src/cmd/compile/internal/gc/obj.go | 26 +++++++++++++++++++++++++-
src/cmd/compile/internal/gc/subr.go | 2 +-
3 files changed, 28 insertions(+), 4 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/compile/internal/gc/alg.go b/src/cmd/compile/internal/gc/alg.go
index c9d71ea00b..6302b88f59 100644
--- a/src/cmd/compile/internal/gc/alg.go
+++ b/src/cmd/compile/internal/gc/alg.go
@@ -392,7 +392,7 @@ func genhash(t *types.Type) *obj.LSym {
}
fn.Func.SetNilCheckDisabled(true)
- funccompile(fn)
+ xtop = append(xtop, fn)
// Build closure. It doesn't close over any variables, so
// it contains just the function pointer.
@@ -754,7 +754,7 @@ func geneq(t *types.Type) *obj.LSym {
// neither of which can be nil, and our comparisons
// are shallow.
fn.Func.SetNilCheckDisabled(true)
- funccompile(fn)
+ xtop = append(xtop, fn)
// Generate a closure which points at the function we just generated.
dsymptr(closure, 0, sym.Linksym(), 0)
diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go
index af5037c5a8..b55331a948 100644
--- a/src/cmd/compile/internal/gc/obj.go
+++ b/src/cmd/compile/internal/gc/obj.go
@@ -113,12 +113,16 @@ func dumpCompilerObj(bout *bio.Writer) {
func dumpdata() {
externs := len(externdcl)
+ xtops := len(xtop)
dumpglobls()
addptabs()
+ exportlistLen := len(exportlist)
addsignats(externdcl)
dumpsignats()
dumptabs()
+ ptabsLen := len(ptabs)
+ itabsLen := len(itabs)
dumpimportstrings()
dumpbasictypes()
@@ -129,9 +133,19 @@ func dumpdata() {
// number of types in a finite amount of code.
// In the typical case, we loop 0 or 1 times.
// It was not until issue 24761 that we found any code that required a loop at all.
- for len(compilequeue) > 0 {
+ for {
+ for i := xtops; i < len(xtop); i++ {
+ n := xtop[i]
+ if n.Op == ODCLFUNC {
+ funccompile(n)
+ }
+ }
+ xtops = len(xtop)
compileFunctions()
dumpsignats()
+ if xtops == len(xtop) {
+ break
+ }
}
// Dump extra globals.
@@ -149,6 +163,16 @@ func dumpdata() {
}
addGCLocals()
+
+ if exportlistLen != len(exportlist) {
+ Fatalf("exportlist changed after compile functions loop")
+ }
+ if ptabsLen != len(ptabs) {
+ Fatalf("ptabs changed after compile functions loop")
+ }
+ if itabsLen != len(itabs) {
+ Fatalf("itabs changed after compile functions loop")
+ }
}
func dumpLinkerObj(bout *bio.Writer) {
diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go
index d3ba53ff0c..5a5833d19f 100644
--- a/src/cmd/compile/internal/gc/subr.go
+++ b/src/cmd/compile/internal/gc/subr.go
@@ -1615,7 +1615,7 @@ func genwrapper(rcvr *types.Type, method *types.Field, newnam *types.Sym) {
escapeFuncs([]*Node{fn}, false)
Curfn = nil
- funccompile(fn)
+ xtop = append(xtop, fn)
}
func paramNnames(ft *types.Type) []*Node {
--
cgit v1.3
From ea33523877e4c7a136f2db94a8b5bc4e40220be2 Mon Sep 17 00:00:00 2001
From: Xiangdong Ji
Date: Tue, 8 Sep 2020 09:55:20 +0000
Subject: cmd/compile: rewrite some ARM64 rules to use typed aux
Passes toolstash-check -all.
Change-Id: I7ec36bc048f3031c8201107e6fc5d1257271dbf1
Reviewed-on: https://go-review.googlesource.com/c/go/+/234379
Run-TryBot: Alberto Donizetti
TryBot-Result: Go Bot
Trust: Alberto Donizetti
Reviewed-by: Keith Randall
---
src/cmd/compile/internal/ssa/gen/ARM64.rules | 496 +++++++--------
src/cmd/compile/internal/ssa/rewriteARM64.go | 912 +++++++++++++--------------
2 files changed, 704 insertions(+), 704 deletions(-)
(limited to 'src/cmd')
diff --git a/src/cmd/compile/internal/ssa/gen/ARM64.rules b/src/cmd/compile/internal/ssa/gen/ARM64.rules
index 311067e87a..c4a3532632 100644
--- a/src/cmd/compile/internal/ssa/gen/ARM64.rules
+++ b/src/cmd/compile/internal/ssa/gen/ARM64.rules
@@ -1116,280 +1116,280 @@
// if a register move has only 1 use, just use the same register without emitting instruction
// MOVDnop doesn't emit instruction, only for ensuring the type.
-(MOVDreg x) && x.Uses == 1 -> (MOVDnop x)
+(MOVDreg x) && x.Uses == 1 => (MOVDnop x)
// fold constant into arithmatic ops
-(ADD x (MOVDconst [c])) -> (ADDconst [c] x)
-(SUB x (MOVDconst [c])) -> (SUBconst [c] x)
-(AND x (MOVDconst [c])) -> (ANDconst [c] x)
-(OR x (MOVDconst [c])) -> (ORconst [c] x)
-(XOR x (MOVDconst [c])) -> (XORconst [c] x)
-(TST x (MOVDconst [c])) -> (TSTconst [c] x)
-(TSTW x (MOVDconst [c])) -> (TSTWconst [c] x)
-(CMN x (MOVDconst [c])) -> (CMNconst [c] x)
-(CMNW x (MOVDconst [c])) -> (CMNWconst [c] x)
-(BIC x (MOVDconst [c])) -> (ANDconst [^c] x)
-(EON x (MOVDconst [c])) -> (XORconst [^c] x)
-(ORN x (MOVDconst [c])) -> (ORconst [^c] x)
-
-(SLL x (MOVDconst [c])) -> (SLLconst x [c&63]) // Note: I don't think we ever generate bad constant shifts (i.e. c>=64)
-(SRL x (MOVDconst [c])) -> (SRLconst x [c&63])
-(SRA x (MOVDconst [c])) -> (SRAconst x [c&63])
-
-(CMP x (MOVDconst [c])) -> (CMPconst [c] x)
-(CMP (MOVDconst [c]) x) -> (InvertFlags (CMPconst [c] x))
+(ADD x (MOVDconst [c])) => (ADDconst [c] x)
+(SUB x (MOVDconst [c])) => (SUBconst [c] x)
+(AND x (MOVDconst [c])) => (ANDconst [c] x)
+(OR x (MOVDconst [c])) => (ORconst [c] x)
+(XOR x (MOVDconst [c])) => (XORconst [c] x)
+(TST x (MOVDconst [c])) => (TSTconst [c] x)
+(TSTW x (MOVDconst [c])) => (TSTWconst [int32(c)] x)
+(CMN x (MOVDconst [c])) => (CMNconst [c] x)
+(CMNW x (MOVDconst [c])) => (CMNWconst [int32(c)] x)
+(BIC x (MOVDconst [c])) => (ANDconst [^c] x)
+(EON x (MOVDconst [c])) => (XORconst [^c] x)
+(ORN x (MOVDconst [c])) => (ORconst [^c] x)
+
+(SLL x (MOVDconst [c])) => (SLLconst x [c&63]) // Note: I don't think we ever generate bad constant shifts (i.e. c>=64)
+(SRL x (MOVDconst [c])) => (SRLconst x [c&63])
+(SRA x (MOVDconst [c])) => (SRAconst x [c&63])
+
+(CMP x (MOVDconst [c])) => (CMPconst [c] x)
+(CMP (MOVDconst [c]) x) => (InvertFlags (CMPconst [c] x))
(CMPW x (MOVDconst [c])) => (CMPWconst [int32(c)] x)
(CMPW (MOVDconst [c]) x) => (InvertFlags (CMPWconst [int32(c)] x))
// Canonicalize the order of arguments to comparisons - helps with CSE.
-((CMP|CMPW) x y) && x.ID > y.ID -> (InvertFlags ((CMP|CMPW) y x))
+((CMP|CMPW) x y) && x.ID > y.ID => (InvertFlags ((CMP|CMPW) y x))
-// mul-neg -> mneg
-(NEG (MUL x y)) -> (MNEG x y)
-(NEG (MULW x y)) -> (MNEGW x y)
-(MUL (NEG x) y) -> (MNEG x y)
-(MULW (NEG x) y) -> (MNEGW x y)
+// mul-neg => mneg
+(NEG (MUL x y)) => (MNEG x y)
+(NEG (MULW x y)) => (MNEGW x y)
+(MUL (NEG x) y) => (MNEG x y)
+(MULW (NEG x) y) => (MNEGW x y)
// madd/msub
-(ADD a l:(MUL x y)) && l.Uses==1 && clobber(l) -> (MADD a x y)
-(SUB a l:(MUL x y)) && l.Uses==1 && clobber(l) -> (MSUB a x y)
-(ADD a l:(MNEG x y)) && l.Uses==1 && clobber(l) -> (MSUB a x y)
-(SUB a l:(MNEG x y)) && l.Uses==1 && clobber(l) -> (MADD a x y)
+(ADD a l:(MUL x y)) && l.Uses==1 && clobber(l) => (MADD a x y)
+(SUB a l:(MUL x y)) && l.Uses==1 && clobber(l) => (MSUB a x y)
+(ADD a l:(MNEG x y)) && l.Uses==1 && clobber(l) => (MSUB a x y)
+(SUB a l:(MNEG x y)) && l.Uses==1 && clobber(l) => (MADD a x y)
-(ADD a l:(MULW x y)) && a.Type.Size() != 8 && l.Uses==1 && clobber(l) -> (MADDW a x y)
-(SUB a l:(MULW x y)) && a.Type.Size() != 8 && l.Uses==1 && clobber(l) -> (MSUBW a x y)
-(ADD a l:(MNEGW x y)) && a.Type.Size() != 8 && l.Uses==1 && clobber(l) -> (MSUBW a x y)
-(SUB a l:(MNEGW x y)) && a.Type.Size() != 8 && l.Uses==1 && clobber(l) -> (MADDW a x y)
+(ADD a l:(MULW x y)) && a.Type.Size() != 8 && l.Uses==1 && clobber(l) => (MADDW a x y)
+(SUB a l:(MULW x y)) && a.Type.Size() != 8 && l.Uses==1 && clobber(l) => (MSUBW a x y)
+(ADD a l:(MNEGW x y)) && a.Type.Size() != 8 && l.Uses==1 && clobber(l) => (MSUBW a x y)
+(SUB a l:(MNEGW x y)) && a.Type.Size() != 8 && l.Uses==1 && clobber(l) => (MADDW a x y)
// optimize ADCSflags, SBCSflags and friends
-(ADCSflags x y (Select1 (ADDSconstflags [-1] (ADCzerocarry c)))) -> (ADCSflags x y c)
-(ADCSflags x y (Select1 (ADDSconstflags [-1] (MOVDconst [0])))) -> (ADDSflags x y)
-(SBCSflags x y (Select1 (NEGSflags (NEG (NGCzerocarry bo))))) -> (SBCSflags x y bo)
-(SBCSflags x y (Select1 (NEGSflags (MOVDconst [0])))) -> (SUBSflags x y)
+(ADCSflags x y (Select1 (ADDSconstflags [-1] (ADCzerocarry c)))) => (ADCSflags x y c)
+(ADCSflags x y (Select1 (ADDSconstflags [-1] (MOVDconst [0])))) => (ADDSflags x y)
+(SBCSflags x y (Select1 (NEGSflags (NEG (NGCzerocarry bo))))) => (SBCSflags x y bo)
+(SBCSflags x y (Select1 (NEGSflags (MOVDconst [0])))) => (SUBSflags x y)
// mul by constant
-(MUL x (MOVDconst [-1])) -> (NEG x)
-(MUL _ (MOVDconst [0])) -> (MOVDconst [0])
-(MUL x (MOVDconst [1])) -> x
-(MUL x (MOVDconst [c])) && isPowerOfTwo(c) -> (SLLconst [log2(c)] x)
-(MUL x (MOVDconst [c])) && isPowerOfTwo(c-1) && c >= 3 -> (ADDshiftLL x x [log2(c-1)])
-(MUL x (MOVDconst [c])) && isPowerOfTwo(c+1) && c >= 7 -> (ADDshiftLL (NEG x) x [log2(c+1)])
-(MUL x (MOVDconst [c])) && c%3 == 0 && isPowerOfTwo(c/3) -> (SLLconst [log2(c/3)] (ADDshiftLL x x [1]))
-(MUL x (MOVDconst [c])) && c%5 == 0 && isPowerOfTwo(c/5) -> (SLLconst [log2(c/5)] (ADDshiftLL x x [2]))
-(MUL x (MOVDconst [c])) && c%7 == 0 && isPowerOfTwo(c/7) -> (SLLconst [log2(c/7)] (ADDshiftLL (NEG x) x [3]))
-(MUL x (MOVDconst [c])) && c%9 == 0 && isPowerOfTwo(c/9) -> (SLLconst [log2(c/9)] (ADDshiftLL x x [3]))
-
-(MULW x (MOVDconst [c])) && int32(c)==-1 -> (NEG x)
-(MULW _ (MOVDconst [c])) && int32(c)==0 -> (MOVDconst [0])
-(MULW x (MOVDconst [c])) && int32(c)==1 -> x
-(MULW x (MOVDconst [c])) && isPowerOfTwo(c) -> (SLLconst [log2(c)] x)
-(MULW x (MOVDconst [c])) && isPowerOfTwo(c-1) && int32(c) >= 3 -> (ADDshiftLL x x [log2(c-1)])
-(MULW x (MOVDconst [c])) && isPowerOfTwo(c+1) && int32(c) >= 7 -> (ADDshiftLL (NEG x) x [log2(c+1)])
-(MULW x (MOVDconst [c])) && c%3 == 0 && isPowerOfTwo(c/3) && is32Bit(c) -> (SLLconst [log2(c/3)] (ADDshiftLL x x [1]))
-(MULW x (MOVDconst [c])) && c%5 == 0 && isPowerOfTwo(c/5) && is32Bit(c) -> (SLLconst [log2(c/5)] (ADDshiftLL x x [2]))
-(MULW x (MOVDconst [c])) && c%7 == 0 && isPowerOfTwo(c/7) && is32Bit(c) -> (SLLconst [log2(c/7)] (ADDshiftLL (NEG x) x [3]))
-(MULW x (MOVDconst [c])) && c%9 == 0 && isPowerOfTwo(c/9) && is32Bit(c) -> (SLLconst [log2(c/9)] (ADDshiftLL x x [3]))
+(MUL x (MOVDconst [-1])) => (NEG x)
+(MUL _ (MOVDconst [0])) => (MOVDconst [0])
+(MUL x (MOVDconst [1])) => x
+(MUL x (MOVDconst [c])) && isPowerOfTwo(c) => (SLLconst [log2(c)] x)
+(MUL x (MOVDconst [c])) && isPowerOfTwo(c-1) && c >= 3 => (ADDshiftLL x x [log2(c-1)])
+(MUL x (MOVDconst [c])) && isPowerOfTwo(c+1) && c >= 7 => (ADDshiftLL (NEG x) x [log2(c+1)])
+(MUL x (MOVDconst [c])) && c%3 == 0 && isPowerOfTwo(c/3) => (SLLconst [log2(c/3)] (ADDshiftLL x x [1]))
+(MUL x (MOVDconst [c])) && c%5 == 0 && isPowerOfTwo(c/5) => (SLLconst [log2(c/5)] (ADDshiftLL x x [2]))
+(MUL x (MOVDconst [c])) && c%7 == 0 && isPowerOfTwo(c/7) => (SLLconst [log2(c/7)] (ADDshiftLL (NEG x) x [3]))
+(MUL x (MOVDconst [c])) && c%9 == 0 && isPowerOfTwo(c/9) => (SLLconst [log2(c/9)] (ADDshiftLL x x [3]))
+
+(MULW x (MOVDconst [c])) && int32(c)==-1 => (NEG x)
+(MULW _ (MOVDconst [c])) && int32(c)==0 => (MOVDconst [0])
+(MULW x (MOVDconst [c])) && int32(c)==1 => x
+(MULW x (MOVDconst [c])) && isPowerOfTwo(c) => (SLLconst [log2(c)] x)
+(MULW x (MOVDconst [c])) && isPowerOfTwo(c-1) && int32(c) >= 3 => (ADDshiftLL x x [log2(c-1)])
+(MULW x (MOVDconst [c])) && isPowerOfTwo(c+1) && int32(c) >= 7 => (ADDshiftLL (NEG x) x [log2(c+1)])
+(MULW x (MOVDconst [c])) && c%3 == 0 && isPowerOfTwo(c/3) && is32Bit(c) => (SLLconst [log2(c/3)] (ADDshiftLL x x [1]))
+(MULW x (MOVDconst [c])) && c%5 == 0 && isPowerOfTwo(c/5) && is32Bit(c) => (SLLconst [log2(c/5)] (ADDshiftLL x x [2]))
+(MULW x (MOVDconst [c])) && c%7 == 0 && isPowerOfTwo(c/7) && is32Bit(c) => (SLLconst [log2(c/7)] (ADDshiftLL (NEG x) x [3]))
+(MULW x (MOVDconst [c])) && c%9 == 0 && isPowerOfTwo(c/9) && is32Bit(c) => (SLLconst [log2(c/9)] (ADDshiftLL x x [3]))
// mneg by constant
-(MNEG x (MOVDconst [-1])) -> x
-(MNEG _ (MOVDconst [0])) -> (MOVDconst [0])
-(MNEG x (MOVDconst [1])) -> (NEG x)
-(MNEG x (MOVDconst [c])) && isPowerOfTwo(c) -> (NEG (SLLconst [log2(c)] x))
-(MNEG x (MOVDconst [c])) && isPowerOfTwo(c-1) && c >= 3 -> (NEG (ADDshiftLL x x [log2(c-1)]))
-(MNEG x (MOVDconst [c])) && isPowerOfTwo(c+1) && c >= 7 -> (NEG (ADDshiftLL (NEG x) x [log2(c+1)]))
-(MNEG x (MOVDconst [c])) && c%3 == 0 && isPowerOfTwo(c/3) -> (SLLconst [log2(c/3)] (SUBshiftLL x x [2]))
-(MNEG x (MOVDconst [c])) && c%5 == 0 && isPowerOfTwo(c/5) -> (NEG (SLLconst [log2(c/5)] (ADDshiftLL x x [2])))
-(MNEG x (MOVDconst [c])) && c%7 == 0 && isPowerOfTwo(c/7) -> (SLLconst [log2(c/7)] (SUBshiftLL x x [3]))
-(MNEG x (MOVDconst [c])) && c%9 == 0 && isPowerOfTwo(c/9) -> (NEG (SLLconst [log2(c/9)] (ADDshiftLL x x [3])))
-
-(MNEGW x (MOVDconst [c])) && int32(c)==-1 -> x
-(MNEGW _ (MOVDconst [c])) && int32(c)==0 -> (MOVDconst [0])
-(MNEGW x (MOVDconst [c])) && int32(c)==1 -> (NEG x)
-(MNEGW x (MOVDconst [c])) && isPowerOfTwo(c) -> (NEG (SLLconst [log2(c)] x))
-(MNEGW x (MOVDconst [c])) && isPowerOfTwo(c-1) && int32(c) >= 3 -> (NEG (ADDshiftLL x x [log2(c-1)]))
-(MNEGW x (MOVDconst [c])) && isPowerOfTwo(c+1) && int32(c) >= 7 -> (NEG (ADDshiftLL (NEG x) x [log2(c+1)]))
-(MNEGW x (MOVDconst [c])) && c%3 == 0 && isPowerOfTwo(c/3) && is32Bit(c) -> (SLLconst [log2(c/3)] (SUBshiftLL x x [2]))
-(MNEGW x (MOVDconst [c])) && c%5 == 0 && isPowerOfTwo(c/5) && is32Bit(c) -> (NEG (SLLconst [log2(c/5)] (ADDshiftLL x x [2])))
-(MNEGW x (MOVDconst [c])) && c%7 == 0 && isPowerOfTwo(c/7) && is32Bit(c) -> (SLLconst [log2(c/7)] (SUBshiftLL x x [3]))
-(MNEGW x (MOVDconst [c])) && c%9 == 0 && isPowerOfTwo(c/9) && is32Bit(c) -> (NEG (SLLconst [log2(c/9)] (ADDshiftLL x x [3])))
-
-(MADD a x (MOVDconst [-1])) -> (SUB a x)
-(MADD a _ (MOVDconst [0])) -> a
-(MADD a x (MOVDconst [1])) -> (ADD a x)
-(MADD a x (MOVDconst [c])) && isPowerOfTwo(c) -> (ADDshiftLL a x [log2(c)])
-(MADD a x (MOVDconst [c])) && isPowerOfTwo(c-1) && c>=3 -> (ADD a (ADDshiftLL x x [log2(c-1)]))
-(MADD a x (MOVDconst [c])) && isPowerOfTwo(c+1) && c>=7 -> (SUB a (SUBshiftLL x x [log2(c+1)]))
-(MADD a x (MOVDconst [c])) && c%3 == 0 && isPowerOfTwo(c/3) -> (SUBshiftLL a (SUBshiftLL x x [2]) [log2(c/3)])
-(MADD a x (MOVDconst [c])) && c%5 == 0 && isPowerOfTwo(c/5) -> (ADDshiftLL a (ADDshiftLL x x [2]) [log2(c/5)])
-(MADD a x (MOVDconst [c])) && c%7 == 0 && isPowerOfTwo(c/7) -> (SUBshiftLL a (SUBshiftLL x x [3]) [log2(c/7)])
-(MADD a x (MOVDconst [c])) && c%9 == 0 && isPowerOfTwo(c/9) -> (ADDshiftLL a (ADDshiftLL x x [3]) [log2(c/9)])
-
-(MADD a (MOVDconst [-1]) x) -> (SUB a x)
-(MADD a (MOVDconst [0]) _) -> a
-(MADD a (MOVDconst [1]) x) -> (ADD a x)
-(MADD a (MOVDconst [c]) x) && isPowerOfTwo(c) -> (ADDshiftLL a x [log2(c)])
-(MADD a (MOVDconst [c]) x) && isPowerOfTwo(c-1) && c>=3 -> (ADD a (ADDshiftLL x x [log2(c-1)]))
-(MADD a (MOVDconst [c]) x) && isPowerOfTwo(c+1) && c>=7 -> (SUB a (SUBshiftLL x x [log2(c+1)]))
-(MADD a (MOVDconst [c]) x) && c%3 == 0 && isPowerOfTwo(c/3) -> (SUBshiftLL a (SUBshiftLL x x [2]) [log2(c/3)])
-(MADD a (MOVDconst [c]) x) && c%5 == 0 && isPowerOfTwo(c/5) -> (ADDshiftLL a (ADDshiftLL x x [2]) [log2(c/5)])
-(MADD a (MOVDconst [c]) x) && c%7 == 0 && isPowerOfTwo(c/7) -> (SUBshiftLL a (SUBshiftLL x x [3]) [log2(c/7)])
-(MADD a (MOVDconst [c]) x) && c%9 == 0 && isPowerOfTwo(c/9) -> (ADDshiftLL a (ADDshiftLL x x [3]) [log2(c/9)])
-
-(MADDW a x (MOVDconst [c])) && int32(c)==-1 -> (SUB a x)
-(MADDW a _ (MOVDconst [c])) && int32(c)==0 -> a
-(MADDW a x (MOVDconst [c])) && int32(c)==1 -> (ADD a x)
-(MADDW a x (MOVDconst [c])) && isPowerOfTwo(c) -> (ADDshiftLL a x [log2(c)])
-(MADDW a x (MOVDconst [c])) && isPowerOfTwo(c-1) && int32(c)>=3 -> (ADD a (ADDshiftLL x x [log2(c-1)]))
-(MADDW a x (MOVDconst [c])) && isPowerOfTwo(c+1) && int32(c)>=7 -> (SUB a (SUBshiftLL x x [log2(c+1)]))
-(MADDW a x (MOVDconst [c])) && c%3 == 0 && isPowerOfTwo(c/3) && is32Bit(c) -> (SUBshiftLL a (SUBshiftLL x x [2]) [log2(c/3)])
-(MADDW a x (MOVDconst [c])) && c%5 == 0 && isPowerOfTwo(c/5) && is32Bit(c) -> (ADDshiftLL a (ADDshiftLL x x [2]) [log2(c/5)])
-(MADDW a x (MOVDconst [c])) && c%7 == 0 && isPowerOfTwo(c/7) && is32Bit(c) -> (SUBshiftLL a (SUBshiftLL x x [3]) [log2(c/7)])
-(MADDW a x (MOVDconst [c])) && c%9 == 0 && isPowerOfTwo(c/9) && is32Bit(c) -> (ADDshiftLL a (ADDshiftLL x x [3]) [log2(c/9)])
-
-(MADDW a (MOVDconst [c]) x) && int32(c)==-1 -> (SUB a x)
-(MADDW a (MOVDconst [c]) _) && int32(c)==0 -> a
-(MADDW a (MOVDconst [c]) x) && int32(c)==1 -> (ADD a x)
-(MADDW a (MOVDconst [c]) x) && isPowerOfTwo(c) -> (ADDshiftLL a x [log2(c)])
-(MADDW a (MOVDconst [c]) x) && isPowerOfTwo(c-1) && int32(c)>=3 -> (ADD a (ADDshiftLL x x [log2(c-1)]))
-(MADDW a (MOVDconst [c]) x) && isPowerOfTwo(c+1) && int32(c)>=7 -> (SUB a (SUBshiftLL