@@ -44,6 +44,7 @@ import (
44
44
"golang.org/x/tools/gopls/internal/util/bug"
45
45
"golang.org/x/tools/gopls/internal/util/frob"
46
46
"golang.org/x/tools/gopls/internal/util/maps"
47
+ "golang.org/x/tools/gopls/internal/util/slices"
47
48
"golang.org/x/tools/internal/analysisinternal"
48
49
"golang.org/x/tools/internal/event"
49
50
"golang.org/x/tools/internal/facts"
@@ -255,6 +256,7 @@ func (s *Snapshot) Analyze(ctx context.Context, pkgs map[PackageID]*metadata.Pac
255
256
256
257
an = & analysisNode {
257
258
fset : fset ,
259
+ fsource : struct { file.Source }{s }, // expose only ReadFile
258
260
mp : mp ,
259
261
analyzers : facty , // all nodes run at least the facty analyzers
260
262
allDeps : make (map [PackagePath ]* analysisNode ),
@@ -519,6 +521,7 @@ func (an *analysisNode) decrefPreds() {
519
521
// type-checking and analyzing syntax (miss).
520
522
type analysisNode struct {
521
523
fset * token.FileSet // file set shared by entire batch (DAG)
524
+ fsource file.Source // Snapshot.ReadFile, for use by Pass.ReadFile
522
525
mp * metadata.Package // metadata for this package
523
526
files []file.Handle // contents of CompiledGoFiles
524
527
analyzers []* analysis.Analyzer // set of analyzers to run
@@ -885,6 +888,7 @@ func (an *analysisNode) run(ctx context.Context) (*analyzeSummary, error) {
885
888
}
886
889
act = & action {
887
890
a : a ,
891
+ fsource : an .fsource ,
888
892
stableName : an .stableNames [a ],
889
893
pkg : pkg ,
890
894
vdeps : an .succs ,
@@ -902,7 +906,7 @@ func (an *analysisNode) run(ctx context.Context) (*analyzeSummary, error) {
902
906
}
903
907
904
908
// Execute the graph in parallel.
905
- execActions (roots )
909
+ execActions (ctx , roots )
906
910
// Inv: each root's summary is set (whether success or error).
907
911
908
912
// Don't return (or cache) the result in case of cancellation.
@@ -1135,7 +1139,8 @@ type analysisPackage struct {
1135
1139
type action struct {
1136
1140
once sync.Once
1137
1141
a * analysis.Analyzer
1138
- stableName string // cross-process stable name of analyzer
1142
+ fsource file.Source // Snapshot.ReadFile, for Pass.ReadFile
1143
+ stableName string // cross-process stable name of analyzer
1139
1144
pkg * analysisPackage
1140
1145
hdeps []* action // horizontal dependencies
1141
1146
vdeps map [PackageID ]* analysisNode // vertical dependencies
@@ -1152,16 +1157,16 @@ func (act *action) String() string {
1152
1157
1153
1158
// execActions executes a set of action graph nodes in parallel.
1154
1159
// Postcondition: each action.summary is set, even in case of error.
1155
- func execActions (actions []* action ) {
1160
+ func execActions (ctx context. Context , actions []* action ) {
1156
1161
var wg sync.WaitGroup
1157
1162
for _ , act := range actions {
1158
1163
act := act
1159
1164
wg .Add (1 )
1160
1165
go func () {
1161
1166
defer wg .Done ()
1162
1167
act .once .Do (func () {
1163
- execActions (act .hdeps ) // analyze "horizontal" dependencies
1164
- act .result , act .summary , act .err = act .exec ()
1168
+ execActions (ctx , act .hdeps ) // analyze "horizontal" dependencies
1169
+ act .result , act .summary , act .err = act .exec (ctx )
1165
1170
if act .err != nil {
1166
1171
act .summary = & actionSummary {Err : act .err .Error ()}
1167
1172
// TODO(adonovan): suppress logging. But
@@ -1185,7 +1190,7 @@ func execActions(actions []*action) {
1185
1190
// along with its (serializable) facts and diagnostics.
1186
1191
// Or it returns an error if the analyzer did not run to
1187
1192
// completion and deliver a valid result.
1188
- func (act * action ) exec () (interface {} , * actionSummary , error ) {
1193
+ func (act * action ) exec (ctx context. Context ) (any , * actionSummary , error ) {
1189
1194
analyzer := act .a
1190
1195
pkg := act .pkg
1191
1196
@@ -1284,75 +1289,114 @@ func (act *action) exec() (interface{}, *actionSummary, error) {
1284
1289
factFilter [reflect .TypeOf (f )] = true
1285
1290
}
1286
1291
1287
- // If the package contains "fixed" files, it's not necessarily an error if we
1288
- // can't convert positions.
1289
- hasFixedFiles := false
1290
- for _ , p := range pkg .parsed {
1291
- if p .Fixed () {
1292
- hasFixedFiles = true
1293
- break
1294
- }
1295
- }
1296
-
1297
1292
// posToLocation converts from token.Pos to protocol form.
1298
- // TODO(adonovan): improve error messages.
1299
1293
posToLocation := func (start , end token.Pos ) (protocol.Location , error ) {
1300
1294
tokFile := pkg .fset .File (start )
1301
1295
1296
+ // Find existing mapper by file name.
1297
+ // (Don't require an exact token.File match
1298
+ // as the analyzer may have re-parsed the file.)
1299
+ var (
1300
+ mapper * protocol.Mapper
1301
+ fixed bool
1302
+ )
1302
1303
for _ , p := range pkg .parsed {
1303
- if p .Tok == tokFile {
1304
- if end == token .NoPos {
1305
- end = start
1306
- }
1304
+ if p .Tok .Name () == tokFile .Name () {
1305
+ mapper = p .Mapper
1306
+ fixed = p .Fixed () // suppress some assertions after parser recovery
1307
+ break
1308
+ }
1309
+ }
1310
+ if mapper == nil {
1311
+ // The start position was not among the package's parsed
1312
+ // Go files, indicating that the analyzer added new files
1313
+ // to the FileSet.
1314
+ //
1315
+ // For example, the cgocall analyzer re-parses and
1316
+ // type-checks some of the files in a special environment;
1317
+ // and asmdecl and other low-level runtime analyzers call
1318
+ // ReadFile to parse non-Go files.
1319
+ // (This is a supported feature, documented at go/analysis.)
1320
+ //
1321
+ // In principle these files could be:
1322
+ //
1323
+ // - OtherFiles (non-Go files such as asm).
1324
+ // However, we set Pass.OtherFiles=[] because
1325
+ // gopls won't service "diagnose" requests
1326
+ // for non-Go files, so there's no point
1327
+ // reporting diagnostics in them.
1328
+ //
1329
+ // - IgnoredFiles (files tagged for other configs).
1330
+ // However, we set Pass.IgnoredFiles=[] because,
1331
+ // in most cases, zero-config gopls should create
1332
+ // another view that covers these files.
1333
+ //
1334
+ // - Referents of //line directives, as in cgo packages.
1335
+ // The file names in this case are not known a priori.
1336
+ // gopls generally tries to avoid honoring line directives,
1337
+ // but analyzers such as cgocall may honor them.
1338
+ //
1339
+ // In short, it's unclear how this can be reached
1340
+ // other than due to an analyzer bug.
1341
+ return protocol.Location {}, bug .Errorf ("diagnostic location is not among files of package: %s" , tokFile .Name ())
1342
+ }
1343
+ // Inv: mapper != nil
1307
1344
1308
- // debugging #64547
1309
- fileStart := token .Pos (tokFile .Base ())
1310
- fileEnd := fileStart + token .Pos (tokFile .Size ())
1311
- if start < fileStart {
1312
- bug .Reportf ("start < start of file" )
1313
- start = fileStart
1314
- }
1315
- if end < start {
1316
- // This can happen if End is zero (#66683)
1317
- // or a small positive displacement from zero
1318
- // due to recursively Node.End() computation.
1319
- // This usually arises from poor parser recovery
1320
- // of an incomplete term at EOF.
1321
- bug .Reportf ("end < start of file" )
1322
- end = fileEnd
1323
- }
1324
- if end > fileEnd + 1 {
1325
- bug .Reportf ("end > end of file + 1" )
1326
- end = fileEnd
1327
- }
1345
+ if end == token .NoPos {
1346
+ end = start
1347
+ }
1328
1348
1329
- return p .PosLocation (start , end )
1349
+ // debugging #64547
1350
+ fileStart := token .Pos (tokFile .Base ())
1351
+ fileEnd := fileStart + token .Pos (tokFile .Size ())
1352
+ if start < fileStart {
1353
+ if ! fixed {
1354
+ bug .Reportf ("start < start of file" )
1330
1355
}
1356
+ start = fileStart
1331
1357
}
1332
- errorf := bug .Errorf
1333
- if hasFixedFiles {
1334
- errorf = fmt .Errorf
1358
+ if end < start {
1359
+ // This can happen if End is zero (#66683)
1360
+ // or a small positive displacement from zero
1361
+ // due to recursive Node.End() computation.
1362
+ // This usually arises from poor parser recovery
1363
+ // of an incomplete term at EOF.
1364
+ if ! fixed {
1365
+ bug .Reportf ("end < start of file" )
1366
+ }
1367
+ end = fileEnd
1368
+ }
1369
+ if end > fileEnd + 1 {
1370
+ if ! fixed {
1371
+ bug .Reportf ("end > end of file + 1" )
1372
+ }
1373
+ end = fileEnd
1335
1374
}
1336
- return protocol.Location {}, errorf ("token.Pos not within package" )
1375
+
1376
+ return mapper .PosLocation (tokFile , start , end )
1337
1377
}
1338
1378
1339
1379
// Now run the (pkg, analyzer) action.
1340
1380
var diagnostics []gobDiagnostic
1381
+
1341
1382
pass := & analysis.Pass {
1342
- Analyzer : analyzer ,
1343
- Fset : pkg .fset ,
1344
- Files : pkg .files ,
1345
- Pkg : pkg .types ,
1346
- TypesInfo : pkg .typesInfo ,
1347
- TypesSizes : pkg .typesSizes ,
1348
- TypeErrors : pkg .typeErrors ,
1349
- ResultOf : inputs ,
1383
+ Analyzer : analyzer ,
1384
+ Fset : pkg .fset ,
1385
+ Files : pkg .files ,
1386
+ OtherFiles : nil , // since gopls doesn't handle non-Go (e.g. asm) files
1387
+ IgnoredFiles : nil , // zero-config gopls should analyze these files in another view
1388
+ Pkg : pkg .types ,
1389
+ TypesInfo : pkg .typesInfo ,
1390
+ TypesSizes : pkg .typesSizes ,
1391
+ TypeErrors : pkg .typeErrors ,
1392
+ ResultOf : inputs ,
1350
1393
Report : func (d analysis.Diagnostic ) {
1351
1394
diagnostic , err := toGobDiagnostic (posToLocation , analyzer , d )
1352
1395
if err != nil {
1353
- if ! hasFixedFiles {
1354
- bug .Reportf ("internal error converting diagnostic from analyzer %q: %v" , analyzer .Name , err )
1355
- }
1396
+ // Don't bug.Report here: these errors all originate in
1397
+ // posToLocation, and we can more accurately discriminate
1398
+ // severe errors from benign ones in that function.
1399
+ event .Error (ctx , fmt .Sprintf ("internal error converting diagnostic from analyzer %q" , analyzer .Name ), err )
1356
1400
return
1357
1401
}
1358
1402
diagnostics = append (diagnostics , diagnostic )
@@ -1364,9 +1408,29 @@ func (act *action) exec() (interface{}, *actionSummary, error) {
1364
1408
AllObjectFacts : func () []analysis.ObjectFact { return factset .AllObjectFacts (factFilter ) },
1365
1409
AllPackageFacts : func () []analysis.PackageFact { return factset .AllPackageFacts (factFilter ) },
1366
1410
}
1367
- // TODO(adonovan): integrate this into the snapshot's file
1368
- // cache and its dependency analysis.
1369
- pass .ReadFile = analysisinternal .MakeReadFile (pass )
1411
+
1412
+ pass .ReadFile = func (filename string ) ([]byte , error ) {
1413
+ // Read file from snapshot, to ensure reads are consistent.
1414
+ //
1415
+ // TODO(adonovan): make the dependency analysis sound by
1416
+ // incorporating these additional files into the the analysis
1417
+ // hash. This requires either (a) preemptively reading and
1418
+ // hashing a potentially large number of mostly irrelevant
1419
+ // files; or (b) some kind of dynamic dependency discovery
1420
+ // system like used in Bazel for C++ headers. Neither entices.
1421
+ if err := analysisinternal .CheckReadable (pass , filename ); err != nil {
1422
+ return nil , err
1423
+ }
1424
+ h , err := act .fsource .ReadFile (ctx , protocol .URIFromPath (filename ))
1425
+ if err != nil {
1426
+ return nil , err
1427
+ }
1428
+ content , err := h .Content ()
1429
+ if err != nil {
1430
+ return nil , err // file doesn't exist
1431
+ }
1432
+ return slices .Clone (content ), nil // follow ownership of os.ReadFile
1433
+ }
1370
1434
1371
1435
// Recover from panics (only) within the analyzer logic.
1372
1436
// (Use an anonymous function to limit the recover scope.)
0 commit comments