github.com/jd-ly/tools@v0.5.7/internal/lsp/cache/analysis.go (about) 1 // Copyright 2019 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package cache 6 7 import ( 8 "context" 9 "fmt" 10 "go/ast" 11 "go/types" 12 "reflect" 13 "sort" 14 "sync" 15 16 "golang.org/x/sync/errgroup" 17 "github.com/jd-ly/tools/go/analysis" 18 "github.com/jd-ly/tools/internal/analysisinternal" 19 "github.com/jd-ly/tools/internal/event" 20 "github.com/jd-ly/tools/internal/lsp/debug/tag" 21 "github.com/jd-ly/tools/internal/lsp/source" 22 "github.com/jd-ly/tools/internal/memoize" 23 errors "golang.org/x/xerrors" 24 ) 25 26 func (s *snapshot) Analyze(ctx context.Context, id string, analyzers ...*analysis.Analyzer) ([]*source.Error, error) { 27 var roots []*actionHandle 28 29 for _, a := range analyzers { 30 ah, err := s.actionHandle(ctx, packageID(id), a) 31 if err != nil { 32 return nil, err 33 } 34 roots = append(roots, ah) 35 } 36 37 // Check if the context has been canceled before running the analyses. 38 if ctx.Err() != nil { 39 return nil, ctx.Err() 40 } 41 42 var results []*source.Error 43 for _, ah := range roots { 44 diagnostics, _, err := ah.analyze(ctx, s) 45 if err != nil { 46 return nil, err 47 } 48 results = append(results, diagnostics...) 49 } 50 return results, nil 51 } 52 53 type actionHandleKey string 54 55 // An action represents one unit of analysis work: the application of 56 // one analysis to one package. Actions form a DAG, both within a 57 // package (as different analyzers are applied, either in sequence or 58 // parallel), and across packages (as dependencies are analyzed). 59 type actionHandle struct { 60 handle *memoize.Handle 61 62 analyzer *analysis.Analyzer 63 pkg *pkg 64 } 65 66 type actionData struct { 67 diagnostics []*source.Error 68 result interface{} 69 objectFacts map[objectFactKey]analysis.Fact 70 packageFacts map[packageFactKey]analysis.Fact 71 err error 72 } 73 74 type objectFactKey struct { 75 obj types.Object 76 typ reflect.Type 77 } 78 79 type packageFactKey struct { 80 pkg *types.Package 81 typ reflect.Type 82 } 83 84 func (s *snapshot) actionHandle(ctx context.Context, id packageID, a *analysis.Analyzer) (*actionHandle, error) { 85 ph := s.getPackage(id, source.ParseFull) 86 if ph == nil { 87 return nil, errors.Errorf("no package for %s", id) 88 } 89 act := s.getActionHandle(id, ph.mode, a) 90 if act != nil { 91 return act, nil 92 } 93 if len(ph.key) == 0 { 94 return nil, errors.Errorf("no key for package %s", id) 95 } 96 pkg, err := ph.check(ctx, s) 97 if err != nil { 98 return nil, err 99 } 100 act = &actionHandle{ 101 analyzer: a, 102 pkg: pkg, 103 } 104 var deps []*actionHandle 105 // Add a dependency on each required analyzers. 106 for _, req := range a.Requires { 107 reqActionHandle, err := s.actionHandle(ctx, id, req) 108 if err != nil { 109 return nil, err 110 } 111 deps = append(deps, reqActionHandle) 112 } 113 114 // TODO(golang/go#35089): Re-enable this when we doesn't use ParseExported 115 // mode for dependencies. In the meantime, disable analysis for dependencies, 116 // since we don't get anything useful out of it. 117 if false { 118 // An analysis that consumes/produces facts 119 // must run on the package's dependencies too. 120 if len(a.FactTypes) > 0 { 121 importIDs := make([]string, 0, len(ph.m.deps)) 122 for _, importID := range ph.m.deps { 123 importIDs = append(importIDs, string(importID)) 124 } 125 sort.Strings(importIDs) // for determinism 126 for _, importID := range importIDs { 127 depActionHandle, err := s.actionHandle(ctx, packageID(importID), a) 128 if err != nil { 129 return nil, err 130 } 131 deps = append(deps, depActionHandle) 132 } 133 } 134 } 135 136 h := s.generation.Bind(buildActionKey(a, ph), func(ctx context.Context, arg memoize.Arg) interface{} { 137 snapshot := arg.(*snapshot) 138 // Analyze dependencies first. 139 results, err := execAll(ctx, snapshot, deps) 140 if err != nil { 141 return &actionData{ 142 err: err, 143 } 144 } 145 return runAnalysis(ctx, snapshot, a, pkg, results) 146 }, nil) 147 act.handle = h 148 149 act = s.addActionHandle(act) 150 return act, nil 151 } 152 153 func (act *actionHandle) analyze(ctx context.Context, snapshot *snapshot) ([]*source.Error, interface{}, error) { 154 d, err := act.handle.Get(ctx, snapshot.generation, snapshot) 155 if err != nil { 156 return nil, nil, err 157 } 158 data, ok := d.(*actionData) 159 if !ok { 160 return nil, nil, errors.Errorf("unexpected type for %s:%s", act.pkg.ID(), act.analyzer.Name) 161 } 162 if data == nil { 163 return nil, nil, errors.Errorf("unexpected nil analysis for %s:%s", act.pkg.ID(), act.analyzer.Name) 164 } 165 return data.diagnostics, data.result, data.err 166 } 167 168 func buildActionKey(a *analysis.Analyzer, ph *packageHandle) actionHandleKey { 169 return actionHandleKey(hashContents([]byte(fmt.Sprintf("%p %s", a, string(ph.key))))) 170 } 171 172 func (act *actionHandle) String() string { 173 return fmt.Sprintf("%s@%s", act.analyzer, act.pkg.PkgPath()) 174 } 175 176 func execAll(ctx context.Context, snapshot *snapshot, actions []*actionHandle) (map[*actionHandle]*actionData, error) { 177 var mu sync.Mutex 178 results := make(map[*actionHandle]*actionData) 179 180 g, ctx := errgroup.WithContext(ctx) 181 for _, act := range actions { 182 act := act 183 g.Go(func() error { 184 v, err := act.handle.Get(ctx, snapshot.generation, snapshot) 185 if err != nil { 186 return err 187 } 188 data, ok := v.(*actionData) 189 if !ok { 190 return errors.Errorf("unexpected type for %s: %T", act, v) 191 } 192 193 mu.Lock() 194 defer mu.Unlock() 195 results[act] = data 196 197 return nil 198 }) 199 } 200 return results, g.Wait() 201 } 202 203 func runAnalysis(ctx context.Context, snapshot *snapshot, analyzer *analysis.Analyzer, pkg *pkg, deps map[*actionHandle]*actionData) (data *actionData) { 204 data = &actionData{ 205 objectFacts: make(map[objectFactKey]analysis.Fact), 206 packageFacts: make(map[packageFactKey]analysis.Fact), 207 } 208 defer func() { 209 if r := recover(); r != nil { 210 event.Log(ctx, fmt.Sprintf("analysis panicked: %s", r), tag.Package.Of(pkg.PkgPath())) 211 data.err = errors.Errorf("analysis %s for package %s panicked: %v", analyzer.Name, pkg.PkgPath(), r) 212 } 213 }() 214 215 // Plumb the output values of the dependencies 216 // into the inputs of this action. Also facts. 217 inputs := make(map[*analysis.Analyzer]interface{}) 218 219 for depHandle, depData := range deps { 220 if depHandle.pkg == pkg { 221 // Same package, different analysis (horizontal edge): 222 // in-memory outputs of prerequisite analyzers 223 // become inputs to this analysis pass. 224 inputs[depHandle.analyzer] = depData.result 225 } else if depHandle.analyzer == analyzer { // (always true) 226 // Same analysis, different package (vertical edge): 227 // serialized facts produced by prerequisite analysis 228 // become available to this analysis pass. 229 for key, fact := range depData.objectFacts { 230 // Filter out facts related to objects 231 // that are irrelevant downstream 232 // (equivalently: not in the compiler export data). 233 if !exportedFrom(key.obj, depHandle.pkg.types) { 234 continue 235 } 236 data.objectFacts[key] = fact 237 } 238 for key, fact := range depData.packageFacts { 239 // TODO: filter out facts that belong to 240 // packages not mentioned in the export data 241 // to prevent side channels. 242 243 data.packageFacts[key] = fact 244 } 245 } 246 } 247 248 var syntax []*ast.File 249 for _, cgf := range pkg.compiledGoFiles { 250 syntax = append(syntax, cgf.File) 251 } 252 253 var diagnostics []*analysis.Diagnostic 254 255 // Run the analysis. 256 pass := &analysis.Pass{ 257 Analyzer: analyzer, 258 Fset: snapshot.view.session.cache.fset, 259 Files: syntax, 260 Pkg: pkg.GetTypes(), 261 TypesInfo: pkg.GetTypesInfo(), 262 TypesSizes: pkg.GetTypesSizes(), 263 ResultOf: inputs, 264 Report: func(d analysis.Diagnostic) { 265 // Prefix the diagnostic category with the analyzer's name. 266 if d.Category == "" { 267 d.Category = analyzer.Name 268 } else { 269 d.Category = analyzer.Name + "." + d.Category 270 } 271 diagnostics = append(diagnostics, &d) 272 }, 273 ImportObjectFact: func(obj types.Object, ptr analysis.Fact) bool { 274 if obj == nil { 275 panic("nil object") 276 } 277 key := objectFactKey{obj, factType(ptr)} 278 279 if v, ok := data.objectFacts[key]; ok { 280 reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem()) 281 return true 282 } 283 return false 284 }, 285 ExportObjectFact: func(obj types.Object, fact analysis.Fact) { 286 if obj.Pkg() != pkg.types { 287 panic(fmt.Sprintf("internal error: in analysis %s of package %s: Fact.Set(%s, %T): can't set facts on objects belonging another package", 288 analyzer, pkg.ID(), obj, fact)) 289 } 290 key := objectFactKey{obj, factType(fact)} 291 data.objectFacts[key] = fact // clobber any existing entry 292 }, 293 ImportPackageFact: func(pkg *types.Package, ptr analysis.Fact) bool { 294 if pkg == nil { 295 panic("nil package") 296 } 297 key := packageFactKey{pkg, factType(ptr)} 298 if v, ok := data.packageFacts[key]; ok { 299 reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem()) 300 return true 301 } 302 return false 303 }, 304 ExportPackageFact: func(fact analysis.Fact) { 305 key := packageFactKey{pkg.types, factType(fact)} 306 data.packageFacts[key] = fact // clobber any existing entry 307 }, 308 AllObjectFacts: func() []analysis.ObjectFact { 309 facts := make([]analysis.ObjectFact, 0, len(data.objectFacts)) 310 for k := range data.objectFacts { 311 facts = append(facts, analysis.ObjectFact{Object: k.obj, Fact: data.objectFacts[k]}) 312 } 313 return facts 314 }, 315 AllPackageFacts: func() []analysis.PackageFact { 316 facts := make([]analysis.PackageFact, 0, len(data.packageFacts)) 317 for k := range data.packageFacts { 318 facts = append(facts, analysis.PackageFact{Package: k.pkg, Fact: data.packageFacts[k]}) 319 } 320 return facts 321 }, 322 } 323 analysisinternal.SetTypeErrors(pass, pkg.typeErrors) 324 325 if pkg.IsIllTyped() { 326 data.err = errors.Errorf("analysis skipped due to errors in package: %v", pkg.GetErrors()) 327 return data 328 } 329 data.result, data.err = pass.Analyzer.Run(pass) 330 if data.err != nil { 331 return data 332 } 333 334 if got, want := reflect.TypeOf(data.result), pass.Analyzer.ResultType; got != want { 335 data.err = errors.Errorf( 336 "internal error: on package %s, analyzer %s returned a result of type %v, but declared ResultType %v", 337 pass.Pkg.Path(), pass.Analyzer, got, want) 338 return data 339 } 340 341 // disallow calls after Run 342 pass.ExportObjectFact = func(obj types.Object, fact analysis.Fact) { 343 panic(fmt.Sprintf("%s:%s: Pass.ExportObjectFact(%s, %T) called after Run", analyzer.Name, pkg.PkgPath(), obj, fact)) 344 } 345 pass.ExportPackageFact = func(fact analysis.Fact) { 346 panic(fmt.Sprintf("%s:%s: Pass.ExportPackageFact(%T) called after Run", analyzer.Name, pkg.PkgPath(), fact)) 347 } 348 349 for _, diag := range diagnostics { 350 srcErr, err := sourceError(ctx, snapshot, pkg, diag) 351 if err != nil { 352 event.Error(ctx, "unable to compute analysis error position", err, tag.Category.Of(diag.Category), tag.Package.Of(pkg.ID())) 353 continue 354 } 355 if ctx.Err() != nil { 356 data.err = ctx.Err() 357 return data 358 } 359 data.diagnostics = append(data.diagnostics, srcErr) 360 } 361 return data 362 } 363 364 // exportedFrom reports whether obj may be visible to a package that imports pkg. 365 // This includes not just the exported members of pkg, but also unexported 366 // constants, types, fields, and methods, perhaps belonging to oether packages, 367 // that find there way into the API. 368 // This is an overapproximation of the more accurate approach used by 369 // gc export data, which walks the type graph, but it's much simpler. 370 // 371 // TODO(adonovan): do more accurate filtering by walking the type graph. 372 func exportedFrom(obj types.Object, pkg *types.Package) bool { 373 switch obj := obj.(type) { 374 case *types.Func: 375 return obj.Exported() && obj.Pkg() == pkg || 376 obj.Type().(*types.Signature).Recv() != nil 377 case *types.Var: 378 return obj.Exported() && obj.Pkg() == pkg || 379 obj.IsField() 380 case *types.TypeName, *types.Const: 381 return true 382 } 383 return false // Nil, Builtin, Label, or PkgName 384 } 385 386 func factType(fact analysis.Fact) reflect.Type { 387 t := reflect.TypeOf(fact) 388 if t.Kind() != reflect.Ptr { 389 panic(fmt.Sprintf("invalid Fact type: got %T, want pointer", t)) 390 } 391 return t 392 }