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] }