Skip to content

Commit 2b29c66

Browse files
committed
internal/gcimporter: API for shallow export data
This change adds an internal API for marshalling and unmarshalling a types.Package to "shallow" export data, which does not index packages other than the main one. The import function accepts a function that loads symbols on demand (e.g. by recursively reading export data for indirect dependencies). The CL includes a test that the entire standard library can be type-checked using shallow data. Also: - break dependency on go/ast. - narrow the name and type of qualifiedObject. - add (test) dependency on errgroup, and tidy go.mod. Change-Id: I92d31efd343cf5dd6fca6d7b918a23749e2d1e83 Reviewed-on: https://go-review.googlesource.com/c/tools/+/447737 Run-TryBot: Alan Donovan <[email protected]> Reviewed-by: Matthew Dempsky <[email protected]> TryBot-Result: Gopher Robot <[email protected]> gopls-CI: kokoro <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent affa603 commit 2b29c66

File tree

10 files changed

+242
-23
lines changed

10 files changed

+242
-23
lines changed

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ require (
88
golang.org/x/net v0.1.0
99
golang.org/x/sys v0.1.0
1010
)
11+
12+
require golang.org/x/sync v0.1.0

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
1313
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
1414
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1515
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
16+
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
17+
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1618
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1719
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1820
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

gopls/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/jba/templatecheck v0.6.0
99
github.com/sergi/go-diff v1.1.0
1010
golang.org/x/mod v0.6.0
11-
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
11+
golang.org/x/sync v0.1.0
1212
golang.org/x/sys v0.1.0
1313
golang.org/x/text v0.4.0
1414
golang.org/x/tools v0.1.13-0.20220928184430-f80e98464e27

gopls/go.sum

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
5555
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
5656
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
5757
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
58-
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
5958
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
59+
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
60+
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
6061
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
6162
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
6263
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

internal/gcimporter/bexport.go

+4-5
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"bytes"
1313
"encoding/binary"
1414
"fmt"
15-
"go/ast"
1615
"go/constant"
1716
"go/token"
1817
"go/types"
@@ -145,7 +144,7 @@ func BExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error)
145144
objcount := 0
146145
scope := pkg.Scope()
147146
for _, name := range scope.Names() {
148-
if !ast.IsExported(name) {
147+
if !token.IsExported(name) {
149148
continue
150149
}
151150
if trace {
@@ -482,7 +481,7 @@ func (p *exporter) method(m *types.Func) {
482481

483482
p.pos(m)
484483
p.string(m.Name())
485-
if m.Name() != "_" && !ast.IsExported(m.Name()) {
484+
if m.Name() != "_" && !token.IsExported(m.Name()) {
486485
p.pkg(m.Pkg(), false)
487486
}
488487

@@ -501,7 +500,7 @@ func (p *exporter) fieldName(f *types.Var) {
501500
// 3) field name doesn't match base type name (alias name)
502501
bname := basetypeName(f.Type())
503502
if name == bname {
504-
if ast.IsExported(name) {
503+
if token.IsExported(name) {
505504
name = "" // 1) we don't need to know the field name or package
506505
} else {
507506
name = "?" // 2) use unexported name "?" to force package export
@@ -514,7 +513,7 @@ func (p *exporter) fieldName(f *types.Var) {
514513
}
515514

516515
p.string(name)
517-
if name != "" && !ast.IsExported(name) {
516+
if name != "" && !token.IsExported(name) {
518517
p.pkg(f.Pkg(), false)
519518
}
520519
}

internal/gcimporter/bexport_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ type UnknownType undefined
109109

110110
// Compare the packages' corresponding members.
111111
for _, name := range pkg.Scope().Names() {
112-
if !ast.IsExported(name) {
112+
if !token.IsExported(name) {
113113
continue
114114
}
115115
obj1 := pkg.Scope().Lookup(name)

internal/gcimporter/iexport.go

+52-9
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"bytes"
1313
"encoding/binary"
1414
"fmt"
15-
"go/ast"
1615
"go/constant"
1716
"go/token"
1817
"go/types"
@@ -26,6 +25,41 @@ import (
2625
"golang.org/x/tools/internal/typeparams"
2726
)
2827

28+
// IExportShallow encodes "shallow" export data for the specified package.
29+
//
30+
// No promises are made about the encoding other than that it can be
31+
// decoded by the same version of IIExportShallow. If you plan to save
32+
// export data in the file system, be sure to include a cryptographic
33+
// digest of the executable in the key to avoid version skew.
34+
func IExportShallow(fset *token.FileSet, pkg *types.Package) ([]byte, error) {
35+
// In principle this operation can only fail if out.Write fails,
36+
// but that's impossible for bytes.Buffer---and as a matter of
37+
// fact iexportCommon doesn't even check for I/O errors.
38+
// TODO(adonovan): handle I/O errors properly.
39+
// TODO(adonovan): use byte slices throughout, avoiding copying.
40+
const bundle, shallow = false, true
41+
var out bytes.Buffer
42+
err := iexportCommon(&out, fset, bundle, shallow, iexportVersion, []*types.Package{pkg})
43+
return out.Bytes(), err
44+
}
45+
46+
// IImportShallow decodes "shallow" types.Package data encoded by IExportShallow
47+
// in the same executable. This function cannot import data from
48+
// cmd/compile or gcexportdata.Write.
49+
func IImportShallow(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string, insert InsertType) (*types.Package, error) {
50+
const bundle = false
51+
pkgs, err := iimportCommon(fset, imports, data, bundle, path, insert)
52+
if err != nil {
53+
return nil, err
54+
}
55+
return pkgs[0], nil
56+
}
57+
58+
// InsertType is the type of a function that creates a types.TypeName
59+
// object for a named type and inserts it into the scope of the
60+
// specified Package.
61+
type InsertType = func(pkg *types.Package, name string)
62+
2963
// Current bundled export format version. Increase with each format change.
3064
// 0: initial implementation
3165
const bundleVersion = 0
@@ -36,15 +70,17 @@ const bundleVersion = 0
3670
// The package path of the top-level package will not be recorded,
3771
// so that calls to IImportData can override with a provided package path.
3872
func IExportData(out io.Writer, fset *token.FileSet, pkg *types.Package) error {
39-
return iexportCommon(out, fset, false, iexportVersion, []*types.Package{pkg})
73+
const bundle, shallow = false, false
74+
return iexportCommon(out, fset, bundle, shallow, iexportVersion, []*types.Package{pkg})
4075
}
4176

4277
// IExportBundle writes an indexed export bundle for pkgs to out.
4378
func IExportBundle(out io.Writer, fset *token.FileSet, pkgs []*types.Package) error {
44-
return iexportCommon(out, fset, true, iexportVersion, pkgs)
79+
const bundle, shallow = true, false
80+
return iexportCommon(out, fset, bundle, shallow, iexportVersion, pkgs)
4581
}
4682

47-
func iexportCommon(out io.Writer, fset *token.FileSet, bundle bool, version int, pkgs []*types.Package) (err error) {
83+
func iexportCommon(out io.Writer, fset *token.FileSet, bundle, shallow bool, version int, pkgs []*types.Package) (err error) {
4884
if !debug {
4985
defer func() {
5086
if e := recover(); e != nil {
@@ -61,6 +97,7 @@ func iexportCommon(out io.Writer, fset *token.FileSet, bundle bool, version int,
6197
p := iexporter{
6298
fset: fset,
6399
version: version,
100+
shallow: shallow,
64101
allPkgs: map[*types.Package]bool{},
65102
stringIndex: map[string]uint64{},
66103
declIndex: map[types.Object]uint64{},
@@ -82,7 +119,7 @@ func iexportCommon(out io.Writer, fset *token.FileSet, bundle bool, version int,
82119
for _, pkg := range pkgs {
83120
scope := pkg.Scope()
84121
for _, name := range scope.Names() {
85-
if ast.IsExported(name) {
122+
if token.IsExported(name) {
86123
p.pushDecl(scope.Lookup(name))
87124
}
88125
}
@@ -205,7 +242,8 @@ type iexporter struct {
205242
out *bytes.Buffer
206243
version int
207244

208-
localpkg *types.Package
245+
shallow bool // don't put types from other packages in the index
246+
localpkg *types.Package // (nil in bundle mode)
209247

210248
// allPkgs tracks all packages that have been referenced by
211249
// the export data, so we can ensure to include them in the
@@ -256,6 +294,11 @@ func (p *iexporter) pushDecl(obj types.Object) {
256294
panic("cannot export package unsafe")
257295
}
258296

297+
// Shallow export data: don't index decls from other packages.
298+
if p.shallow && obj.Pkg() != p.localpkg {
299+
return
300+
}
301+
259302
if _, ok := p.declIndex[obj]; ok {
260303
return
261304
}
@@ -497,7 +540,7 @@ func (w *exportWriter) pkg(pkg *types.Package) {
497540
w.string(w.exportPath(pkg))
498541
}
499542

500-
func (w *exportWriter) qualifiedIdent(obj types.Object) {
543+
func (w *exportWriter) qualifiedType(obj *types.TypeName) {
501544
name := w.p.exportName(obj)
502545

503546
// Ensure any referenced declarations are written out too.
@@ -556,11 +599,11 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) {
556599
return
557600
}
558601
w.startType(definedType)
559-
w.qualifiedIdent(t.Obj())
602+
w.qualifiedType(t.Obj())
560603

561604
case *typeparams.TypeParam:
562605
w.startType(typeParamType)
563-
w.qualifiedIdent(t.Obj())
606+
w.qualifiedType(t.Obj())
564607

565608
case *types.Pointer:
566609
w.startType(pointerType)

internal/gcimporter/iexport_test.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ func readExportFile(filename string) ([]byte, error) {
5959

6060
func iexport(fset *token.FileSet, version int, pkg *types.Package) ([]byte, error) {
6161
var buf bytes.Buffer
62-
if err := gcimporter.IExportCommon(&buf, fset, false, version, []*types.Package{pkg}); err != nil {
62+
const bundle, shallow = false, false
63+
if err := gcimporter.IExportCommon(&buf, fset, bundle, shallow, version, []*types.Package{pkg}); err != nil {
6364
return nil, err
6465
}
6566
return buf.Bytes(), nil
@@ -197,7 +198,7 @@ func testPkg(t *testing.T, fset *token.FileSet, version int, pkg *types.Package,
197198

198199
// Compare the packages' corresponding members.
199200
for _, name := range pkg.Scope().Names() {
200-
if !ast.IsExported(name) {
201+
if !token.IsExported(name) {
201202
continue
202203
}
203204
obj1 := pkg.Scope().Lookup(name)

internal/gcimporter/iimport.go

+22-4
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const (
8585
// If the export data version is not recognized or the format is otherwise
8686
// compromised, an error is returned.
8787
func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (int, *types.Package, error) {
88-
pkgs, err := iimportCommon(fset, imports, data, false, path)
88+
pkgs, err := iimportCommon(fset, imports, data, false, path, nil)
8989
if err != nil {
9090
return 0, nil, err
9191
}
@@ -94,10 +94,10 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []
9494

9595
// IImportBundle imports a set of packages from the serialized package bundle.
9696
func IImportBundle(fset *token.FileSet, imports map[string]*types.Package, data []byte) ([]*types.Package, error) {
97-
return iimportCommon(fset, imports, data, true, "")
97+
return iimportCommon(fset, imports, data, true, "", nil)
9898
}
9999

100-
func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data []byte, bundle bool, path string) (pkgs []*types.Package, err error) {
100+
func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data []byte, bundle bool, path string, insert InsertType) (pkgs []*types.Package, err error) {
101101
const currentVersion = iexportVersionCurrent
102102
version := int64(-1)
103103
if !debug {
@@ -147,6 +147,7 @@ func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data
147147
p := iimporter{
148148
version: int(version),
149149
ipath: path,
150+
insert: insert,
150151

151152
stringData: stringData,
152153
stringCache: make(map[uint64]string),
@@ -187,11 +188,18 @@ func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data
187188
} else if pkg.Name() != pkgName {
188189
errorf("conflicting names %s and %s for package %q", pkg.Name(), pkgName, path)
189190
}
191+
if i == 0 && !bundle {
192+
p.localpkg = pkg
193+
}
190194

191195
p.pkgCache[pkgPathOff] = pkg
192196

197+
// Read index for package.
193198
nameIndex := make(map[string]uint64)
194-
for nSyms := r.uint64(); nSyms > 0; nSyms-- {
199+
nSyms := r.uint64()
200+
// In shallow mode we don't expect an index for other packages.
201+
assert(nSyms == 0 || p.localpkg == pkg || p.insert == nil)
202+
for ; nSyms > 0; nSyms-- {
195203
name := p.stringAt(r.uint64())
196204
nameIndex[name] = r.uint64()
197205
}
@@ -267,6 +275,9 @@ type iimporter struct {
267275
version int
268276
ipath string
269277

278+
localpkg *types.Package
279+
insert func(pkg *types.Package, name string) // "shallow" mode only
280+
270281
stringData []byte
271282
stringCache map[uint64]string
272283
pkgCache map[uint64]*types.Package
@@ -310,6 +321,13 @@ func (p *iimporter) doDecl(pkg *types.Package, name string) {
310321

311322
off, ok := p.pkgIndex[pkg][name]
312323
if !ok {
324+
// In "shallow" mode, call back to the application to
325+
// find the object and insert it into the package scope.
326+
if p.insert != nil {
327+
assert(pkg != p.localpkg)
328+
p.insert(pkg, name) // "can't fail"
329+
return
330+
}
313331
errorf("%v.%v not in index", pkg, name)
314332
}
315333

0 commit comments

Comments
 (0)