diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go index e3ef8aca281..653203377ef 100644 --- a/go/packages/packages_test.go +++ b/go/packages/packages_test.go @@ -3843,3 +3843,35 @@ import _ "errors" t.Errorf("packages checked = %v, want a and errors", checked) } } + +func TestCgoExportDataPosition(t *testing.T) { + testAllOrModulesParallel(t, func(t *testing.T, exporter packagestest.Exporter) { + testenv.NeedsTool(t, "cgo") + exported := packagestest.Export(t, exporter, []packagestest.Module{{ + Name: "golang.org/fake", + Files: map[string]any{ + "c/c.go": "package c\nimport \"C\"\n\nfunc F() {}\n\n//line c.go:70000\nfunc G() {}\nfunc H() {}\n", + "p/p.go": "package p\nimport \"golang.org/fake/c\"\nvar _, _, _ = c.F, c.G, c.H\n", + }, + }}) + exported.Config.Env = append(exported.Config.Env, "CGO_ENABLED=1") + exported.Config.Mode = packages.LoadSyntax + pkgs, err := packages.Load(exported.Config, "golang.org/fake/p") + if err != nil || len(pkgs) != 1 || len(pkgs[0].Errors) != 0 { + t.Fatalf("Load: %v, %v", pkgs, err) + } + wantLines := map[string]int{"F": 4, "G": 70000, "H": 70001} + for id, obj := range pkgs[0].TypesInfo.Uses { + if want, ok := wantLines[id.Name]; ok { + pos := pkgs[0].Fset.Position(obj.Pos()) + if filepath.Base(pos.Filename) != "c.go" || pos.Line != want { + t.Errorf("position of c.%s = %v, want c.go:%d", id.Name, pos, want) + } + delete(wantLines, id.Name) + } + } + for name := range wantLines { + t.Errorf("missing use of c.%s", name) + } + }) +} diff --git a/internal/gcimporter/bimport.go b/internal/gcimporter/bimport.go index 3fca56c09b8..43eaf0247e6 100644 --- a/internal/gcimporter/bimport.go +++ b/internal/gcimporter/bimport.go @@ -24,7 +24,12 @@ const deltaNewFile = -64 // see cmd/compile/internal/gc/bexport.go // Synthesize a token.Pos type fakeFileSet struct { fset *token.FileSet - files map[string]*fileInfo + files map[fileKey]*fileInfo +} + +type fileKey struct { + name string + lineBase int } type fileInfo struct { @@ -37,17 +42,21 @@ const maxlines = 64 * 1024 func (s *fakeFileSet) pos(file string, line, column int) token.Pos { _ = column // TODO(mdempsky): Make use of column. - // Since we don't know the set of needed file positions, we reserve maxlines - // positions per file. We delay calling token.File.SetLines until all - // positions have been calculated (by way of fakeFileSet.setLines), so that - // we can avoid setting unnecessary lines. See also golang/go#46586. - f := s.files[file] + // Use fixed-size windows so high line numbers can share the same + // line table. Delay SetLines until all positions have been calculated + // to avoid setting unnecessary lines. See golang/go#46586. + key := fileKey{name: file} + if line > maxlines { + key.lineBase = (line - 1) / maxlines * maxlines + line -= key.lineBase + } + f := s.files[key] if f == nil { f = &fileInfo{file: s.fset.AddFile(file, -1, maxlines)} - s.files[file] = f - } - if line > maxlines { - line = 1 + if key.lineBase != 0 { + f.file.AddLineColumnInfo(0, file, key.lineBase+1, 1) + } + s.files[key] = f } if line > f.lastline { f.lastline = line diff --git a/internal/gcimporter/iimport.go b/internal/gcimporter/iimport.go index 30585022b85..103a05d8385 100644 --- a/internal/gcimporter/iimport.go +++ b/internal/gcimporter/iimport.go @@ -223,7 +223,7 @@ func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte fake: fakeFileSet{ fset: fset, - files: make(map[string]*fileInfo), + files: make(map[fileKey]*fileInfo), }, } defer p.fake.setLines() // set lines for files in fset diff --git a/internal/gcimporter/ureader.go b/internal/gcimporter/ureader.go index ffc0b54dcc1..996741161a7 100644 --- a/internal/gcimporter/ureader.go +++ b/internal/gcimporter/ureader.go @@ -29,7 +29,7 @@ type pkgReader struct { // lazily initialized arrays corresponding to the unified IR // PosBase, Pkg, and Type sections, respectively. - posBases []string // position bases (i.e., file names) + posBases []positionBase pkgs []*types.Package typs []types.Type @@ -96,13 +96,13 @@ func readUnifiedPackage(fset *token.FileSet, ctxt *types.Context, imports map[st fake: fakeFileSet{ fset: fset, - files: make(map[string]*fileInfo), + files: make(map[fileKey]*fileInfo), }, ctxt: ctxt, imports: imports, - posBases: make([]string, input.NumElems(pkgbits.RelocPosBase)), + posBases: make([]positionBase, input.NumElems(pkgbits.RelocPosBase)), pkgs: make([]*types.Package, input.NumElems(pkgbits.RelocPkg)), typs: make([]types.Type, input.NumElems(pkgbits.RelocType)), } @@ -194,6 +194,11 @@ func (pr *pkgReader) retireReader(r *reader) { // @@@ Positions +type positionBase struct { + filename string + lineOffset int +} + func (r *reader) pos() token.Pos { r.Sync(pkgbits.SyncPos) if !r.Bool() { @@ -201,45 +206,31 @@ func (r *reader) pos() token.Pos { } // TODO(mdempsky): Delta encoding. - posBase := r.posBase() - line := r.Uint() - col := r.Uint() - return r.p.fake.pos(posBase, int(line), int(col)) -} - -func (r *reader) posBase() string { - return r.p.posBaseIdx(r.Reloc(pkgbits.RelocPosBase)) + base := r.p.posBaseIdx(r.Reloc(pkgbits.RelocPosBase)) + line := int(r.Uint()) + base.lineOffset + col := int(r.Uint()) + return r.p.fake.pos(base.filename, line, col) } -func (pr *pkgReader) posBaseIdx(idx pkgbits.Index) string { - if b := pr.posBases[idx]; b != "" { +func (pr *pkgReader) posBaseIdx(idx pkgbits.Index) positionBase { + if b := pr.posBases[idx]; b.filename != "" { return b } - var filename string - { - r := pr.tempReader(pkgbits.RelocPosBase, idx, pkgbits.SyncPosBase) - - // Within types2, position bases have a lot more details (e.g., - // keeping track of where //line directives appeared exactly). - // - // For go/types, we just track the file name. - - filename = r.String() - - if r.Bool() { // file base - // Was: "b = token.NewTrimmedFileBase(filename, true)" - } else { // line base - pos := r.pos() - line := r.Uint() - col := r.Uint() - - // Was: "b = token.NewLineBase(pos, filename, true, line, col)" - _, _, _ = pos, line, col + r := pr.tempReader(pkgbits.RelocPosBase, idx, pkgbits.SyncPosBase) + b := positionBase{filename: r.String()} + if !r.Bool() { // line directive + r.Sync(pkgbits.SyncPos) + var physicalLine int + if r.Bool() { + r.Reloc(pkgbits.RelocPosBase) + physicalLine = int(r.Uint()) + r.Uint() // physical column } - pr.retireReader(r) + b.lineOffset = int(r.Uint()) - physicalLine + r.Uint() // directive column } - b := filename + pr.retireReader(r) pr.posBases[idx] = b return b }