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  }