blob: cc345acd5d18c793ecf7c4f918be1f19dc48d069 [file] [log] [blame]
/*
* Copyright 2017 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 metadata provides support code for processing Kythe metadata
// records, of the type generated by instrumented code generators for
// cross-language linkage.
package metadata
import (
"encoding/json"
"errors"
"fmt"
"io"
"strconv"
"strings"
"kythe.io/kythe/go/util/schema/edges"
protopb "github.com/golang/protobuf/protoc-gen-go/descriptor"
spb "kythe.io/kythe/proto/storage_go_proto"
)
// TODO(fromberger): Add a link to the format documentation here.
// Rules are a collection of metadata rules.
type Rules []Rule
// MarshalJSON encodes the specified rule set as a JSON file.
func (rs Rules) MarshalJSON() ([]byte, error) {
f := file{
Type: fileType,
Meta: make([]rule, len(rs)),
}
for i, r := range rs {
kind := r.EdgeOut
if r.Reverse {
kind = edges.Mirror(kind)
}
rtype := "nop"
if r.EdgeIn == edges.DefinesBinding {
rtype = "anchor_defines"
}
f.Meta[i] = rule{
Type: rtype,
Begin: r.Begin,
End: r.End,
VName: r.VName,
Edge: kind,
}
}
return json.Marshal(f)
}
// A Rule denotes a single metadata rule, associating type linkage information
// for an anchor spanning a given range of text.
type Rule struct {
// The Begin and End fields represent a half-closed interval of byte
// positions to match. Begin is inclusive, End is exclusive.
Begin, End int
EdgeIn string // edge kind to match over the anchor spanned
EdgeOut string // outbound edge kind to emit
VName *spb.VName // the vname to create an edge to or from
Reverse bool // whether to draw to vname (false) or from it (true)
}
// The types below are intermediate structures used for JSON marshaling.
const fileType = "kythe0" // protocol marker
// A file represents an encoded set of rules in JSON notation.
type file struct {
Type string `json:"type"` // required: must equal fileType
Meta []rule `json:"meta,omitempty"`
}
// A rule is the encoded format of a single rule.
type rule struct {
Type string `json:"type"`
Begin int `json:"begin"`
End int `json:"end"`
Edge string `json:"edge,omitempty"`
VName *spb.VName `json:"vname,omitempty"`
}
// Parse parses a single JSON metadata object from r and returns the
// corresponding rules. It is an error if there are extra data after the
// metadata object, or if the type tag of the object does not match the current
// format code.
func Parse(r io.Reader) (Rules, error) {
dec := json.NewDecoder(r)
var f file
if err := dec.Decode(&f); err != nil {
return nil, fmt.Errorf("metadata: invalid file: %v", err)
} else if _, err := dec.Token(); err != io.EOF {
return nil, errors.New("metadata: extra junk at end of input")
} else if f.Type != fileType {
return nil, fmt.Errorf("metadata: wrong type tag: %q", f.Type)
}
rs := make(Rules, len(f.Meta))
for i, meta := range f.Meta {
rs[i] = Rule{
Begin: meta.Begin,
End: meta.End,
EdgeOut: edges.Canonical(meta.Edge),
Reverse: edges.IsReverse(meta.Edge),
VName: meta.VName,
}
switch t := meta.Type; t {
case "nop":
// ok, no special behaviour
case "anchor_defines":
rs[i].EdgeIn = edges.DefinesBinding
default:
return nil, fmt.Errorf("metadata: unknown rule type: %q", t)
}
}
return rs, nil
}
// FromGeneratedCodeInfo constructs a set of rules from the corresponding
// protobuf descriptor message and the vname of the metadata file from which
// the generated descriptor was loaded.
func FromGeneratedCodeInfo(msg *protopb.GeneratedCodeInfo, vname *spb.VName) Rules {
rs := make(Rules, len(msg.Annotation))
for i, anno := range msg.Annotation {
// Convert the path to a dot-separated string, e.g., 1.0.3.2,
// for use in the vname signature.
sig := make([]string, len(anno.Path))
for i, elt := range anno.Path {
sig[i] = strconv.Itoa(int(elt))
}
// TODO(fromberger): Work out how to derive the correct corpus and root
// labels. When the protobuf source file is in the same corpus as its
// metadata, this will work as-is.
//
// If the protobuf inputs live in a different corpus, it will be
// necessary to make the extractor map the metadata file to the correct
// corpus and root at build time. Since the metadata file does not get
// pointed to directly, this ensures we get the right contact.
//
// This does NOT solve how to deal with generated .proto files, but
// that is a much less common case, and we can address it separately.
vname := &spb.VName{
Corpus: vname.GetCorpus(),
Root: vname.GetRoot(),
Path: anno.GetSourceFile(),
Language: "protobuf",
Signature: strings.Join(sig, "."),
}
rs[i] = Rule{
EdgeIn: edges.DefinesBinding,
EdgeOut: edges.Generates,
Reverse: true,
Begin: int(anno.GetBegin()),
End: int(anno.GetEnd()),
VName: vname,
}
}
return rs
}