kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/services/xrefs/xrefs.go (about)

     1  /*
     2   * Copyright 2015 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 xrefs defines the xrefs Service interface and some useful utility
    18  // functions.
    19  package xrefs // import "kythe.io/kythe/go/services/xrefs"
    20  
    21  import (
    22  	"context"
    23  	"net/http"
    24  	"regexp"
    25  	"strings"
    26  	"time"
    27  
    28  	"kythe.io/kythe/go/services/web"
    29  	"kythe.io/kythe/go/util/kytheuri"
    30  	"kythe.io/kythe/go/util/log"
    31  	"kythe.io/kythe/go/util/schema/edges"
    32  
    33  	"bitbucket.org/creachadair/stringset"
    34  	"google.golang.org/grpc/codes"
    35  	"google.golang.org/grpc/status"
    36  
    37  	cpb "kythe.io/kythe/proto/common_go_proto"
    38  	xpb "kythe.io/kythe/proto/xref_go_proto"
    39  )
    40  
    41  // Service defines the interface for file based cross-references.  Informally,
    42  // the cross-references of an entity comprise the definitions of that entity,
    43  // together with all the places where those definitions are referenced through
    44  // constructs such as type declarations, variable references, function calls,
    45  // and so on.
    46  type Service interface {
    47  	// Decorations returns an index of the nodes associated with a specified file.
    48  	Decorations(context.Context, *xpb.DecorationsRequest) (*xpb.DecorationsReply, error)
    49  
    50  	// CrossReferences returns the global cross-references for the given nodes.
    51  	CrossReferences(context.Context, *xpb.CrossReferencesRequest) (*xpb.CrossReferencesReply, error)
    52  
    53  	// Documentation takes a set of tickets and returns documentation for them.
    54  	Documentation(context.Context, *xpb.DocumentationRequest) (*xpb.DocumentationReply, error)
    55  
    56  	// Close releases any underlying resources.
    57  	Close(context.Context) error
    58  }
    59  
    60  var (
    61  	// ErrPermissionDenied is returned by an implementation of a method when the
    62  	// user is not allowed to view the content because of some restrictions.
    63  	ErrPermissionDenied = status.Error(codes.PermissionDenied, "access denied")
    64  
    65  	// ErrDecorationsNotFound is returned by an implementation of the Decorations
    66  	// method when decorations for the given file cannot be found.
    67  	ErrDecorationsNotFound = status.Error(codes.NotFound, "file decorations not found")
    68  
    69  	// ErrCanceled is returned by services when the caller cancels the RPC.
    70  	ErrCanceled = status.Error(codes.Canceled, "canceled")
    71  
    72  	// ErrDeadlineExceeded is returned by services when something times out.
    73  	ErrDeadlineExceeded = status.Error(codes.DeadlineExceeded, "deadline exceeded")
    74  )
    75  
    76  // FixTickets converts the specified tickets, which are expected to be Kythe
    77  // URIs, into canonical form. It is an error if len(tickets) == 0.
    78  func FixTickets(tickets []string) ([]string, error) {
    79  	if len(tickets) == 0 {
    80  		return nil, status.Error(codes.InvalidArgument, "no tickets specified")
    81  	}
    82  
    83  	canonical := make([]string, len(tickets))
    84  	for i, ticket := range tickets {
    85  		fixed, err := kytheuri.Fix(ticket)
    86  		if err != nil {
    87  			return nil, status.Errorf(codes.InvalidArgument, "invalid ticket %q: %v", ticket, err)
    88  		}
    89  		canonical[i] = fixed
    90  	}
    91  	return canonical, nil
    92  }
    93  
    94  // IsDefKind reports whether the given edgeKind matches the requested
    95  // definition kind.
    96  func IsDefKind(requestedKind xpb.CrossReferencesRequest_DefinitionKind, edgeKind string, incomplete bool) bool {
    97  	edgeKind = edges.Canonical(edgeKind)
    98  	if IsDeclKind(xpb.CrossReferencesRequest_ALL_DECLARATIONS, edgeKind, incomplete) {
    99  		return false
   100  	}
   101  	switch requestedKind {
   102  	case xpb.CrossReferencesRequest_NO_DEFINITIONS:
   103  		return false
   104  	case xpb.CrossReferencesRequest_FULL_DEFINITIONS:
   105  		return edgeKind == edges.Defines
   106  	case xpb.CrossReferencesRequest_BINDING_DEFINITIONS:
   107  		return edgeKind == edges.DefinesBinding
   108  	case xpb.CrossReferencesRequest_ALL_DEFINITIONS:
   109  		return edges.IsVariant(edgeKind, edges.Defines)
   110  	default:
   111  		log.Errorf("unhandled CrossReferencesRequest_DefinitionKind: %v", requestedKind)
   112  		return false
   113  	}
   114  }
   115  
   116  // IsDeclKind reports whether the given edgeKind matches the requested
   117  // declaration kind
   118  func IsDeclKind(requestedKind xpb.CrossReferencesRequest_DeclarationKind, edgeKind string, incomplete bool) bool {
   119  	edgeKind = edges.Canonical(edgeKind)
   120  	switch requestedKind {
   121  	case xpb.CrossReferencesRequest_NO_DECLARATIONS:
   122  		return false
   123  	case xpb.CrossReferencesRequest_ALL_DECLARATIONS:
   124  		return (incomplete && edges.IsVariant(edgeKind, edges.Defines)) || edgeKind == internalDeclarationKind
   125  	default:
   126  		log.Errorf("unhandled CrossReferenceRequest_DeclarationKind: %v", requestedKind)
   127  		return false
   128  	}
   129  }
   130  
   131  // IsRefKind determines whether the given edgeKind matches the requested
   132  // reference kind.
   133  func IsRefKind(requestedKind xpb.CrossReferencesRequest_ReferenceKind, edgeKind string) bool {
   134  	edgeKind = edges.Canonical(edgeKind)
   135  	switch requestedKind {
   136  	case xpb.CrossReferencesRequest_NO_REFERENCES:
   137  		return false
   138  	case xpb.CrossReferencesRequest_CALL_REFERENCES:
   139  		return edges.IsVariant(edgeKind, edges.RefCall)
   140  	case xpb.CrossReferencesRequest_NON_CALL_REFERENCES:
   141  		return !edges.IsVariant(edgeKind, edges.RefCall) && edges.IsVariant(edgeKind, edges.Ref)
   142  	case xpb.CrossReferencesRequest_ALL_REFERENCES:
   143  		return edges.IsVariant(edgeKind, edges.Ref)
   144  	default:
   145  		log.Errorf("unhandled CrossReferencesRequest_ReferenceKind: %v", requestedKind)
   146  		return false
   147  	}
   148  }
   149  
   150  // Internal-only edge kinds for cross-references
   151  const (
   152  	internalKindPrefix         = "#internal/"
   153  	internalCallerKindDirect   = internalKindPrefix + "ref/call/direct"
   154  	internalCallerKindOverride = internalKindPrefix + "ref/call/override"
   155  	internalDeclarationKind    = internalKindPrefix + "ref/declare"
   156  )
   157  
   158  // IsInternalKind determines whether the given edge kind is an internal variant.
   159  func IsInternalKind(kind string) bool {
   160  	return strings.HasPrefix(kind, internalKindPrefix)
   161  }
   162  
   163  // IsRelatedNodeKind determines whether the give edge kind matches the requested
   164  // related node kinds.
   165  func IsRelatedNodeKind(requestedKinds stringset.Set, kind string) bool {
   166  	return !IsInternalKind(kind) && !edges.IsAnchorEdge(kind) && (len(requestedKinds) == 0 || requestedKinds.Contains(kind))
   167  }
   168  
   169  // IsCallerKind determines whether the given edgeKind matches the requested
   170  // caller kind.
   171  func IsCallerKind(requestedKind xpb.CrossReferencesRequest_CallerKind, edgeKind string) bool {
   172  	edgeKind = edges.Canonical(edgeKind)
   173  	switch requestedKind {
   174  	case xpb.CrossReferencesRequest_NO_CALLERS:
   175  		return false
   176  	case xpb.CrossReferencesRequest_DIRECT_CALLERS:
   177  		return edgeKind == internalCallerKindDirect
   178  	case xpb.CrossReferencesRequest_OVERRIDE_CALLERS:
   179  		return edgeKind == internalCallerKindDirect || edgeKind == internalCallerKindOverride
   180  	default:
   181  		log.Errorf("unhandled CrossReferencesRequest_CallerKind: %v", requestedKind)
   182  		return false
   183  	}
   184  }
   185  
   186  // IsSpeculative returns whether the edge kind is considered speculative.
   187  func IsSpeculative(edgeKind string) bool {
   188  	return edgeKind == internalCallerKindOverride || strings.HasSuffix(edgeKind, "/thunk")
   189  }
   190  
   191  // ConvertFilters converts each filter glob into an equivalent regexp.
   192  func ConvertFilters(filters []string) []*regexp.Regexp {
   193  	var patterns []*regexp.Regexp
   194  	for _, filter := range filters {
   195  		re := filterToRegexp(filter)
   196  		if re == matchesAll {
   197  			return []*regexp.Regexp{re}
   198  		}
   199  		patterns = append(patterns, re)
   200  	}
   201  	return patterns
   202  }
   203  
   204  var (
   205  	filterOpsRE = regexp.MustCompile("[*][*]|[*?]")
   206  	matchesAll  = regexp.MustCompile(".*")
   207  )
   208  
   209  func filterToRegexp(pattern string) *regexp.Regexp {
   210  	if pattern == "**" {
   211  		return matchesAll
   212  	}
   213  	var re string
   214  	for {
   215  		loc := filterOpsRE.FindStringIndex(pattern)
   216  		if loc == nil {
   217  			break
   218  		}
   219  		re += regexp.QuoteMeta(pattern[:loc[0]])
   220  		switch pattern[loc[0]:loc[1]] {
   221  		case "**":
   222  			re += ".*"
   223  		case "*":
   224  			re += "[^/]*"
   225  		case "?":
   226  			re += "[^/]"
   227  		default:
   228  			log.Fatal("Unknown filter operator: " + pattern[loc[0]:loc[1]])
   229  		}
   230  		pattern = pattern[loc[1]:]
   231  	}
   232  	return regexp.MustCompile(re + regexp.QuoteMeta(pattern))
   233  }
   234  
   235  // MatchesAny reports whether if str matches any of the patterns
   236  func MatchesAny(str string, patterns []*regexp.Regexp) bool {
   237  	for _, p := range patterns {
   238  		if p == matchesAll || p.MatchString(str) {
   239  			return true
   240  		}
   241  	}
   242  	return false
   243  }
   244  
   245  // BoundedRequests guards against requests for more tickets than allowed per
   246  // the MaxTickets configuration.
   247  type BoundedRequests struct {
   248  	MaxTickets int
   249  	Service
   250  }
   251  
   252  // CrossReferences implements part of the Service interface.
   253  func (b BoundedRequests) CrossReferences(ctx context.Context, req *xpb.CrossReferencesRequest) (*xpb.CrossReferencesReply, error) {
   254  	if len(req.Ticket) > b.MaxTickets {
   255  		return nil, status.Errorf(codes.InvalidArgument, "too many tickets requested: %d (max %d)", len(req.Ticket), b.MaxTickets)
   256  	}
   257  	return b.Service.CrossReferences(ctx, req)
   258  }
   259  
   260  // Documentation implements part of the Service interface.
   261  func (b BoundedRequests) Documentation(ctx context.Context, req *xpb.DocumentationRequest) (*xpb.DocumentationReply, error) {
   262  	if len(req.Ticket) > b.MaxTickets {
   263  		return nil, status.Errorf(codes.InvalidArgument, "too many tickets requested: %d (max %d)", len(req.Ticket), b.MaxTickets)
   264  	}
   265  	return b.Service.Documentation(ctx, req)
   266  }
   267  
   268  type webClient struct{ addr string }
   269  
   270  func (webClient) Close(context.Context) error { return nil }
   271  
   272  // Decorations implements part of the Service interface.
   273  func (w *webClient) Decorations(ctx context.Context, q *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) {
   274  	var reply xpb.DecorationsReply
   275  	return &reply, web.Call(w.addr, "decorations", q, &reply)
   276  }
   277  
   278  // CrossReferences implements part of the Service interface.
   279  func (w *webClient) CrossReferences(ctx context.Context, q *xpb.CrossReferencesRequest) (*xpb.CrossReferencesReply, error) {
   280  	var reply xpb.CrossReferencesReply
   281  	return &reply, web.Call(w.addr, "xrefs", q, &reply)
   282  }
   283  
   284  // Documentation implements part of the Service interface.
   285  func (w *webClient) Documentation(ctx context.Context, q *xpb.DocumentationRequest) (*xpb.DocumentationReply, error) {
   286  	var reply xpb.DocumentationReply
   287  	return &reply, web.Call(w.addr, "documentation", q, &reply)
   288  }
   289  
   290  // WebClient returns an xrefs Service based on a remote web server.
   291  func WebClient(addr string) Service {
   292  	return &webClient{addr}
   293  }
   294  
   295  // RegisterHTTPHandlers registers JSON HTTP handlers with mux using the given
   296  // xrefs Service.  The following methods with be exposed:
   297  //
   298  //	GET /decorations
   299  //	  Request: JSON encoded xrefs.DecorationsRequest
   300  //	  Response: JSON encoded xrefs.DecorationsReply
   301  //	GET /xrefs
   302  //	  Request: JSON encoded xrefs.CrossReferencesRequest
   303  //	  Response: JSON encoded xrefs.CrossReferencesReply
   304  //	GET /documentation
   305  //	  Request: JSON encoded xrefs.DocumentationRequest
   306  //	  Response: JSON encoded xrefs.DocumentationReply
   307  //
   308  // Note: /nodes, /edges, /decorations, and /xrefs will return their responses as
   309  // serialized protobufs if the "proto" query parameter is set.
   310  func RegisterHTTPHandlers(ctx context.Context, xs Service, mux *http.ServeMux) {
   311  	mux.HandleFunc("/xrefs", func(w http.ResponseWriter, r *http.Request) {
   312  		start := time.Now()
   313  		defer func() {
   314  			log.InfoContextf(ctx, "xrefs.CrossReferences:\t%s", time.Since(start))
   315  		}()
   316  		var req xpb.CrossReferencesRequest
   317  		if err := web.ReadJSONBody(r, &req); err != nil {
   318  			http.Error(w, err.Error(), http.StatusBadRequest)
   319  			return
   320  		}
   321  		reply, err := xs.CrossReferences(ctx, &req)
   322  		if err != nil {
   323  			http.Error(w, err.Error(), http.StatusInternalServerError)
   324  			return
   325  		}
   326  
   327  		if err := web.WriteResponse(w, r, reply); err != nil {
   328  			log.ErrorContextf(ctx, "CrossReferences error: %v", err)
   329  		}
   330  	})
   331  	mux.HandleFunc("/decorations", func(w http.ResponseWriter, r *http.Request) {
   332  		start := time.Now()
   333  		defer func() {
   334  			log.InfoContextf(ctx, "xrefs.Decorations:\t%s", time.Since(start))
   335  		}()
   336  		var req xpb.DecorationsRequest
   337  		if err := web.ReadJSONBody(r, &req); err != nil {
   338  			http.Error(w, err.Error(), http.StatusBadRequest)
   339  			return
   340  		}
   341  		reply, err := xs.Decorations(ctx, &req)
   342  		if err != nil {
   343  			http.Error(w, err.Error(), http.StatusInternalServerError)
   344  			return
   345  		}
   346  
   347  		if err := web.WriteResponse(w, r, reply); err != nil {
   348  			log.ErrorContextf(ctx, "Decorations error: %v", err)
   349  		}
   350  	})
   351  	mux.HandleFunc("/documentation", func(w http.ResponseWriter, r *http.Request) {
   352  		start := time.Now()
   353  		defer func() {
   354  			log.InfoContextf(ctx, "xrefs.Documentation:\t%s", time.Since(start))
   355  		}()
   356  		var req xpb.DocumentationRequest
   357  		if err := web.ReadJSONBody(r, &req); err != nil {
   358  			http.Error(w, err.Error(), http.StatusBadRequest)
   359  			return
   360  		}
   361  		reply, err := xs.Documentation(ctx, &req)
   362  		if err != nil {
   363  			http.Error(w, err.Error(), http.StatusInternalServerError)
   364  			return
   365  		}
   366  
   367  		if err := web.WriteResponse(w, r, reply); err != nil {
   368  			log.ErrorContextf(ctx, "Documentation error: %v", err)
   369  		}
   370  	})
   371  }
   372  
   373  // ByName orders a slice of facts by their fact names.
   374  type ByName []*cpb.Fact
   375  
   376  func (s ByName) Len() int           { return len(s) }
   377  func (s ByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
   378  func (s ByName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }