| /* |
| * Copyright 2016 The Kythe Authors. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package indexer |
| |
| import ( |
| "context" |
| "fmt" |
| "go/ast" |
| "go/token" |
| "go/types" |
| "log" |
| "net/url" |
| "path" |
| "strconv" |
| "strings" |
| |
| "kythe.io/kythe/go/extractors/govname" |
| "kythe.io/kythe/go/util/metadata" |
| "kythe.io/kythe/go/util/schema/edges" |
| "kythe.io/kythe/go/util/schema/facts" |
| "kythe.io/kythe/go/util/schema/nodes" |
| |
| "github.com/golang/protobuf/proto" |
| "golang.org/x/tools/go/types/typeutil" |
| |
| cpb "kythe.io/kythe/proto/common_go_proto" |
| spb "kythe.io/kythe/proto/storage_go_proto" |
| ) |
| |
| // EmitOptions control the behaviour of the Emit function. A nil options |
| // pointer provides default values. |
| type EmitOptions struct { |
| // If true, emit nodes for standard library packages when they are first |
| // encountered. This is helpful if you want to index a package in isolation |
| // where data for the standard library are not available. |
| EmitStandardLibs bool |
| |
| // If true, emit code facts containing MarkedSource messages. |
| EmitMarkedSource bool |
| |
| // If true, emit linkages specified by metadata rules. |
| EmitLinkages bool |
| |
| // If set, use this as the base URL for links to godoc. The import path is |
| // appended to the path of this URL to obtain the target URL to link to. |
| DocBase *url.URL |
| } |
| |
| func (e *EmitOptions) emitMarkedSource() bool { |
| if e == nil { |
| return false |
| } |
| return e.EmitMarkedSource |
| } |
| |
| // shouldEmit reports whether the indexer should emit a node for the given |
| // vname. Presently this is true if vname denotes a standard library and the |
| // corresponding option is enabled. |
| func (e *EmitOptions) shouldEmit(vname *spb.VName) bool { |
| return e != nil && e.EmitStandardLibs && govname.IsStandardLibrary(vname) |
| } |
| |
| // docURL returns a documentation URL for the specified package, if one is |
| // specified by the options, or "" if not. |
| func (e *EmitOptions) docURL(pi *PackageInfo) string { |
| if e != nil && e.DocBase != nil { |
| u := *e.DocBase |
| u.Path = path.Join(u.Path, pi.ImportPath) |
| return u.String() |
| } |
| return "" |
| } |
| |
| // An impl records that a type A implements an interface B. |
| type impl struct{ A, B types.Object } |
| |
| // Emit generates Kythe facts and edges to represent pi, and writes them to |
| // sink. In case of errors, processing continues as far as possible before the |
| // first error encountered is reported. |
| func (pi *PackageInfo) Emit(ctx context.Context, sink Sink, opts *EmitOptions) error { |
| e := &emitter{ |
| ctx: ctx, |
| pi: pi, |
| sink: sink, |
| opts: opts, |
| impl: make(map[impl]struct{}), |
| anchored: make(map[ast.Node]struct{}), |
| } |
| |
| // Emit a node to represent the package as a whole. |
| e.writeFact(pi.VName, facts.NodeKind, nodes.Package) |
| if url := e.opts.docURL(pi); url != "" { |
| e.writeFact(pi.VName, facts.DocURI, url) |
| } |
| e.emitPackageMarkedSource(pi) |
| |
| // Emit facts for all the source files claimed by this package. |
| for file, text := range pi.SourceText { |
| vname := pi.FileVName(file) |
| e.writeFact(vname, facts.NodeKind, nodes.File) |
| e.writeFact(vname, facts.Text, text) |
| // All Go source files are encoded as UTF-8, which is the default. |
| |
| e.writeEdge(vname, pi.VName, edges.ChildOf) |
| } |
| |
| // Traverse the AST of each file in the package for xref entries. |
| for _, file := range pi.Files { |
| e.writeDoc(file.Doc, pi.VName) // capture package comments |
| e.writeRef(file.Name, pi.VName, edges.DefinesBinding) // define a binding for the package |
| ast.Walk(newASTVisitor(func(node ast.Node, stack stackFunc) bool { |
| switch n := node.(type) { |
| case *ast.Ident: |
| e.visitIdent(n, stack) |
| case *ast.FuncDecl: |
| e.visitFuncDecl(n, stack) |
| case *ast.FuncLit: |
| e.visitFuncLit(n, stack) |
| case *ast.ValueSpec: |
| e.visitValueSpec(n, stack) |
| case *ast.TypeSpec: |
| e.visitTypeSpec(n, stack) |
| case *ast.ImportSpec: |
| e.visitImportSpec(n, stack) |
| case *ast.AssignStmt: |
| e.visitAssignStmt(n, stack) |
| case *ast.RangeStmt: |
| e.visitRangeStmt(n, stack) |
| case *ast.CompositeLit: |
| e.visitCompositeLit(n, stack) |
| } |
| return true |
| }), file) |
| } |
| |
| // Emit edges from each named type to the interface types it satisfies, for |
| // those interface types that are known to this compiltion. |
| e.emitSatisfactions() |
| |
| // TODO(fromberger): Add diagnostics for type-checker errors. |
| for _, err := range pi.Errors { |
| log.Printf("WARNING: Type resolution error: %v", err) |
| } |
| return e.firstErr |
| } |
| |
| type emitter struct { |
| ctx context.Context |
| pi *PackageInfo |
| sink Sink |
| opts *EmitOptions |
| impl map[impl]struct{} // see checkImplements |
| rmap map[*ast.File]map[int]metadata.Rules // see applyRules |
| anchored map[ast.Node]struct{} // see writeAnchor |
| firstErr error |
| } |
| |
| // visitIdent handles referring identifiers. Declaring identifiers are handled |
| // as part of their parent syntax. |
| func (e *emitter) visitIdent(id *ast.Ident, stack stackFunc) { |
| obj := e.pi.Info.Uses[id] |
| if obj == nil { |
| // Defining identifiers are handled by their parent nodes. |
| return |
| } |
| |
| target := e.pi.ObjectVName(obj) |
| if target == nil { |
| // This should not happen in well-formed packages, but can if the |
| // extractor gets confused. Avoid emitting confusing references in such |
| // cases. Note that in this case we need to emit a fresh anchor, since |
| // we aren't otherwise emitting a reference. |
| e.writeNodeDiagnostic(id, diagnostic{ |
| Message: fmt.Sprintf("Unable to identify the package for %q", id.Name), |
| }) |
| return |
| } |
| e.writeRef(id, target, edges.Ref) |
| if call, ok := isCall(id, obj, stack); ok { |
| callAnchor := e.writeRef(call, target, edges.RefCall) |
| |
| // Paint an edge to the function blamed for the call, or if there is |
| // none then to the package initializer. |
| e.writeEdge(callAnchor, e.callContext(stack).vname, edges.ChildOf) |
| } |
| } |
| |
| // visitFuncDecl handles function and method declarations and their parameters. |
| func (e *emitter) visitFuncDecl(decl *ast.FuncDecl, stack stackFunc) { |
| info := &funcInfo{vname: new(spb.VName)} |
| e.pi.function[decl] = info |
| |
| // Get the type of this function, even if its name is blank. |
| obj, _ := e.pi.Info.Defs[decl.Name].(*types.Func) |
| if obj == nil { |
| return // a redefinition, for example |
| } |
| |
| // Special case: There may be multiple package-level init functions, so |
| // override the normal signature generation to include a discriminator. |
| if decl.Recv == nil && obj.Name() == "init" { |
| e.pi.numInits++ |
| e.pi.sigs[obj] = fmt.Sprintf("%s#%d", e.pi.Signature(obj), e.pi.numInits) |
| } |
| |
| info.vname = e.mustWriteBinding(decl.Name, nodes.Function, nil) |
| e.writeDef(decl, info.vname) |
| e.writeDoc(decl.Doc, info.vname) |
| |
| // For concrete methods: Emit the receiver if named, and connect the method |
| // to its declaring type. |
| sig := obj.Type().(*types.Signature) |
| if sig.Recv() != nil { |
| // The receiver is treated as parameter 0. |
| if names := decl.Recv.List[0].Names; names != nil { |
| if recv := e.writeBinding(names[0], nodes.Variable, info.vname); recv != nil { |
| e.writeEdge(info.vname, recv, edges.ParamIndex(0)) |
| } |
| } |
| |
| // The method should be a child of its (named) enclosing type. |
| if named, _ := deref(sig.Recv().Type()).(*types.Named); named != nil { |
| base := e.pi.ObjectVName(named.Obj()) |
| e.writeEdge(info.vname, base, edges.ChildOf) |
| } |
| } |
| e.emitParameters(decl.Type, sig, info) |
| } |
| |
| // emitTApp emits a tapp node and returns its VName. The new tapp is emitted |
| // with given constructor and parameters. The constructor's kind is also |
| // emitted if this is the first time seeing it. |
| func (e *emitter) emitTApp(ms *cpb.MarkedSource, ctorKind string, ctor *spb.VName, params ...*spb.VName) *spb.VName { |
| if e.pi.typeEmitted.Add(ctor.Signature) { |
| e.writeFact(ctor, facts.NodeKind, ctorKind) |
| if ctorKind == nodes.TBuiltin { |
| e.emitBuiltinMarkedSource(ctor) |
| } |
| } |
| components := []interface{}{ctor} |
| for _, p := range params { |
| components = append(components, p) |
| } |
| v := &spb.VName{Language: govname.Language, Signature: hashSignature(components)} |
| if e.pi.typeEmitted.Add(v.Signature) { |
| e.writeFact(v, facts.NodeKind, nodes.TApp) |
| e.writeEdge(v, ctor, edges.ParamIndex(0)) |
| for i, p := range params { |
| e.writeEdge(v, p, edges.ParamIndex(i+1)) |
| } |
| if ms != nil && e.opts.emitMarkedSource() { |
| e.emitCode(v, ms) |
| } |
| } |
| return v |
| } |
| |
| // emitType emits the type as a node and returns its VName. VNames are cached |
| // so the type nodes are only emitted the first time they are seen. |
| func (e *emitter) emitType(typ types.Type) *spb.VName { |
| v, ok := e.pi.typeVName[typ] |
| if ok { |
| return v |
| } |
| |
| switch typ := typ.(type) { |
| case *types.Named: |
| v = e.pi.ObjectVName(typ.Obj()) |
| case *types.Basic: |
| v = govname.BasicType(typ) |
| if e.pi.typeEmitted.Add(v.Signature) { |
| e.writeFact(v, facts.NodeKind, nodes.TBuiltin) |
| e.emitBuiltinMarkedSource(v) |
| } |
| case *types.Array: |
| v = e.emitTApp(arrayTAppMS(typ.Len()), nodes.TBuiltin, govname.ArrayConstructorType(typ.Len()), e.emitType(typ.Elem())) |
| case *types.Slice: |
| v = e.emitTApp(sliceTAppMS, nodes.TBuiltin, govname.SliceConstructorType(), e.emitType(typ.Elem())) |
| case *types.Pointer: |
| v = e.emitTApp(pointerTAppMS, nodes.TBuiltin, govname.PointerConstructorType(), e.emitType(typ.Elem())) |
| case *types.Chan: |
| v = e.emitTApp(chanTAppMS(typ.Dir()), nodes.TBuiltin, govname.ChanConstructorType(typ.Dir()), e.emitType(typ.Elem())) |
| case *types.Map: |
| v = e.emitTApp(mapTAppMS, nodes.TBuiltin, govname.MapConstructorType(), e.emitType(typ.Key()), e.emitType(typ.Elem())) |
| case *types.Tuple: // function return types |
| v = e.emitTApp(tupleTAppMS, nodes.TBuiltin, govname.TupleConstructorType(), e.visitTuple(typ)...) |
| case *types.Signature: // function types |
| ms := &cpb.MarkedSource{ |
| Kind: cpb.MarkedSource_TYPE, |
| Child: []*cpb.MarkedSource{{ |
| Kind: cpb.MarkedSource_PARAMETER_LOOKUP_BY_PARAM, |
| LookupIndex: 3, |
| PreText: "func(", |
| PostChildText: ", ", |
| PostText: ")", |
| }}, |
| } |
| |
| params := e.visitTuple(typ.Params()) |
| if typ.Variadic() && len(params) > 0 { |
| // Convert last parameter type from slice type to variadic type. |
| last := len(params) - 1 |
| if slice, ok := typ.Params().At(last).Type().(*types.Slice); ok { |
| params[last] = e.emitTApp(variadicTAppMS, nodes.TBuiltin, govname.VariadicConstructorType(), e.emitType(slice.Elem())) |
| } |
| } |
| |
| var ret *spb.VName |
| if typ.Results().Len() == 1 { |
| ret = e.emitType(typ.Results().At(0).Type()) |
| } else { |
| ret = e.emitType(typ.Results()) |
| } |
| if typ.Results().Len() != 0 { |
| ms.Child = append(ms.Child, &cpb.MarkedSource{ |
| Kind: cpb.MarkedSource_BOX, |
| PreText: " ", |
| Child: []*cpb.MarkedSource{{ |
| Kind: cpb.MarkedSource_LOOKUP_BY_PARAM, |
| LookupIndex: 1, |
| }}, |
| }) |
| } |
| |
| var recv *spb.VName |
| if r := typ.Recv(); r != nil { |
| recv = e.emitType(r.Type()) |
| ms.Child = append([]*cpb.MarkedSource{{ |
| Kind: cpb.MarkedSource_BOX, |
| PreText: "(", |
| PostText: ") ", |
| Child: []*cpb.MarkedSource{{ |
| Kind: cpb.MarkedSource_LOOKUP_BY_PARAM, |
| LookupIndex: 2, |
| }}, |
| }}, ms.Child...) |
| } else { |
| recv = e.emitType(types.NewTuple()) |
| } |
| |
| v = e.emitTApp(ms, nodes.TBuiltin, govname.FunctionConstructorType(), |
| append([]*spb.VName{ret, recv}, params...)...) |
| case *types.Interface: |
| v = &spb.VName{Language: govname.Language, Signature: hashSignature(typ)} |
| if e.pi.typeEmitted.Add(v.Signature) { |
| e.writeFact(v, facts.NodeKind, nodes.Interface) |
| if e.opts.emitMarkedSource() { |
| e.emitCode(v, &cpb.MarkedSource{ |
| Kind: cpb.MarkedSource_TYPE, |
| PreText: typ.String(), |
| }) |
| } |
| } |
| case *types.Struct: |
| v = &spb.VName{Language: govname.Language, Signature: hashSignature(typ)} |
| if e.pi.typeEmitted.Add(v.Signature) { |
| e.writeFact(v, facts.NodeKind, nodes.Record) |
| if e.opts.emitMarkedSource() { |
| e.emitCode(v, &cpb.MarkedSource{ |
| Kind: cpb.MarkedSource_TYPE, |
| PreText: typ.String(), |
| }) |
| } |
| } |
| default: |
| log.Printf("WARNING: unknown type %T: %+v", typ, typ) |
| } |
| |
| e.pi.typeVName[typ] = v |
| return v |
| } |
| |
| func (e *emitter) emitTypeOf(expr ast.Expr) *spb.VName { return e.emitType(e.pi.Info.TypeOf(expr)) } |
| |
| func (e *emitter) visitTuple(t *types.Tuple) []*spb.VName { |
| size := t.Len() |
| ts := make([]*spb.VName, size) |
| for i := 0; i < size; i++ { |
| ts[i] = e.emitType(t.At(i).Type()) |
| } |
| return ts |
| } |
| |
| // visitFuncLit handles function literals and their parameters. The signature |
| // for a function literal is named relative to the signature of its parent |
| // function, or the file scope if the literal is at the top level. |
| func (e *emitter) visitFuncLit(flit *ast.FuncLit, stack stackFunc) { |
| fi := e.callContext(stack) |
| if fi == nil { |
| log.Panic("Function literal without a context: ", flit) |
| } |
| |
| fi.numAnons++ |
| info := &funcInfo{vname: proto.Clone(fi.vname).(*spb.VName)} |
| info.vname.Language = govname.Language |
| info.vname.Signature += "$" + strconv.Itoa(fi.numAnons) |
| e.pi.function[flit] = info |
| e.writeDef(flit, info.vname) |
| e.writeFact(info.vname, facts.NodeKind, nodes.Function) |
| |
| if sig, ok := e.pi.Info.Types[flit].Type.(*types.Signature); ok { |
| e.emitParameters(flit.Type, sig, info) |
| } |
| } |
| |
| // visitValueSpec handles variable and constant bindings. |
| func (e *emitter) visitValueSpec(spec *ast.ValueSpec, stack stackFunc) { |
| kind := nodes.Variable |
| if stack(1).(*ast.GenDecl).Tok == token.CONST { |
| kind = nodes.Constant |
| } |
| doc := specComment(spec, stack) |
| for _, id := range spec.Names { |
| target := e.writeBinding(id, kind, e.nameContext(stack)) |
| if target == nil { |
| continue // type error (reported elsewhere) |
| } |
| e.writeDoc(doc, target) |
| } |
| |
| // Handle members of anonymous types declared in situ. |
| if spec.Type != nil { |
| e.emitAnonMembers(spec.Type) |
| } |
| for _, v := range spec.Values { |
| if lit, ok := v.(*ast.CompositeLit); ok { |
| e.emitAnonMembers(lit.Type) |
| } |
| } |
| } |
| |
| // visitTypeSpec handles type declarations, including the bindings for fields |
| // of struct types and methods of interfaces. |
| func (e *emitter) visitTypeSpec(spec *ast.TypeSpec, stack stackFunc) { |
| obj := e.pi.Info.Defs[spec.Name] |
| if obj == nil { |
| return // type error |
| } |
| target := e.mustWriteBinding(spec.Name, "", e.nameContext(stack)) |
| e.writeDef(spec, target) |
| e.writeDoc(specComment(spec, stack), target) |
| |
| // Emit type-specific structure. |
| switch t := obj.Type().Underlying().(type) { |
| case *types.Struct: |
| e.writeFact(target, facts.NodeKind, nodes.Record) |
| e.writeFact(target, facts.Subkind, nodes.Struct) |
| // Add parent edges for all fields, including promoted ones. |
| for i, n := 0, t.NumFields(); i < n; i++ { |
| e.writeEdge(e.pi.ObjectVName(t.Field(i)), target, edges.ChildOf) |
| } |
| |
| // Add bindings for the explicitly-named fields in this declaration. |
| // Parent edges were already added, so skip them here. |
| if st, ok := spec.Type.(*ast.StructType); ok { |
| mapFields(st.Fields, func(i int, id *ast.Ident) { |
| target := e.writeVarBinding(id, nodes.Field, nil) |
| f := st.Fields.List[i] |
| e.writeDoc(f.Doc, target) |
| e.emitAnonMembers(f.Type) |
| }) |
| |
| // Handle anonymous fields. Such fields behave as if they were |
| // named by the base identifier of their type. |
| for _, field := range st.Fields.List { |
| if len(field.Names) != 0 { |
| continue // already handled above |
| } |
| id, ok := e.pi.findFieldName(field.Type) |
| obj := e.pi.Info.Defs[id] |
| if ok && obj != nil { |
| // Don't write a fresh anchor here; we already wrote one as |
| // part of the ref to the type, and we don't want duplicate |
| // outputs. |
| anchor := e.pi.AnchorVName(e.pi.Span(id)) |
| target := e.pi.ObjectVName(obj) |
| e.writeEdge(anchor, target, edges.DefinesBinding) |
| e.writeFact(target, facts.NodeKind, nodes.Variable) |
| e.writeFact(target, facts.Subkind, nodes.Field) |
| e.writeDoc(field.Doc, target) |
| } |
| } |
| } |
| |
| case *types.Interface: |
| e.writeFact(target, facts.NodeKind, nodes.Interface) |
| // Add parent edges for all methods, including inherited ones. |
| for i, n := 0, t.NumMethods(); i < n; i++ { |
| e.writeEdge(e.pi.ObjectVName(t.Method(i)), target, edges.ChildOf) |
| } |
| // Mark the interface as an extension of any embedded interfaces. |
| for i, n := 0, t.NumEmbeddeds(); i < n; i++ { |
| if eobj := t.Embedded(i).Obj(); e.checkImplements(obj, eobj) { |
| e.writeEdge(target, e.pi.ObjectVName(eobj), edges.Extends) |
| } |
| } |
| |
| // Add bindings for the explicitly-named methods in this declaration. |
| // Parent edges were already added, so skip them here. |
| if it, ok := spec.Type.(*ast.InterfaceType); ok { |
| mapFields(it.Methods, func(_ int, id *ast.Ident) { |
| e.writeBinding(id, nodes.Function, nil) |
| }) |
| } |
| |
| default: |
| // We model a newtype form whose underlying type is not already a |
| // struct (e.g., "type Foo int") as if it were a record with a single |
| // unexported field of the underlying type. That is not really what Go |
| // does, but it is close enough for the graph model to work. Since |
| // there is no actual field declaration, however, we don't emit that. |
| e.writeFact(target, facts.NodeKind, nodes.Record) |
| e.writeFact(target, facts.Subkind, nodes.Type) |
| } |
| } |
| |
| // visitImportSpec handles references to imported packages. |
| func (e *emitter) visitImportSpec(spec *ast.ImportSpec, stack stackFunc) { |
| ipath, _ := strconv.Unquote(spec.Path.Value) |
| if vPath, ok := e.pi.Vendored[ipath]; ok { |
| ipath = vPath |
| } |
| |
| pkg := e.pi.Dependencies[ipath] |
| target := e.pi.PackageVName[pkg] |
| if target == nil { |
| log.Printf("Unable to resolve import path %q", ipath) |
| return |
| } |
| |
| e.writeRef(spec.Path, target, edges.RefImports) |
| if e.opts.shouldEmit(target) && !e.pi.standardLib.Contains(ipath) { |
| e.writeFact(target, facts.NodeKind, nodes.Package) |
| e.pi.standardLib.Add(ipath) |
| } |
| } |
| |
| // visitAssignStmt handles bindings introduced by short-declaration syntax in |
| // assignment statments, e.g., "x, y := 1, 2". |
| func (e *emitter) visitAssignStmt(stmt *ast.AssignStmt, stack stackFunc) { |
| if stmt.Tok != token.DEFINE { |
| return // no new bindings in this statement |
| } |
| |
| // Not all the names in a short declaration assignment may be defined here. |
| // We only add bindings for newly-defined ones, of which there must be at |
| // least one in a well-typed program. |
| up := e.nameContext(stack) |
| for _, expr := range stmt.Lhs { |
| if id, _ := expr.(*ast.Ident); id != nil { |
| // Add a binding only if this is the definition site for the name. |
| if obj := e.pi.Info.Defs[id]; obj != nil && obj.Pos() == id.Pos() { |
| e.mustWriteBinding(id, nodes.Variable, up) |
| } |
| } |
| } |
| |
| // TODO(fromberger): Add information about initializers where available. |
| } |
| |
| // visitRangeStmt handles the bindings introduced by a for ... range statement. |
| func (e *emitter) visitRangeStmt(stmt *ast.RangeStmt, stack stackFunc) { |
| if stmt.Tok != token.DEFINE { |
| return // no new bindings in this statement |
| } |
| |
| // In a well-typed program, the key and value will always be identifiers. |
| up := e.nameContext(stack) |
| if key, _ := stmt.Key.(*ast.Ident); key != nil { |
| e.writeBinding(key, nodes.Variable, up) |
| } |
| if val, _ := stmt.Value.(*ast.Ident); val != nil { |
| e.writeBinding(val, nodes.Variable, up) |
| } |
| } |
| |
| // visitCompositeLit handles references introduced by positional initializers |
| // in composite literals that construct (pointer to) struct values. Named |
| // initializers are handled separately. |
| func (e *emitter) visitCompositeLit(expr *ast.CompositeLit, stack stackFunc) { |
| if len(expr.Elts) == 0 { |
| return // no fields to initialize |
| } |
| |
| tv, ok := e.pi.Info.Types[expr] |
| if !ok { |
| log.Printf("WARNING: Unable to determine composite literal type (%s)", e.pi.FileSet.Position(expr.Pos())) |
| return |
| } |
| sv, ok := deref(tv.Type.Underlying()).(*types.Struct) |
| if !ok { |
| return // non-struct type, e.g. a slice; nothing to do here |
| } |
| |
| if n := sv.NumFields(); n < len(expr.Elts) { |
| // Embedded struct fields from an imported package may not appear in |
| // the list if the import did not succeed. To remain robust against |
| // such cases, don't try to read into the fields of a struct type if |
| // the counts don't line up. The information we emit will still be |
| // correct, we'll just miss some initializers. |
| log.Printf("ERROR: Struct has %d fields but %d initializers (skipping)", n, len(expr.Elts)) |
| return |
| } |
| for i, elt := range expr.Elts { |
| // The keys for key-value initializers are handled upstream of us, so |
| // we need only handle the values. But note that key-value initializers |
| // may not be in order, so we have to take care to get the right field. |
| // Positional fields must be in order, in well-formed code. |
| switch t := elt.(type) { |
| case *ast.KeyValueExpr: |
| f, ok := fieldIndex(t.Key, sv) |
| if !ok { |
| log.Printf("ERROR: Found no field index for %v (skipping)", t.Key) |
| continue |
| } |
| e.emitPosRef(t.Value, sv.Field(f), edges.RefInit) |
| default: |
| e.emitPosRef(t, sv.Field(i), edges.RefInit) |
| } |
| } |
| } |
| |
| // emitPosRef emits an anchor spanning loc, pointing to obj. |
| func (e *emitter) emitPosRef(loc ast.Node, obj types.Object, kind string) { |
| target := e.pi.ObjectVName(obj) |
| file, start, end := e.pi.Span(loc) |
| anchor := e.pi.AnchorVName(file, start, end) |
| e.writeAnchor(loc, anchor, start, end) |
| e.writeEdge(anchor, target, kind) |
| } |
| |
| // emitParameters emits parameter edges for the parameters of a function type, |
| // given the type signature and info of the enclosing declaration or function |
| // literal. |
| func (e *emitter) emitParameters(ftype *ast.FuncType, sig *types.Signature, info *funcInfo) { |
| paramIndex := 0 |
| |
| // If there is a receiver, it is treated as param.0. |
| if sig.Recv() != nil { |
| paramIndex++ |
| } |
| |
| // Emit bindings and parameter edges for the parameters. |
| mapFields(ftype.Params, func(i int, id *ast.Ident) { |
| if sig.Params().At(i) != nil { |
| if param := e.writeBinding(id, nodes.Variable, info.vname); param != nil { |
| e.writeEdge(info.vname, param, edges.ParamIndex(paramIndex)) |
| e.emitAnonMembers(ftype.Params.List[i].Type) |
| } |
| } |
| paramIndex++ |
| }) |
| // Emit bindings for any named result variables. |
| // Results are not considered parameters. |
| mapFields(ftype.Results, func(i int, id *ast.Ident) { |
| e.writeBinding(id, nodes.Variable, info.vname) |
| }) |
| } |
| |
| // emitAnonMembers checks whether expr denotes an anonymous struct or interface |
| // type, and if so emits bindings for its member fields/methods. The resulting |
| // members do not parent to the type, since it has no referential identity; but |
| // we do capture documentation in the unlikely event someone wrote any. |
| func (e *emitter) emitAnonMembers(expr ast.Expr) { |
| if st, ok := expr.(*ast.StructType); ok { |
| mapFields(st.Fields, func(i int, id *ast.Ident) { |
| target := e.writeVarBinding(id, nodes.Field, nil) // no parent |
| e.writeDoc(st.Fields.List[i].Doc, target) |
| }) |
| } else if it, ok := expr.(*ast.InterfaceType); ok { |
| mapFields(it.Methods, func(i int, id *ast.Ident) { |
| target := e.writeBinding(id, nodes.Function, nil) // no parent |
| e.writeDoc(it.Methods.List[i].Doc, target) |
| }) |
| } |
| } |
| |
| // An override represents the relationship that x overrides y. |
| type override struct { |
| x, y types.Object |
| } |
| |
| // overrides represents a set of override relationships we've already generated. |
| type overrides map[override]bool |
| |
| // seen reports whether an x overrides y was already cached, and if not adds it |
| // to the set. |
| func (o overrides) seen(x, y types.Object) bool { |
| ov := override{x: x, y: y} |
| ok := o[ov] |
| if !ok { |
| o[ov] = true |
| } |
| return ok |
| } |
| |
| // emitSatisfactions visits each named type known through the compilation being |
| // indexed, and emits edges connecting it to any known interfaces its method |
| // set satisfies. |
| func (e *emitter) emitSatisfactions() { |
| // Find the names of all defined types mentioned in this compilation. |
| var allNames []*types.TypeName |
| |
| // For the current source package, use all names, even local ones. |
| for _, obj := range e.pi.Info.Defs { |
| if obj, ok := obj.(*types.TypeName); ok { |
| if _, ok := obj.Type().(*types.Named); ok { |
| allNames = append(allNames, obj) |
| } |
| } |
| } |
| |
| // For dependencies, we only have access to package-level types, not those |
| // defined by inner scopes. |
| for _, pkg := range e.pi.Dependencies { |
| scope := pkg.Scope() |
| for _, name := range scope.Names() { |
| if obj, ok := scope.Lookup(name).(*types.TypeName); ok { |
| // Note that the names of some "named" types that are brought |
| // in from dependencies may not be known at this point -- the |
| // compiled package headers omit the names if they are not |
| // needed. Skip such cases, even though they would qualify if |
| // we had the source package. |
| if _, ok := obj.Type().(*types.Named); ok && obj.Name() != "" { |
| allNames = append(allNames, obj) |
| } |
| } |
| } |
| } |
| |
| // Cache the method set of each named type in this package. |
| var msets typeutil.MethodSetCache |
| // Cache the overrides we've noticed to avoid duplicate entries. |
| cache := make(overrides) |
| for _, xobj := range allNames { |
| if xobj.Pkg() != e.pi.Package { |
| continue // not from this package |
| } |
| |
| // Check whether x is a named type with methods; if not, skip it. |
| x := xobj.Type() |
| if len(typeutil.IntuitiveMethodSet(x, &msets)) == 0 { |
| continue // no methods to consider |
| } |
| |
| // N.B. This implementation is quadratic in the number of visible |
| // interfaces, but that's probably OK since are only considering a |
| // single compilation. |
| |
| // Check the method sets of both x and pointer-to-x for overrides. |
| xmset := msets.MethodSet(x) |
| pxmset := msets.MethodSet(types.NewPointer(x)) |
| |
| for _, yobj := range allNames { |
| if xobj == yobj { |
| continue |
| } |
| |
| y := yobj.Type() |
| ymset := msets.MethodSet(y) |
| |
| ifx, ify := isInterface(x), isInterface(y) |
| switch { |
| case ifx && ify && ymset.Len() > 0: |
| // x and y are both interfaces. Note that extension is handled |
| // elsewhere as part of the type spec for the interface. |
| if types.AssignableTo(x, y) { |
| e.writeSatisfies(xobj, yobj) |
| } |
| if types.AssignableTo(y, x) { |
| e.writeSatisfies(yobj, xobj) |
| } |
| |
| case ifx: |
| // y is a concrete type |
| pymset := msets.MethodSet(types.NewPointer(y)) |
| if types.AssignableTo(y, x) { |
| e.writeSatisfies(yobj, xobj) |
| e.emitOverrides(ymset, pymset, xmset, cache) |
| } else if py := types.NewPointer(y); types.AssignableTo(py, x) { |
| e.writeSatisfies(yobj, xobj) |
| e.emitOverrides(ymset, pymset, xmset, cache) |
| } |
| |
| case ify && ymset.Len() > 0: |
| // x is a concrete type |
| if types.AssignableTo(x, y) { |
| e.writeSatisfies(xobj, yobj) |
| e.emitOverrides(xmset, pxmset, ymset, cache) |
| } else if px := types.NewPointer(x); types.AssignableTo(px, y) { |
| e.writeSatisfies(xobj, yobj) |
| e.emitOverrides(xmset, pxmset, ymset, cache) |
| } |
| |
| default: |
| // Both x and y are concrete. |
| } |
| } |
| } |
| } |
| |
| // Add xm-(overrides)-ym for each concrete method xm with a corresponding |
| // abstract method ym. |
| func (e *emitter) emitOverrides(xmset, pxmset, ymset *types.MethodSet, cache overrides) { |
| for i, n := 0, ymset.Len(); i < n; i++ { |
| ym := ymset.At(i) |
| yobj := ym.Obj() |
| xm := xmset.Lookup(yobj.Pkg(), yobj.Name()) |
| if xm == nil { |
| if pxmset != nil { |
| xm = pxmset.Lookup(yobj.Pkg(), yobj.Name()) |
| } |
| if xm == nil { |
| continue // this method is not part of the interface we're probing |
| } |
| } |
| |
| xobj := xm.Obj() |
| if cache.seen(xobj, yobj) { |
| continue |
| } |
| |
| xvname := e.pi.ObjectVName(xobj) |
| yvname := e.pi.ObjectVName(yobj) |
| e.writeEdge(xvname, yvname, edges.Overrides) |
| |
| xt := e.emitType(xobj.Type()) |
| yt := e.emitType(yobj.Type()) |
| if e.pi.typeEmitted.Add(xt.Signature + "+" + yt.Signature) { |
| e.writeEdge(xt, yt, edges.Satisfies) |
| } |
| } |
| } |
| |
| func isInterface(typ types.Type) bool { _, ok := typ.Underlying().(*types.Interface); return ok } |
| |
| func (e *emitter) check(err error) { |
| if err != nil && e.firstErr == nil { |
| e.firstErr = err |
| log.Printf("ERROR indexing %q: %v", e.pi.ImportPath, err) |
| } |
| } |
| |
| func (e *emitter) checkImplements(src, tgt types.Object) bool { |
| i := impl{A: src, B: tgt} |
| if _, ok := e.impl[i]; ok { |
| return false |
| } |
| e.impl[i] = struct{}{} |
| return true |
| } |
| |
| func (e *emitter) writeSatisfies(src, tgt types.Object) { |
| if e.checkImplements(src, tgt) { |
| e.writeEdge(e.pi.ObjectVName(src), e.pi.ObjectVName(tgt), edges.Satisfies) |
| } |
| } |
| |
| func (e *emitter) writeFact(src *spb.VName, name, value string) { |
| e.check(e.sink.writeFact(e.ctx, src, name, value)) |
| } |
| |
| func (e *emitter) writeEdge(src, tgt *spb.VName, kind string) { |
| e.check(e.sink.writeEdge(e.ctx, src, tgt, kind)) |
| } |
| |
| func (e *emitter) writeAnchor(node ast.Node, src *spb.VName, start, end int) { |
| if _, ok := e.anchored[node]; ok { |
| return // this node already has an anchor |
| } |
| e.anchored[node] = struct{}{} |
| e.check(e.sink.writeAnchor(e.ctx, src, start, end)) |
| } |
| |
| func (e *emitter) writeDiagnostic(src *spb.VName, d diagnostic) { |
| e.check(e.sink.writeDiagnostic(e.ctx, src, d)) |
| } |
| |
| func (e *emitter) writeNodeDiagnostic(src ast.Node, d diagnostic) { |
| file, start, end := e.pi.Span(src) |
| anchor := e.pi.AnchorVName(file, start, end) |
| e.writeAnchor(src, anchor, start, end) |
| e.writeDiagnostic(anchor, d) |
| } |
| |
| // writeRef emits an anchor spanning origin and referring to target with an |
| // edge of the given kind. The vname of the anchor is returned. |
| func (e *emitter) writeRef(origin ast.Node, target *spb.VName, kind string) *spb.VName { |
| file, start, end := e.pi.Span(origin) |
| anchor := e.pi.AnchorVName(file, start, end) |
| e.writeAnchor(origin, anchor, start, end) |
| e.writeEdge(anchor, target, kind) |
| |
| // Check whether we are intended to emit metadata linkage edges, and if so, |
| // whether there are any to process. |
| e.applyRules(file, start, end, kind, func(rule metadata.Rule) { |
| if rule.Reverse { |
| e.writeEdge(rule.VName, target, rule.EdgeOut) |
| } else { |
| e.writeEdge(target, rule.VName, rule.EdgeOut) |
| } |
| }) |
| |
| return anchor |
| } |
| |
| // mustWriteBinding is as writeBinding, but panics if id does not resolve. Use |
| // this in cases where the object is known already to exist. |
| func (e *emitter) mustWriteBinding(id *ast.Ident, kind string, parent *spb.VName) *spb.VName { |
| if target := e.writeBinding(id, kind, parent); target != nil { |
| return target |
| } |
| panic("unresolved definition") // logged in writeBinding |
| } |
| |
| // writeVarBinding is as writeBinding, assuming the kind is "variable". |
| // If subkind != "", it is also emitted as a subkind. |
| func (e *emitter) writeVarBinding(id *ast.Ident, subkind string, parent *spb.VName) *spb.VName { |
| vname := e.writeBinding(id, nodes.Variable, parent) |
| if vname != nil && subkind != "" { |
| e.writeFact(vname, facts.Subkind, subkind) |
| } |
| return vname |
| } |
| |
| // writeBinding emits a node of the specified kind for the target of id. If |
| // the identifier is not "_", an anchor for a binding definition of the target |
| // is also emitted at id. If parent != nil, the target is also recorded as its |
| // child. The target vname is returned. |
| func (e *emitter) writeBinding(id *ast.Ident, kind string, parent *spb.VName) *spb.VName { |
| obj := e.pi.Info.Defs[id] |
| if obj == nil { |
| loc := e.pi.FileSet.Position(id.Pos()) |
| log.Printf("ERROR: Missing definition for id %q at %s", id.Name, loc) |
| return nil |
| } |
| target := e.pi.ObjectVName(obj) |
| if kind != "" { |
| e.writeFact(target, facts.NodeKind, kind) |
| } |
| if id.Name != "_" { |
| e.writeRef(id, target, edges.DefinesBinding) |
| } |
| if parent != nil { |
| e.writeEdge(target, parent, edges.ChildOf) |
| } |
| if e.opts.emitMarkedSource() { |
| e.emitCode(target, e.pi.MarkedSource(obj)) |
| } |
| e.writeEdge(target, e.emitTypeOf(id), edges.Typed) |
| return target |
| } |
| |
| // writeDef emits a spanning anchor and defines edge for the specified node. |
| // This function does not create the target node. |
| func (e *emitter) writeDef(node ast.Node, target *spb.VName) { e.writeRef(node, target, edges.Defines) } |
| |
| // writeDoc adds associations between comment groups and a documented node. |
| // It also handles marking deprecated facts on the target. |
| func (e *emitter) writeDoc(comments *ast.CommentGroup, target *spb.VName) { |
| if comments == nil || len(comments.List) == 0 || target == nil { |
| return |
| } |
| var lines []string |
| for _, comment := range comments.List { |
| lines = append(lines, trimComment(comment.Text)) |
| } |
| docNode := proto.Clone(target).(*spb.VName) |
| docNode.Signature += " doc" |
| e.writeFact(docNode, facts.NodeKind, nodes.Doc) |
| e.writeFact(docNode, facts.Text, escComment.Replace(strings.Join(lines, "\n"))) |
| e.writeEdge(docNode, target, edges.Documents) |
| e.emitDeprecation(target, lines) |
| } |
| |
| // emitDeprecation emits a deprecated fact for the specified target if the |
| // comment lines indicate it is deprecated per https://github.com/golang/go/wiki/Deprecated |
| func (e *emitter) emitDeprecation(target *spb.VName, lines []string) { |
| var deplines []string |
| for _, line := range lines { |
| if len(deplines) == 0 { |
| if msg := strings.TrimPrefix(line, "Deprecated:"); msg != line { |
| deplines = append(deplines, strings.TrimSpace(msg)) |
| } |
| } else if line == "" { |
| break |
| } else { |
| deplines = append(deplines, strings.TrimSpace(line)) |
| } |
| } |
| if len(deplines) > 0 { |
| e.writeFact(target, facts.Deprecated, strings.Join(deplines, " ")) |
| } |
| } |
| |
| // isCall reports whether id is a call to obj. This holds if id is in call |
| // position ("id(...") or is the RHS of a selector in call position |
| // ("x.id(...)"). If so, the nearest enclosing call expression is also |
| // returned. |
| // |
| // This will not match if there are redundant parentheses in the expression. |
| func isCall(id *ast.Ident, obj types.Object, stack stackFunc) (*ast.CallExpr, bool) { |
| if _, ok := obj.(*types.Func); ok { |
| if call, ok := stack(1).(*ast.CallExpr); ok && call.Fun == id { |
| return call, true // id(...) |
| } |
| if sel, ok := stack(1).(*ast.SelectorExpr); ok && sel.Sel == id { |
| if call, ok := stack(2).(*ast.CallExpr); ok && call.Fun == sel { |
| return call, true // x.id(...) |
| } |
| } |
| } |
| return nil, false |
| } |
| |
| // callContext returns funcInfo for the nearest enclosing parent function, not |
| // including the node itself, or the enclosing package initializer if the node |
| // is at the top level. |
| func (e *emitter) callContext(stack stackFunc) *funcInfo { |
| for i := 1; ; i++ { |
| switch p := stack(i).(type) { |
| case *ast.FuncDecl, *ast.FuncLit: |
| return e.pi.function[p] |
| case *ast.File: |
| fi := e.pi.packageInit[p] |
| if fi == nil { |
| // Lazily emit a virtual node to represent the static |
| // initializer for top-level expressions in this file of the |
| // package. We only do this if there are expressions that need |
| // to be initialized. |
| vname := proto.Clone(e.pi.VName).(*spb.VName) |
| vname.Signature += fmt.Sprintf(".<init>@%d", p.Package) |
| fi = &funcInfo{vname: vname} |
| e.pi.packageInit[p] = fi |
| e.writeFact(vname, facts.NodeKind, nodes.Function) |
| e.writeEdge(vname, e.pi.VName, edges.ChildOf) |
| |
| // The callgraph requires we provide the caller with a |
| // definition (http://www.kythe.io/docs/schema/callgraph.html). |
| // Since there is no location, attach it to the beginning of |
| // the file itself. |
| anchor := e.pi.AnchorVName(p, 0, 0) |
| e.check(e.sink.writeAnchor(e.ctx, anchor, 0, 0)) |
| e.writeEdge(anchor, vname, edges.Defines) |
| } |
| return fi |
| } |
| } |
| } |
| |
| // nameContext returns the vname for the nearest enclosing parent node, not |
| // including the node itself, or the enclosing package vname if the node is at |
| // the top level. |
| func (e *emitter) nameContext(stack stackFunc) *spb.VName { |
| if fi := e.callContext(stack); !e.pi.isPackageInit(fi) { |
| return fi.vname |
| } |
| return e.pi.VName |
| } |
| |
| // applyRules calls apply for each metadata rule matching the given combination |
| // of location and kind. |
| func (e *emitter) applyRules(file *ast.File, start, end int, kind string, apply func(r metadata.Rule)) { |
| if e.opts == nil || !e.opts.EmitLinkages { |
| return // nothing to do |
| } else if e.rmap == nil { |
| e.rmap = make(map[*ast.File]map[int]metadata.Rules) |
| } |
| |
| // Lazily populate a cache of file :: start :: rules mappings, so that we |
| // need only scan the rules coincident on the starting point of the range |
| // we care about. In almost all cases that will be just one, if any. |
| rules, ok := e.rmap[file] |
| if !ok { |
| rules = make(map[int]metadata.Rules) |
| for _, rule := range e.pi.Rules[file] { |
| rules[rule.Begin] = append(rules[rule.Begin], rule) |
| } |
| e.rmap[file] = rules |
| } |
| |
| for _, rule := range rules[start] { |
| if rule.End == end && rule.EdgeIn == kind { |
| apply(rule) |
| } |
| } |
| } |
| |
| // A visitFunc visits a node of the Go AST. The function can use stack to |
| // retrieve AST nodes on the path from the node up to the root. If the return |
| // value is true, the children of node are also visited; otherwise they are |
| // skipped. |
| type visitFunc func(node ast.Node, stack stackFunc) bool |
| |
| // A stackFunc returns the ith stack entry above of an AST node, where 0 |
| // denotes the node itself. If the ith entry does not exist, the function |
| // returns nil. |
| type stackFunc func(i int) ast.Node |
| |
| // astVisitor implements ast.Visitor, passing each visited node to a callback |
| // function. |
| type astVisitor struct { |
| stack []ast.Node |
| visit visitFunc |
| } |
| |
| func newASTVisitor(f visitFunc) ast.Visitor { return &astVisitor{visit: f} } |
| |
| // Visit implements the required method of the ast.Visitor interface. |
| func (w *astVisitor) Visit(node ast.Node) ast.Visitor { |
| if node == nil { |
| w.stack = w.stack[:len(w.stack)-1] // pop |
| return w |
| } |
| |
| w.stack = append(w.stack, node) // push |
| if !w.visit(node, w.parent) { |
| return nil |
| } |
| return w |
| } |
| |
| func (w *astVisitor) parent(i int) ast.Node { |
| if i >= len(w.stack) { |
| return nil |
| } |
| return w.stack[len(w.stack)-1-i] |
| } |
| |
| // deref returns the base type of T if it is a pointer, otherwise T itself. |
| func deref(T types.Type) types.Type { |
| if U, ok := T.Underlying().(*types.Pointer); ok { |
| return U.Elem() |
| } |
| return T |
| } |
| |
| // mapFields applies f to each identifier declared in fields. Each call to f |
| // is given the offset and the identifier. |
| func mapFields(fields *ast.FieldList, f func(i int, id *ast.Ident)) { |
| if fields == nil { |
| return |
| } |
| for i, field := range fields.List { |
| for _, id := range field.Names { |
| f(i, id) |
| } |
| } |
| } |
| |
| // fieldIndex reports whether sv has a field named by expr, which must be of |
| // type *ast.Ident, and returns its positional index if so. |
| // |
| // N.B. This is a linear scan, but the count of fields should almost always be |
| // small enough not to worry about it. |
| func fieldIndex(expr ast.Expr, sv *types.Struct) (int, bool) { |
| want := expr.(*ast.Ident).Name |
| for i := 0; i < sv.NumFields(); i++ { |
| if sv.Field(i).Name() == want { |
| return i, true |
| } |
| } |
| return -1, false |
| } |
| |
| var escComment = strings.NewReplacer("[", `\[`, "]", `\]`, `\`, `\\`) |
| |
| // trimComment removes the comment delimiters from a comment. For single-line |
| // comments, it also removes a single leading space, if present; for multi-line |
| // comments it discards leading and trailing whitespace. |
| func trimComment(text string) string { |
| if single := strings.TrimPrefix(text, "//"); single != text { |
| return strings.TrimPrefix(single, " ") |
| } |
| return strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(text, "/*"), "*/")) |
| } |
| |
| // specComment returns the innermost comment associated with spec, or nil. |
| func specComment(spec ast.Spec, stack stackFunc) *ast.CommentGroup { |
| var comment *ast.CommentGroup |
| switch t := spec.(type) { |
| case *ast.TypeSpec: |
| comment = t.Doc |
| case *ast.ValueSpec: |
| comment = t.Doc |
| case *ast.ImportSpec: |
| comment = t.Doc |
| } |
| if comment == nil { |
| if t, ok := stack(1).(*ast.GenDecl); ok { |
| return t.Doc |
| } |
| } |
| return comment |
| } |