kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/serving/explore/explore.go (about) 1 /* 2 * Copyright 2018 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // Package explore provides a high-performance table-based implementation of the 18 // ExploreService defined in kythe/proto/explore.proto. 19 // 20 // Table format: 21 // 22 // <parent ticket> -> srvpb.Relatives (children) 23 // <child ticket> -> srvpb.Relatives (parents) 24 // <called ticket> -> srvpb.Callgraph (callers) 25 // <calling ticket> -> srvpb.Callgraph (callees) 26 package explore // import "kythe.io/kythe/go/serving/explore" 27 28 import ( 29 "context" 30 "fmt" 31 32 "kythe.io/kythe/go/storage/table" 33 34 "bitbucket.org/creachadair/stringset" 35 36 epb "kythe.io/kythe/proto/explore_go_proto" 37 srvpb "kythe.io/kythe/proto/serving_go_proto" 38 ) 39 40 // Tables implements the explore.Service interface using separate static lookup tables 41 // for each API component. 42 type Tables struct { 43 // ParentToChildren is a table of srvpb.Relatives keyed by parent ticket. 44 ParentToChildren table.ProtoLookup 45 46 // ChildToParents is a table of srvpb.Relatives keyed by child ticket. 47 ChildToParents table.ProtoLookup 48 49 // FunctionToCallers is a table of srvpb.Callgraph keyed by function ticket 50 // that points to the callers of the specified function. 51 FunctionToCallers table.ProtoLookup 52 53 // FunctionToCallees is a table of srvpb.Callgraph keyed by function ticket 54 // that points to the callees of the specified function. 55 FunctionToCallees table.ProtoLookup 56 } 57 58 // TypeHierarchy returns the hierarchy (supertypes and subtypes, including implementations) 59 // of a specified type, as a directed acyclic graph. 60 // TODO: not yet implemented 61 func (t *Tables) TypeHierarchy(ctx context.Context, req *epb.TypeHierarchyRequest) (*epb.TypeHierarchyReply, error) { 62 return nil, nil 63 } 64 65 // Callers returns the callers of a specified function, as a directed graph. 66 func (t *Tables) Callers(ctx context.Context, req *epb.CallersRequest) (*epb.CallersReply, error) { 67 tickets := req.Tickets 68 if len(tickets) == 0 { 69 return nil, fmt.Errorf("missing input tickets: %v", req) 70 } 71 72 // succMap maps nodes onto sets of successor nodes 73 succMap := make(map[string]stringset.Set) 74 75 // At the moment, this is our policy for missing data: if an input ticket has 76 // no record in the table, we don't include data for that ticket in the response. 77 // Other table access errors result in returning an error. 78 for _, ticket := range tickets { 79 var callgraph srvpb.Callgraph 80 if err := t.FunctionToCallers.Lookup(ctx, []byte(ticket), &callgraph); err == table.ErrNoSuchKey { 81 continue // skip tickets with no mappings 82 } else if err != nil { 83 return nil, fmt.Errorf("error looking up callers with ticket %q: %v", ticket, err) 84 } 85 86 // This can only happen in the context of a postprocessor bug. 87 if callgraph.Type != srvpb.Callgraph_CALLER { 88 return nil, fmt.Errorf("type of callgraph is not 'CALLER': %v", callgraph) 89 } 90 91 // TODO(jrtom): consider logging a warning if len(callgraph.Tickets) == 0 92 // (postprocessing should disallow this) 93 for _, predTicket := range callgraph.Tickets { 94 if _, ok := succMap[predTicket]; !ok { 95 succMap[predTicket] = stringset.New() 96 } 97 set := succMap[predTicket] 98 set.Add(ticket) 99 } 100 } 101 102 return &epb.CallersReply{Graph: convertSuccMapToGraph(succMap)}, nil 103 } 104 105 func convertSuccMapToGraph(succMap map[string]stringset.Set) *epb.Graph { 106 graph := &epb.Graph{ 107 Nodes: make(map[string]*epb.GraphNode, len(succMap)), 108 } 109 110 for ticket, succs := range succMap { 111 node := getGraphNode(graph, ticket) 112 for s := range succs { 113 node.Successors = append(node.Successors, s) 114 succ := getGraphNode(graph, s) 115 succ.Predecessors = append(succ.Predecessors, ticket) 116 } 117 } 118 119 return graph 120 } 121 122 func getGraphNode(graph *epb.Graph, ticket string) *epb.GraphNode { 123 node, ok := graph.Nodes[ticket] 124 if !ok { 125 node = &epb.GraphNode{} 126 graph.Nodes[ticket] = node 127 } 128 return node 129 } 130 131 // Callees returns the callees of a specified function 132 // (that is, what functions this function calls), as a directed graph. 133 func (t *Tables) Callees(ctx context.Context, req *epb.CalleesRequest) (*epb.CalleesReply, error) { 134 tickets := req.Tickets 135 if len(tickets) == 0 { 136 return nil, fmt.Errorf("missing input tickets: %v", req) 137 } 138 139 // succMap maps nodes onto sets of successor nodes 140 succMap := make(map[string]stringset.Set) 141 142 // At the moment, this is our policy for missing data: if an input ticket has 143 // no record in the table, we don't include data for that ticket in the response. 144 // Other table access errors result in returning an error. 145 for _, ticket := range tickets { 146 var callgraph srvpb.Callgraph 147 if err := t.FunctionToCallees.Lookup(ctx, []byte(ticket), &callgraph); err == table.ErrNoSuchKey { 148 continue // skip tickets with no mappings 149 } else if err != nil { 150 return nil, fmt.Errorf("error looking up callees with ticket %q: %v", ticket, err) 151 } 152 153 // This can only happen in the context of a postprocessor bug. 154 if callgraph.Type != srvpb.Callgraph_CALLEE { 155 return nil, fmt.Errorf("type of callgraph is not 'CALLEE': %v", callgraph) 156 } 157 158 // TODO(jrtom): consider logging a warning if len(callgraph.Tickets) == 0 159 // (postprocessing should disallow this) 160 succMap[ticket] = stringset.New() 161 for _, succTicket := range callgraph.Tickets { 162 set := succMap[ticket] 163 set.Add(succTicket) 164 } 165 } 166 167 return &epb.CalleesReply{Graph: convertSuccMapToGraph(succMap)}, nil 168 } 169 170 // Parameters returns the parameters of a specified function. 171 // TODO: not yet implemented 172 func (t *Tables) Parameters(ctx context.Context, req *epb.ParametersRequest) (*epb.ParametersReply, error) { 173 return nil, nil 174 } 175 176 // Parents returns the parents of a specified node 177 // (for example, the file for a class, or the class for a function). 178 // Note: in some cases a node may have more than one parent. 179 func (t *Tables) Parents(ctx context.Context, req *epb.ParentsRequest) (*epb.ParentsReply, error) { 180 childTickets := req.Tickets 181 if len(childTickets) == 0 { 182 return nil, fmt.Errorf("missing input tickets: %v", req) 183 } 184 185 reply := &epb.ParentsReply{ 186 InputToParents: make(map[string]*epb.Tickets), 187 } 188 189 // At the moment, this is our policy for missing data: if a child (input) ticket has 190 // (a) no record in the table, we don't include a mapping for that ticket in the response. 191 // (b) an empty set of parents in the table, we include a mapping from that ticket to nil 192 // Other table access errors result in returning an error. 193 for _, ticket := range childTickets { 194 var relatives srvpb.Relatives 195 err := t.ChildToParents.Lookup(ctx, []byte(ticket), &relatives) 196 197 if err != nil { 198 if err != table.ErrNoSuchKey { 199 return nil, fmt.Errorf("error looking up parents with ticket %q: %v", ticket, err) 200 } 201 continue // skip tickets with no mappings 202 } 203 204 if relatives.Type != srvpb.Relatives_PARENTS { 205 return nil, fmt.Errorf("type of relatives is not 'PARENTS': %v", relatives) 206 } 207 208 // TODO: consider logging a warning if len(relatives.Tickets) == 0 209 // (postprocessing should disallow this) 210 if len(relatives.Tickets) != 0 { 211 reply.InputToParents[ticket] = &epb.Tickets{Tickets: relatives.Tickets} 212 } 213 } 214 215 return reply, nil 216 } 217 218 // Children returns the children of a specified node 219 // (for example, the classes contained in a file, or the functions contained in a class). 220 func (t *Tables) Children(ctx context.Context, req *epb.ChildrenRequest) (*epb.ChildrenReply, error) { 221 parentTickets := req.Tickets 222 if len(parentTickets) == 0 { 223 return nil, fmt.Errorf("missing input tickets: %v", req) 224 } 225 226 reply := &epb.ChildrenReply{ 227 InputToChildren: make(map[string]*epb.Tickets), 228 } 229 230 // At the moment, this is our policy for missing data: if a parent (input) ticket has 231 // (a) no record in the table, we don't include a mapping for that ticket in the response. 232 // (b) an empty set of children in the table, we include a mapping from that ticket to nil 233 // Other table access errors result in returning an error. 234 for _, ticket := range parentTickets { 235 var relatives srvpb.Relatives 236 err := t.ParentToChildren.Lookup(ctx, []byte(ticket), &relatives) 237 238 if err != nil { 239 if err != table.ErrNoSuchKey { 240 return nil, fmt.Errorf("error looking up children with ticket %q: %v", ticket, err) 241 } 242 continue // skip tickets with no mappings 243 } 244 245 if relatives.Type != srvpb.Relatives_CHILDREN { 246 return nil, fmt.Errorf("type of relatives is not 'CHILDREN': %v", relatives) 247 } 248 249 // TODO: consider logging a warning if len(relatives.Tickets) == 0 250 // (postprocessing should disallow this) 251 if len(relatives.Tickets) != 0 { 252 reply.InputToChildren[ticket] = &epb.Tickets{Tickets: relatives.Tickets} 253 } 254 } 255 256 return reply, nil 257 }