blob: ce9a3e4195e40f641135c93ea30d7121ab15f37d [file] [log] [blame]
package protoparse
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"os"
"path/filepath"
"sort"
"strings"
"github.com/golang/protobuf/proto"
dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
"github.com/jhump/protoreflect/desc"
"github.com/jhump/protoreflect/desc/internal"
)
//go:generate goyacc -o proto.y.go -p proto proto.y
var errNoImportPathsForAbsoluteFilePath = errors.New("must specify at least one import path if any absolute file paths are given")
func init() {
protoErrorVerbose = true
// fix up the generated "token name" array so that error messages are nicer
setTokenName(_STRING_LIT, "string literal")
setTokenName(_INT_LIT, "int literal")
setTokenName(_FLOAT_LIT, "float literal")
setTokenName(_NAME, "identifier")
setTokenName(_FQNAME, "fully-qualified name")
setTokenName(_TYPENAME, "type name")
setTokenName(_ERROR, "error")
// for keywords, just show the keyword itself wrapped in quotes
for str, i := range keywords {
setTokenName(i, fmt.Sprintf(`"%s"`, str))
}
}
func setTokenName(token int, text string) {
// NB: this is based on logic in generated parse code that translates the
// int returned from the lexer into an internal token number.
var intern int
if token < len(protoTok1) {
intern = protoTok1[token]
} else {
if token >= protoPrivate {
if token < protoPrivate+len(protoTok2) {
intern = protoTok2[token-protoPrivate]
}
}
if intern == 0 {
for i := 0; i+1 < len(protoTok3); i += 2 {
if protoTok3[i] == token {
intern = protoTok3[i+1]
break
}
}
}
}
if intern >= 1 && intern-1 < len(protoToknames) {
protoToknames[intern-1] = text
return
}
panic(fmt.Sprintf("Unknown token value: %d", token))
}
// FileAccessor is an abstraction for opening proto source files. It takes the
// name of the file to open and returns either the input reader or an error.
type FileAccessor func(filename string) (io.ReadCloser, error)
// FileContentsFromMap returns a FileAccessor that uses the given map of file
// contents. This allows proto source files to be constructed in memory and
// easily supplied to a parser. The map keys are the paths to the proto source
// files, and the values are the actual proto source contents.
func FileContentsFromMap(files map[string]string) FileAccessor {
return func(filename string) (io.ReadCloser, error) {
contents, ok := files[filename]
if !ok {
return nil, os.ErrNotExist
}
return ioutil.NopCloser(strings.NewReader(contents)), nil
}
}
// ResolveFilenames tries to resolve fileNames into paths that are relative to
// directories in the given importPaths. The returned slice has the results in
// the same order as they are supplied in fileNames.
//
// The resulting names should be suitable for passing to Parser.ParseFiles.
//
// If importPaths is empty and any path is absolute, this returns error.
// If importPaths is empty and all paths are relative, this returns the original fileNames.
func ResolveFilenames(importPaths []string, fileNames ...string) ([]string, error) {
if len(importPaths) == 0 {
if containsAbsFilePath(fileNames) {
// We have to do this as otherwise parseProtoFiles can result in duplicate symbols.
// For example, assume we import "foo/bar/bar.proto" in a file "/home/alice/dev/foo/bar/baz.proto"
// as we call ParseFiles("/home/alice/dev/foo/bar/bar.proto","/home/alice/dev/foo/bar/baz.proto")
// with "/home/alice/dev" as our current directory. Due to the recursive nature of parseProtoFiles,
// it will discover the import "foo/bar/bar.proto" in the input file, and call parse on this,
// adding "foo/bar/bar.proto" to the parsed results, as well as "/home/alice/dev/foo/bar/bar.proto"
// from the input file list. This will result in a
// 'duplicate symbol SYMBOL: already defined as field in "/home/alice/dev/foo/bar/bar.proto'
// error being returned from ParseFiles.
return nil, errNoImportPathsForAbsoluteFilePath
}
return fileNames, nil
}
absImportPaths, err := absoluteFilePaths(importPaths)
if err != nil {
return nil, err
}
absFileNames, err := absoluteFilePaths(fileNames)
if err != nil {
return nil, err
}
resolvedFileNames := make([]string, 0, len(fileNames))
for _, absFileName := range absFileNames {
resolvedFileName, err := resolveAbsFilename(absImportPaths, absFileName)
if err != nil {
return nil, err
}
resolvedFileNames = append(resolvedFileNames, resolvedFileName)
}
return resolvedFileNames, nil
}
// Parser parses proto source into descriptors.
type Parser struct {
// The paths used to search for dependencies that are referenced in import
// statements in proto source files. If no import paths are provided then
// "." (current directory) is assumed to be the only import path.
//
// This setting is only used during ParseFiles operations. Since calls to
// ParseFilesButDoNotLink do not link, there is no need to load and parse
// dependencies.
ImportPaths []string
// If true, the supplied file names/paths need not necessarily match how the
// files are referenced in import statements. The parser will attempt to
// match import statements to supplied paths, "guessing" the import paths
// for the files. Note that this inference is not perfect and link errors
// could result. It works best when all proto files are organized such that
// a single import path can be inferred (e.g. all files under a single tree
// with import statements all being relative to the root of this tree).
InferImportPaths bool
// Used to create a reader for a given filename, when loading proto source
// file contents. If unset, os.Open is used. If ImportPaths is also empty
// then relative paths are will be relative to the process's current working
// directory.
Accessor FileAccessor
// If true, the resulting file descriptors will retain source code info,
// that maps elements to their location in the source files as well as
// includes comments found during parsing (and attributed to elements of
// the source file).
IncludeSourceCodeInfo bool
// If true, the results from ParseFilesButDoNotLink will be passed through
// some additional validations. But only constraints that do not require
// linking can be checked. These include proto2 vs. proto3 language features,
// looking for incorrect usage of reserved names or tags, and ensuring that
// fields have unique tags and that enum values have unique numbers (unless
// the enum allows aliases).
ValidateUnlinkedFiles bool
// If true, the results from ParseFilesButDoNotLink will have options
// interpreted. Any uninterpretable options (including any custom options or
// options that refer to message and enum types, which can only be
// interpreted after linking) will be left in uninterpreted_options. Also,
// the "default" pseudo-option for fields can only be interpreted for scalar
// fields, excluding enums. (Interpreting default values for enum fields
// requires resolving enum names, which requires linking.)
InterpretOptionsInUnlinkedFiles bool
}
// ParseFiles parses the named files into descriptors. The returned slice has
// the same number of entries as the give filenames, in the same order. So the
// first returned descriptor corresponds to the first given name, and so on.
//
// All dependencies for all specified files (including transitive dependencies)
// must be accessible via the parser's Accessor or a link error will occur. The
// exception to this rule is that files can import standard Google-provided
// files -- e.g. google/protobuf/*.proto -- without needing to supply sources
// for these files. Like protoc, this parser has a built-in version of these
// files it can use if they aren't explicitly supplied.
func (p Parser) ParseFiles(filenames ...string) ([]*desc.FileDescriptor, error) {
accessor := p.Accessor
if accessor == nil {
accessor = func(name string) (io.ReadCloser, error) {
return os.Open(name)
}
}
paths := p.ImportPaths
if len(paths) > 0 {
acc := accessor
accessor = func(name string) (io.ReadCloser, error) {
var ret error
for _, path := range paths {
f, err := acc(filepath.Join(path, name))
if err != nil {
if ret == nil {
ret = err
}
continue
}
return f, nil
}
return nil, ret
}
}
protos := map[string]*parseResult{}
err := parseProtoFiles(accessor, filenames, true, true, protos)
if err != nil {
return nil, err
}
if p.InferImportPaths {
protos = fixupFilenames(protos)
}
linkedProtos, err := newLinker(protos).linkFiles()
if err != nil {
return nil, err
}
if p.IncludeSourceCodeInfo {
for name, fd := range linkedProtos {
pr := protos[name]
fd.AsFileDescriptorProto().SourceCodeInfo = pr.generateSourceCodeInfo()
internal.RecomputeSourceInfo(fd)
}
}
fds := make([]*desc.FileDescriptor, len(filenames))
for i, name := range filenames {
fd := linkedProtos[name]
fds[i] = fd
}
return fds, nil
}
// ParseFilesButDoNotLink parses the named files into descriptor protos. The
// results are just protos, not fully-linked descriptors. It is possible that
// descriptors are invalid and still be returned in parsed form without error
// due to the fact that the linking step is skipped (and thus many validation
// steps omitted).
//
// There are a few side effects to not linking the descriptors:
// 1. No options will be interpreted. Options can refer to extensions or have
// message and enum types. Without linking, these extension and type
// references are not resolved, so the options may not be interpretable.
// So all options will appear in UninterpretedOption fields of the various
// descriptor options messages.
// 2. Type references will not be resolved. This means that the actual type
// names in the descriptors may be unqualified and even relative to the
// scope in which the type reference appears. This goes for fields that
// have message and enum types. It also applies to methods and their
// references to request and response message types.
// 3. Enum fields are not known. Until a field's type reference is resolved
// (during linking), it is not known whether the type refers to a message
// or an enum. So all fields with such type references have their Type set
// to TYPE_MESSAGE.
//
// This method will still validate the syntax of parsed files. If the parser's
// ValidateUnlinkedFiles field is true, additional checks, beyond syntax will
// also be performed.
func (p Parser) ParseFilesButDoNotLink(filenames ...string) ([]*dpb.FileDescriptorProto, error) {
accessor := p.Accessor
if accessor == nil {
accessor = func(name string) (io.ReadCloser, error) {
return os.Open(name)
}
}
protos := map[string]*parseResult{}
err := parseProtoFiles(accessor, filenames, false, p.ValidateUnlinkedFiles, protos)
if err != nil {
return nil, err
}
if p.InferImportPaths {
protos = fixupFilenames(protos)
}
fds := make([]*dpb.FileDescriptorProto, len(filenames))
for i, name := range filenames {
pr := protos[name]
fd := pr.fd
if p.InterpretOptionsInUnlinkedFiles {
pr.lenient = true
interpretFileOptions(pr, poorFileDescriptorish{FileDescriptorProto: fd})
}
if p.IncludeSourceCodeInfo {
fd.SourceCodeInfo = pr.generateSourceCodeInfo()
}
fds[i] = fd
}
return fds, nil
}
func containsAbsFilePath(filePaths []string) bool {
for _, filePath := range filePaths {
if filepath.IsAbs(filePath) {
return true
}
}
return false
}
func absoluteFilePaths(filePaths []string) ([]string, error) {
absFilePaths := make([]string, 0, len(filePaths))
for _, filePath := range filePaths {
absFilePath, err := filepath.Abs(filePath)
if err != nil {
return nil, err
}
absFilePaths = append(absFilePaths, absFilePath)
}
return absFilePaths, nil
}
func resolveAbsFilename(absImportPaths []string, absFileName string) (string, error) {
for _, absImportPath := range absImportPaths {
if isDescendant(absImportPath, absFileName) {
resolvedPath, err := filepath.Rel(absImportPath, absFileName)
if err != nil {
return "", err
}
return resolvedPath, nil
}
}
return "", fmt.Errorf("%s does not reside in any import path", absFileName)
}
// isDescendant returns true if file is a descendant of dir.
func isDescendant(dir, file string) bool {
dir = filepath.Clean(dir)
cur := file
for {
d := filepath.Dir(cur)
if d == dir {
return true
}
if d == "." || d == cur {
// we've run out of path elements
return false
}
cur = d
}
}
func fixupFilenames(protos map[string]*parseResult) map[string]*parseResult {
// In the event that the given filenames (keys in the supplied map) do not
// match the actual paths used in 'import' statements in the files, we try
// to revise names in the protos so that they will match and be linkable.
revisedProtos := map[string]*parseResult{}
protoPaths := map[string]struct{}{}
// TODO: this is O(n^2) but could likely be O(n) with a clever data structure (prefix tree that is indexed backwards?)
importCandidates := map[string]map[string]struct{}{}
candidatesAvailable := map[string]struct{}{}
for name := range protos {
candidatesAvailable[name] = struct{}{}
for _, f := range protos {
for _, imp := range f.fd.Dependency {
if strings.HasSuffix(name, imp) {
candidates := importCandidates[imp]
if candidates == nil {
candidates = map[string]struct{}{}
importCandidates[imp] = candidates
}
candidates[name] = struct{}{}
}
}
}
}
for imp, candidates := range importCandidates {
// if we found multiple possible candidates, use the one that is an exact match
// if it exists, and otherwise, guess that it's the shortest path (fewest elements)
var best string
for c := range candidates {
if _, ok := candidatesAvailable[c]; !ok {
// already used this candidate and re-written its filename accordingly
continue
}
if c == imp {
// exact match!
best = c
break
}
if best == "" {
best = c
} else {
// HACK: we can't actually tell which files is supposed to match
// this import, so arbitrarily pick the "shorter" one (fewest
// path elements) or, on a tie, the lexically earlier one
minLen := strings.Count(best, string(filepath.Separator))
cLen := strings.Count(c, string(filepath.Separator))
if cLen < minLen || (cLen == minLen && c < best) {
best = c
}
}
}
if best != "" {
prefix := best[:len(best)-len(imp)]
if len(prefix) > 0 {
protoPaths[prefix] = struct{}{}
}
f := protos[best]
f.fd.Name = proto.String(imp)
revisedProtos[imp] = f
delete(candidatesAvailable, best)
}
}
if len(candidatesAvailable) == 0 {
return revisedProtos
}
if len(protoPaths) == 0 {
for c := range candidatesAvailable {
revisedProtos[c] = protos[c]
}
return revisedProtos
}
// Any remaining candidates are entry-points (not imported by others), so
// the best bet to "fixing" their file name is to see if they're in one of
// the proto paths we found, and if so strip that prefix.
protoPathStrs := make([]string, len(protoPaths))
i := 0
for p := range protoPaths {
protoPathStrs[i] = p
i++
}
sort.Strings(protoPathStrs)
// we look at paths in reverse order, so we'll use a longer proto path if
// there is more than one match
for c := range candidatesAvailable {
var imp string
for i := len(protoPathStrs) - 1; i >= 0; i-- {
p := protoPathStrs[i]
if strings.HasPrefix(c, p) {
imp = c[len(p):]
break
}
}
if imp != "" {
f := protos[c]
f.fd.Name = proto.String(imp)
revisedProtos[imp] = f
} else {
revisedProtos[c] = protos[c]
}
}
return revisedProtos
}
func parseProtoFiles(acc FileAccessor, filenames []string, recursive, validate bool, parsed map[string]*parseResult) error {
for _, name := range filenames {
if _, ok := parsed[name]; ok {
continue
}
in, err := acc(name)
if err != nil {
if d, ok := standardImports[name]; ok {
parsed[name] = &parseResult{fd: d}
continue
}
return err
}
func() {
defer in.Close()
parsed[name], err = parseProto(name, in, validate)
}()
if err != nil {
return err
}
if recursive {
err = parseProtoFiles(acc, parsed[name].fd.Dependency, true, validate, parsed)
if err != nil {
return fmt.Errorf("failed to load imports for %q: %s", name, err)
}
}
}
return nil
}
type parseResult struct {
// the parsed file descriptor
fd *dpb.FileDescriptorProto
// if set to true, enables lenient interpretation of options, where
// unrecognized options will be left uninterpreted instead of resulting in a
// link error
lenient bool
// a map of elements in the descriptor to nodes in the AST
// (for extracting position information when validating the descriptor)
nodes map[proto.Message]node
// a map of uninterpreted option AST nodes to their relative path
// in the resulting options message
interpretedOptions map[*optionNode][]int32
}
func (r *parseResult) getFileNode(f *dpb.FileDescriptorProto) fileDecl {
if r.nodes == nil {
return noSourceNode{pos: unknownPos(f.GetName())}
}
return r.nodes[f].(fileDecl)
}
func (r *parseResult) getOptionNode(o *dpb.UninterpretedOption) optionDecl {
if r.nodes == nil {
return noSourceNode{pos: unknownPos(r.fd.GetName())}
}
return r.nodes[o].(optionDecl)
}
func (r *parseResult) getOptionNamePartNode(o *dpb.UninterpretedOption_NamePart) node {
if r.nodes == nil {
return noSourceNode{pos: unknownPos(r.fd.GetName())}
}
return r.nodes[o]
}
func (r *parseResult) getMessageNode(m *dpb.DescriptorProto) msgDecl {
if r.nodes == nil {
return noSourceNode{pos: unknownPos(r.fd.GetName())}
}
return r.nodes[m].(msgDecl)
}
func (r *parseResult) getFieldNode(f *dpb.FieldDescriptorProto) fieldDecl {
if r.nodes == nil {
return noSourceNode{pos: unknownPos(r.fd.GetName())}
}
return r.nodes[f].(fieldDecl)
}
func (r *parseResult) getOneOfNode(o *dpb.OneofDescriptorProto) node {
if r.nodes == nil {
return noSourceNode{pos: unknownPos(r.fd.GetName())}
}
return r.nodes[o]
}
func (r *parseResult) getExtensionRangeNode(e *dpb.DescriptorProto_ExtensionRange) rangeDecl {
if r.nodes == nil {
return noSourceNode{pos: unknownPos(r.fd.GetName())}
}
return r.nodes[e].(rangeDecl)
}
func (r *parseResult) getMessageReservedRangeNode(rr *dpb.DescriptorProto_ReservedRange) rangeDecl {
if r.nodes == nil {
return noSourceNode{pos: unknownPos(r.fd.GetName())}
}
return r.nodes[rr].(rangeDecl)
}
func (r *parseResult) getEnumNode(e *dpb.EnumDescriptorProto) node {
if r.nodes == nil {
return noSourceNode{pos: unknownPos(r.fd.GetName())}
}
return r.nodes[e]
}
func (r *parseResult) getEnumValueNode(e *dpb.EnumValueDescriptorProto) enumValueDecl {
if r.nodes == nil {
return noSourceNode{pos: unknownPos(r.fd.GetName())}
}
return r.nodes[e].(enumValueDecl)
}
func (r *parseResult) getEnumReservedRangeNode(rr *dpb.EnumDescriptorProto_EnumReservedRange) rangeDecl {
if r.nodes == nil {
return noSourceNode{pos: unknownPos(r.fd.GetName())}
}
return r.nodes[rr].(rangeDecl)
}
func (r *parseResult) getServiceNode(s *dpb.ServiceDescriptorProto) node {
if r.nodes == nil {
return noSourceNode{pos: unknownPos(r.fd.GetName())}
}
return r.nodes[s]
}
func (r *parseResult) getMethodNode(m *dpb.MethodDescriptorProto) methodDecl {
if r.nodes == nil {
return noSourceNode{pos: unknownPos(r.fd.GetName())}
}
return r.nodes[m].(methodDecl)
}
func (r *parseResult) putFileNode(f *dpb.FileDescriptorProto, n *fileNode) {
r.nodes[f] = n
}
func (r *parseResult) putOptionNode(o *dpb.UninterpretedOption, n *optionNode) {
r.nodes[o] = n
}
func (r *parseResult) putOptionNamePartNode(o *dpb.UninterpretedOption_NamePart, n *optionNamePartNode) {
r.nodes[o] = n
}
func (r *parseResult) putMessageNode(m *dpb.DescriptorProto, n msgDecl) {
r.nodes[m] = n
}
func (r *parseResult) putFieldNode(f *dpb.FieldDescriptorProto, n fieldDecl) {
r.nodes[f] = n
}
func (r *parseResult) putOneOfNode(o *dpb.OneofDescriptorProto, n *oneOfNode) {
r.nodes[o] = n
}
func (r *parseResult) putExtensionRangeNode(e *dpb.DescriptorProto_ExtensionRange, n *rangeNode) {
r.nodes[e] = n
}
func (r *parseResult) putMessageReservedRangeNode(rr *dpb.DescriptorProto_ReservedRange, n *rangeNode) {
r.nodes[rr] = n
}
func (r *parseResult) putEnumNode(e *dpb.EnumDescriptorProto, n *enumNode) {
r.nodes[e] = n
}
func (r *parseResult) putEnumValueNode(e *dpb.EnumValueDescriptorProto, n *enumValueNode) {
r.nodes[e] = n
}
func (r *parseResult) putEnumReservedRangeNode(rr *dpb.EnumDescriptorProto_EnumReservedRange, n *rangeNode) {
r.nodes[rr] = n
}
func (r *parseResult) putServiceNode(s *dpb.ServiceDescriptorProto, n *serviceNode) {
r.nodes[s] = n
}
func (r *parseResult) putMethodNode(m *dpb.MethodDescriptorProto, n *methodNode) {
r.nodes[m] = n
}
func parseProto(filename string, r io.Reader, validate bool) (*parseResult, error) {
lx := newLexer(r)
lx.filename = filename
protoParse(lx)
if lx.err != nil {
if _, ok := lx.err.(ErrorWithSourcePos); ok {
return nil, lx.err
} else {
return nil, ErrorWithSourcePos{Pos: lx.prev(), Underlying: lx.err}
}
}
// parser will not return an error if input is empty, so we
// need to also check if the result is non-nil
if lx.res == nil {
return nil, ErrorWithSourcePos{Pos: lx.prev(), Underlying: errors.New("input is empty")}
}
res, err := createParseResult(filename, lx.res)
if err != nil {
return nil, err
}
if validate {
if err := basicValidate(res); err != nil {
return nil, err
}
}
return res, nil
}
func createParseResult(filename string, file *fileNode) (*parseResult, error) {
res := &parseResult{
nodes: map[proto.Message]node{},
interpretedOptions: map[*optionNode][]int32{},
}
err := res.createFileDescriptor(filename, file)
return res, err
}
func (r *parseResult) createFileDescriptor(filename string, file *fileNode) error {
fd := &dpb.FileDescriptorProto{Name: proto.String(filename)}
r.putFileNode(fd, file)
isProto3 := false
if file.syntax != nil {
isProto3 = file.syntax.syntax.val == "proto3"
// proto2 is the default, so no need to set unless proto3
if isProto3 {
fd.Syntax = proto.String(file.syntax.syntax.val)
}
}
for _, decl := range file.decls {
if decl.enum != nil {
fd.EnumType = append(fd.EnumType, r.asEnumDescriptor(decl.enum))
} else if decl.extend != nil {
r.addExtensions(decl.extend, &fd.Extension, &fd.MessageType, isProto3)
} else if decl.imp != nil {
file.imports = append(file.imports, decl.imp)
index := len(fd.Dependency)
fd.Dependency = append(fd.Dependency, decl.imp.name.val)
if decl.imp.public {
fd.PublicDependency = append(fd.PublicDependency, int32(index))
} else if decl.imp.weak {
fd.WeakDependency = append(fd.WeakDependency, int32(index))
}
} else if decl.message != nil {
fd.MessageType = append(fd.MessageType, r.asMessageDescriptor(decl.message, isProto3))
} else if decl.option != nil {
if fd.Options == nil {
fd.Options = &dpb.FileOptions{}
}
fd.Options.UninterpretedOption = append(fd.Options.UninterpretedOption, r.asUninterpretedOption(decl.option))
} else if decl.service != nil {
fd.Service = append(fd.Service, r.asServiceDescriptor(decl.service))
} else if decl.pkg != nil {
if fd.Package != nil {
return ErrorWithSourcePos{Pos: decl.pkg.start(), Underlying: errors.New("files should have only one package declaration")}
}
file.pkg = decl.pkg
fd.Package = proto.String(decl.pkg.name.val)
}
}
r.fd = fd
return nil
}
func (r *parseResult) asUninterpretedOptions(nodes []*optionNode) []*dpb.UninterpretedOption {
opts := make([]*dpb.UninterpretedOption, len(nodes))
for i, n := range nodes {
opts[i] = r.asUninterpretedOption(n)
}
return opts
}
func (r *parseResult) asUninterpretedOption(node *optionNode) *dpb.UninterpretedOption {
opt := &dpb.UninterpretedOption{Name: r.asUninterpretedOptionName(node.name.parts)}
r.putOptionNode(opt, node)
switch val := node.val.value().(type) {
case bool:
if val {
opt.IdentifierValue = proto.String("true")
} else {
opt.IdentifierValue = proto.String("false")
}
case int64:
opt.NegativeIntValue = proto.Int64(val)
case uint64:
opt.PositiveIntValue = proto.Uint64(val)
case float64:
opt.DoubleValue = proto.Float64(val)
case string:
opt.StringValue = []byte(val)
case identifier:
opt.IdentifierValue = proto.String(string(val))
case []*aggregateEntryNode:
var buf bytes.Buffer
aggToString(val, &buf)
aggStr := buf.String()
opt.AggregateValue = proto.String(aggStr)
}
return opt
}
func (r *parseResult) asUninterpretedOptionName(parts []*optionNamePartNode) []*dpb.UninterpretedOption_NamePart {
ret := make([]*dpb.UninterpretedOption_NamePart, len(parts))
for i, part := range parts {
txt := part.text.val
if !part.isExtension {
txt = part.text.val[part.offset : part.offset+part.length]
}
np := &dpb.UninterpretedOption_NamePart{
NamePart: proto.String(txt),
IsExtension: proto.Bool(part.isExtension),
}
r.putOptionNamePartNode(np, part)
ret[i] = np
}
return ret
}
func (r *parseResult) addExtensions(ext *extendNode, flds *[]*dpb.FieldDescriptorProto, msgs *[]*dpb.DescriptorProto, isProto3 bool) {
extendee := ext.extendee.val
for _, decl := range ext.decls {
if decl.field != nil {
decl.field.extendee = ext
fd := r.asFieldDescriptor(decl.field)
fd.Extendee = proto.String(extendee)
*flds = append(*flds, fd)
} else if decl.group != nil {
decl.group.extendee = ext
fd, md := r.asGroupDescriptors(decl.group, isProto3)
fd.Extendee = proto.String(extendee)
*flds = append(*flds, fd)
*msgs = append(*msgs, md)
}
}
}
func asLabel(lbl *labelNode) *dpb.FieldDescriptorProto_Label {
if lbl == nil {
return nil
}
switch {
case lbl.repeated:
return dpb.FieldDescriptorProto_LABEL_REPEATED.Enum()
case lbl.required:
return dpb.FieldDescriptorProto_LABEL_REQUIRED.Enum()
default:
return dpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum()
}
}
func (r *parseResult) asFieldDescriptor(node *fieldNode) *dpb.FieldDescriptorProto {
fd := newFieldDescriptor(node.name.val, node.fldType.val, int32(node.tag.val), asLabel(node.label))
r.putFieldNode(fd, node)
if len(node.options) > 0 {
fd.Options = &dpb.FieldOptions{UninterpretedOption: r.asUninterpretedOptions(node.options)}
}
return fd
}
func newFieldDescriptor(name string, fieldType string, tag int32, lbl *dpb.FieldDescriptorProto_Label) *dpb.FieldDescriptorProto {
fd := &dpb.FieldDescriptorProto{
Name: proto.String(name),
JsonName: proto.String(internal.JsonName(name)),
Number: proto.Int32(tag),
Label: lbl,
}
switch fieldType {
case "double":
fd.Type = dpb.FieldDescriptorProto_TYPE_DOUBLE.Enum()
case "float":
fd.Type = dpb.FieldDescriptorProto_TYPE_FLOAT.Enum()
case "int32":
fd.Type = dpb.FieldDescriptorProto_TYPE_INT32.Enum()
case "int64":
fd.Type = dpb.FieldDescriptorProto_TYPE_INT64.Enum()
case "uint32":
fd.Type = dpb.FieldDescriptorProto_TYPE_UINT32.Enum()
case "uint64":
fd.Type = dpb.FieldDescriptorProto_TYPE_UINT64.Enum()
case "sint32":
fd.Type = dpb.FieldDescriptorProto_TYPE_SINT32.Enum()
case "sint64":
fd.Type = dpb.FieldDescriptorProto_TYPE_SINT64.Enum()
case "fixed32":
fd.Type = dpb.FieldDescriptorProto_TYPE_FIXED32.Enum()
case "fixed64":
fd.Type = dpb.FieldDescriptorProto_TYPE_FIXED64.Enum()
case "sfixed32":
fd.Type = dpb.FieldDescriptorProto_TYPE_SFIXED32.Enum()
case "sfixed64":
fd.Type = dpb.FieldDescriptorProto_TYPE_SFIXED64.Enum()
case "bool":
fd.Type = dpb.FieldDescriptorProto_TYPE_BOOL.Enum()
case "string":
fd.Type = dpb.FieldDescriptorProto_TYPE_STRING.Enum()
case "bytes":
fd.Type = dpb.FieldDescriptorProto_TYPE_BYTES.Enum()
default:
// NB: we don't have enough info to determine whether this is an enum or a message type,
// so we'll change it to enum later once we can ascertain if it's an enum reference
fd.Type = dpb.FieldDescriptorProto_TYPE_MESSAGE.Enum()
fd.TypeName = proto.String(fieldType)
}
return fd
}
func (r *parseResult) asGroupDescriptors(group *groupNode, isProto3 bool) (*dpb.FieldDescriptorProto, *dpb.DescriptorProto) {
fieldName := strings.ToLower(group.name.val)
fd := &dpb.FieldDescriptorProto{
Name: proto.String(fieldName),
JsonName: proto.String(internal.JsonName(fieldName)),
Number: proto.Int32(int32(group.tag.val)),
Label: asLabel(group.label),
Type: dpb.FieldDescriptorProto_TYPE_GROUP.Enum(),
TypeName: proto.String(group.name.val),
}
r.putFieldNode(fd, group)
md := &dpb.DescriptorProto{Name: proto.String(group.name.val)}
r.putMessageNode(md, group)
r.addMessageDecls(md, &group.reserved, group.decls, isProto3)
return fd, md
}
func (r *parseResult) asMapDescriptors(mapField *mapFieldNode, isProto3 bool) (*dpb.FieldDescriptorProto, *dpb.DescriptorProto) {
var lbl *dpb.FieldDescriptorProto_Label
if !isProto3 {
lbl = dpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum()
}
keyFd := newFieldDescriptor("key", mapField.keyType.val, 1, lbl)
r.putFieldNode(keyFd, mapField.keyField())
valFd := newFieldDescriptor("value", mapField.valueType.val, 2, lbl)
r.putFieldNode(valFd, mapField.valueField())
entryName := internal.InitCap(internal.JsonName(mapField.name.val)) + "Entry"
fd := newFieldDescriptor(mapField.name.val, entryName, int32(mapField.tag.val), dpb.FieldDescriptorProto_LABEL_REPEATED.Enum())
if len(mapField.options) > 0 {
fd.Options = &dpb.FieldOptions{UninterpretedOption: r.asUninterpretedOptions(mapField.options)}
}
r.putFieldNode(fd, mapField)
md := &dpb.DescriptorProto{
Name: proto.String(entryName),
Options: &dpb.MessageOptions{MapEntry: proto.Bool(true)},
Field: []*dpb.FieldDescriptorProto{keyFd, valFd},
}
r.putMessageNode(md, mapField)
return fd, md
}
func (r *parseResult) asExtensionRanges(node *extensionRangeNode) []*dpb.DescriptorProto_ExtensionRange {
opts := r.asUninterpretedOptions(node.options)
ers := make([]*dpb.DescriptorProto_ExtensionRange, len(node.ranges))
for i, rng := range node.ranges {
er := &dpb.DescriptorProto_ExtensionRange{
Start: proto.Int32(rng.st),
End: proto.Int32(rng.en + 1),
}
if len(opts) > 0 {
er.Options = &dpb.ExtensionRangeOptions{UninterpretedOption: opts}
}
r.putExtensionRangeNode(er, rng)
ers[i] = er
}
return ers
}
func (r *parseResult) asEnumValue(ev *enumValueNode) *dpb.EnumValueDescriptorProto {
var num int32
if ev.numberP != nil {
num = int32(ev.numberP.val)
} else {
num = int32(ev.numberN.val)
}
evd := &dpb.EnumValueDescriptorProto{Name: proto.String(ev.name.val), Number: proto.Int32(num)}
r.putEnumValueNode(evd, ev)
if len(ev.options) > 0 {
evd.Options = &dpb.EnumValueOptions{UninterpretedOption: r.asUninterpretedOptions(ev.options)}
}
return evd
}
func (r *parseResult) asMethodDescriptor(node *methodNode) *dpb.MethodDescriptorProto {
md := &dpb.MethodDescriptorProto{
Name: proto.String(node.name.val),
InputType: proto.String(node.input.msgType.val),
OutputType: proto.String(node.output.msgType.val),
}
r.putMethodNode(md, node)
if node.input.streamKeyword != nil {
md.ClientStreaming = proto.Bool(true)
}
if node.output.streamKeyword != nil {
md.ServerStreaming = proto.Bool(true)
}
// protoc always adds a MethodOptions if there are brackets
// We have a non-nil node.options if there are brackets
// We do the same to match protoc as closely as possible
// https://github.com/protocolbuffers/protobuf/blob/0c3f43a6190b77f1f68b7425d1b7e1a8257a8d0c/src/google/protobuf/compiler/parser.cc#L2152
if node.options != nil {
md.Options = &dpb.MethodOptions{UninterpretedOption: r.asUninterpretedOptions(node.options)}
}
return md
}
func (r *parseResult) asEnumDescriptor(en *enumNode) *dpb.EnumDescriptorProto {
ed := &dpb.EnumDescriptorProto{Name: proto.String(en.name.val)}
r.putEnumNode(ed, en)
for _, decl := range en.decls {
if decl.option != nil {
if ed.Options == nil {
ed.Options = &dpb.EnumOptions{}
}
ed.Options.UninterpretedOption = append(ed.Options.UninterpretedOption, r.asUninterpretedOption(decl.option))
} else if decl.value != nil {
ed.Value = append(ed.Value, r.asEnumValue(decl.value))
} else if decl.reserved != nil {
for _, n := range decl.reserved.names {
en.reserved = append(en.reserved, n)
ed.ReservedName = append(ed.ReservedName, n.val)
}
for _, rng := range decl.reserved.ranges {
ed.ReservedRange = append(ed.ReservedRange, r.asEnumReservedRange(rng))
}
}
}
return ed
}
func (r *parseResult) asEnumReservedRange(rng *rangeNode) *dpb.EnumDescriptorProto_EnumReservedRange {
rr := &dpb.EnumDescriptorProto_EnumReservedRange{
Start: proto.Int32(rng.st),
End: proto.Int32(rng.en),
}
r.putEnumReservedRangeNode(rr, rng)
return rr
}
func (r *parseResult) asMessageDescriptor(node *messageNode, isProto3 bool) *dpb.DescriptorProto {
msgd := &dpb.DescriptorProto{Name: proto.String(node.name.val)}
r.putMessageNode(msgd, node)
r.addMessageDecls(msgd, &node.reserved, node.decls, isProto3)
return msgd
}
func (r *parseResult) addMessageDecls(msgd *dpb.DescriptorProto, reservedNames *[]*stringLiteralNode, decls []*messageElement, isProto3 bool) {
for _, decl := range decls {
if decl.enum != nil {
msgd.EnumType = append(msgd.EnumType, r.asEnumDescriptor(decl.enum))
} else if decl.extend != nil {
r.addExtensions(decl.extend, &msgd.Extension, &msgd.NestedType, isProto3)
} else if decl.extensionRange != nil {
msgd.ExtensionRange = append(msgd.ExtensionRange, r.asExtensionRanges(decl.extensionRange)...)
} else if decl.field != nil {
msgd.Field = append(msgd.Field, r.asFieldDescriptor(decl.field))
} else if decl.mapField != nil {
fd, md := r.asMapDescriptors(decl.mapField, isProto3)
msgd.Field = append(msgd.Field, fd)
msgd.NestedType = append(msgd.NestedType, md)
} else if decl.group != nil {
fd, md := r.asGroupDescriptors(decl.group, isProto3)
msgd.Field = append(msgd.Field, fd)
msgd.NestedType = append(msgd.NestedType, md)
} else if decl.oneOf != nil {
oodIndex := len(msgd.OneofDecl)
ood := &dpb.OneofDescriptorProto{Name: proto.String(decl.oneOf.name.val)}
r.putOneOfNode(ood, decl.oneOf)
msgd.OneofDecl = append(msgd.OneofDecl, ood)
for _, oodecl := range decl.oneOf.decls {
if oodecl.option != nil {
if ood.Options == nil {
ood.Options = &dpb.OneofOptions{}
}
ood.Options.UninterpretedOption = append(ood.Options.UninterpretedOption, r.asUninterpretedOption(oodecl.option))
} else if oodecl.field != nil {
fd := r.asFieldDescriptor(oodecl.field)
fd.OneofIndex = proto.Int32(int32(oodIndex))
msgd.Field = append(msgd.Field, fd)
}
}
} else if decl.option != nil {
if msgd.Options == nil {
msgd.Options = &dpb.MessageOptions{}
}
msgd.Options.UninterpretedOption = append(msgd.Options.UninterpretedOption, r.asUninterpretedOption(decl.option))
} else if decl.nested != nil {
msgd.NestedType = append(msgd.NestedType, r.asMessageDescriptor(decl.nested, isProto3))
} else if decl.reserved != nil {
for _, n := range decl.reserved.names {
*reservedNames = append(*reservedNames, n)
msgd.ReservedName = append(msgd.ReservedName, n.val)
}
for _, rng := range decl.reserved.ranges {
msgd.ReservedRange = append(msgd.ReservedRange, r.asMessageReservedRange(rng))
}
}
}
}
func (r *parseResult) asMessageReservedRange(rng *rangeNode) *dpb.DescriptorProto_ReservedRange {
rr := &dpb.DescriptorProto_ReservedRange{
Start: proto.Int32(rng.st),
End: proto.Int32(rng.en + 1),
}
r.putMessageReservedRangeNode(rr, rng)
return rr
}
func (r *parseResult) asServiceDescriptor(svc *serviceNode) *dpb.ServiceDescriptorProto {
sd := &dpb.ServiceDescriptorProto{Name: proto.String(svc.name.val)}
r.putServiceNode(sd, svc)
for _, decl := range svc.decls {
if decl.option != nil {
if sd.Options == nil {
sd.Options = &dpb.ServiceOptions{}
}
sd.Options.UninterpretedOption = append(sd.Options.UninterpretedOption, r.asUninterpretedOption(decl.option))
} else if decl.rpc != nil {
sd.Method = append(sd.Method, r.asMethodDescriptor(decl.rpc))
}
}
return sd
}
func toNameParts(ident *identNode, offset int) []*optionNamePartNode {
parts := strings.Split(ident.val[offset:], ".")
ret := make([]*optionNamePartNode, len(parts))
for i, p := range parts {
ret[i] = &optionNamePartNode{text: ident, offset: offset, length: len(p)}
ret[i].setRange(ident, ident)
offset += len(p) + 1
}
return ret
}
func checkUint64InInt32Range(lex protoLexer, pos *SourcePos, v uint64) {
if v > math.MaxInt32 {
lexError(lex, pos, fmt.Sprintf("constant %d is out of range for int32 (%d to %d)", v, math.MinInt32, math.MaxInt32))
}
}
func checkInt64InInt32Range(lex protoLexer, pos *SourcePos, v int64) {
if v > math.MaxInt32 || v < math.MinInt32 {
lexError(lex, pos, fmt.Sprintf("constant %d is out of range for int32 (%d to %d)", v, math.MinInt32, math.MaxInt32))
}
}
func checkTag(lex protoLexer, pos *SourcePos, v uint64) {
if v > internal.MaxTag {
lexError(lex, pos, fmt.Sprintf("tag number %d is higher than max allowed tag number (%d)", v, internal.MaxTag))
} else if v >= internal.SpecialReservedStart && v <= internal.SpecialReservedEnd {
lexError(lex, pos, fmt.Sprintf("tag number %d is in disallowed reserved range %d-%d", v, internal.SpecialReservedStart, internal.SpecialReservedEnd))
}
}
func aggToString(agg []*aggregateEntryNode, buf *bytes.Buffer) {
buf.WriteString("{")
for _, a := range agg {
buf.WriteString(" ")
buf.WriteString(a.name.value())
if v, ok := a.val.(*aggregateLiteralNode); ok {
aggToString(v.elements, buf)
} else {
buf.WriteString(": ")
elementToString(a.val.value(), buf)
}
}
buf.WriteString(" }")
}
func elementToString(v interface{}, buf *bytes.Buffer) {
switch v := v.(type) {
case bool, int64, uint64, identifier:
fmt.Fprintf(buf, "%v", v)
case float64:
if math.IsInf(v, 1) {
buf.WriteString(": inf")
} else if math.IsInf(v, -1) {
buf.WriteString(": -inf")
} else if math.IsNaN(v) {
buf.WriteString(": nan")
} else {
fmt.Fprintf(buf, ": %v", v)
}
case string:
buf.WriteRune('"')
writeEscapedBytes(buf, []byte(v))
buf.WriteRune('"')
case []valueNode:
buf.WriteString(": [")
first := true
for _, e := range v {
if first {
first = false
} else {
buf.WriteString(", ")
}
elementToString(e.value(), buf)
}
buf.WriteString("]")
case []*aggregateEntryNode:
aggToString(v, buf)
}
}
func writeEscapedBytes(buf *bytes.Buffer, b []byte) {
for _, c := range b {
switch c {
case '\n':
buf.WriteString("\\n")
case '\r':
buf.WriteString("\\r")
case '\t':
buf.WriteString("\\t")
case '"':
buf.WriteString("\\\"")
case '\'':
buf.WriteString("\\'")
case '\\':
buf.WriteString("\\\\")
default:
if c >= 0x20 && c <= 0x7f && c != '"' && c != '\\' {
// simple printable characters
buf.WriteByte(c)
} else {
// use octal escape for all other values
buf.WriteRune('\\')
buf.WriteByte('0' + ((c >> 6) & 0x7))
buf.WriteByte('0' + ((c >> 3) & 0x7))
buf.WriteByte('0' + (c & 0x7))
}
}
}
}
func basicValidate(res *parseResult) error {
fd := res.fd
isProto3 := fd.GetSyntax() == "proto3"
for _, md := range fd.MessageType {
if err := validateMessage(res, isProto3, "", md); err != nil {
return err
}
}
for _, ed := range fd.EnumType {
if err := validateEnum(res, isProto3, "", ed); err != nil {
return err
}
}
for _, fld := range fd.Extension {
if err := validateField(res, isProto3, "", fld); err != nil {
return err
}
}
return nil
}
func validateMessage(res *parseResult, isProto3 bool, prefix string, md *dpb.DescriptorProto) error {
nextPrefix := md.GetName() + "."
for _, fld := range md.Field {
if err := validateField(res, isProto3, nextPrefix, fld); err != nil {
return err
}
}
for _, fld := range md.Extension {
if err := validateField(res, isProto3, nextPrefix, fld); err != nil {
return err
}
}
for _, ed := range md.EnumType {
if err := validateEnum(res, isProto3, nextPrefix, ed); err != nil {
return err
}
}
for _, nmd := range md.NestedType {
if err := validateMessage(res, isProto3, nextPrefix, nmd); err != nil {
return err
}
}
scope := fmt.Sprintf("message %s%s", prefix, md.GetName())
if isProto3 && len(md.ExtensionRange) > 0 {
n := res.getExtensionRangeNode(md.ExtensionRange[0])
return ErrorWithSourcePos{Pos: n.start(), Underlying: fmt.Errorf("%s: extension ranges are not allowed in proto3", scope)}
}
if index, err := findOption(res, scope, md.Options.GetUninterpretedOption(), "map_entry"); err != nil {
return err
} else if index >= 0 {
opt := md.Options.UninterpretedOption[index]
optn := res.getOptionNode(opt)
md.Options.UninterpretedOption = removeOption(md.Options.UninterpretedOption, index)
valid := false
if opt.IdentifierValue != nil {
if opt.GetIdentifierValue() == "true" {
return ErrorWithSourcePos{Pos: optn.getValue().start(), Underlying: fmt.Errorf("%s: map_entry option should not be set explicitly; use map type instead", scope)}
} else if opt.GetIdentifierValue() == "false" {
md.Options.MapEntry = proto.Bool(false)
valid = true
}
}
if !valid {
return ErrorWithSourcePos{Pos: optn.getValue().start(), Underlying: fmt.Errorf("%s: expecting bool value for map_entry option", scope)}
}
}
// reserved ranges should not overlap
rsvd := make(tagRanges, len(md.ReservedRange))
for i, r := range md.ReservedRange {
n := res.getMessageReservedRangeNode(r)
rsvd[i] = tagRange{start: r.GetStart(), end: r.GetEnd(), node: n}
}
sort.Sort(rsvd)
for i := 1; i < len(rsvd); i++ {
if rsvd[i].start < rsvd[i-1].end {
return ErrorWithSourcePos{Pos: rsvd[i].node.start(), Underlying: fmt.Errorf("%s: reserved ranges overlap: %d to %d and %d to %d", scope, rsvd[i-1].start, rsvd[i-1].end-1, rsvd[i].start, rsvd[i].end-1)}
}
}
// extensions ranges should not overlap
exts := make(tagRanges, len(md.ExtensionRange))
for i, r := range md.ExtensionRange {
n := res.getExtensionRangeNode(r)
exts[i] = tagRange{start: r.GetStart(), end: r.GetEnd(), node: n}
}
sort.Sort(exts)
for i := 1; i < len(exts); i++ {
if exts[i].start < exts[i-1].end {
return ErrorWithSourcePos{Pos: exts[i].node.start(), Underlying: fmt.Errorf("%s: extension ranges overlap: %d to %d and %d to %d", scope, exts[i-1].start, exts[i-1].end-1, exts[i].start, exts[i].end-1)}
}
}
// see if any extension range overlaps any reserved range
var i, j int // i indexes rsvd; j indexes exts
for i < len(rsvd) && j < len(exts) {
if rsvd[i].start >= exts[j].start && rsvd[i].start < exts[j].end ||
exts[j].start >= rsvd[i].start && exts[j].start < rsvd[i].end {
var pos *SourcePos
if rsvd[i].start >= exts[j].start && rsvd[i].start < exts[j].end {
pos = rsvd[i].node.start()
} else {
pos = exts[j].node.start()
}
// ranges overlap
return ErrorWithSourcePos{Pos: pos, Underlying: fmt.Errorf("%s: extension range %d to %d overlaps reserved range %d to %d", scope, exts[j].start, exts[j].end-1, rsvd[i].start, rsvd[i].end-1)}
}
if rsvd[i].start < exts[j].start {
i++
} else {
j++
}
}
// now, check that fields don't re-use tags and don't try to use extension
// or reserved ranges or reserved names
rsvdNames := map[string]struct{}{}
for _, n := range md.ReservedName {
rsvdNames[n] = struct{}{}
}
fieldTags := map[int32]string{}
for _, fld := range md.Field {
fn := res.getFieldNode(fld)
if _, ok := rsvdNames[fld.GetName()]; ok {
return ErrorWithSourcePos{Pos: fn.fieldName().start(), Underlying: fmt.Errorf("%s: field %s is using a reserved name", scope, fld.GetName())}
}
if existing := fieldTags[fld.GetNumber()]; existing != "" {
return ErrorWithSourcePos{Pos: fn.fieldTag().start(), Underlying: fmt.Errorf("%s: fields %s and %s both have the same tag %d", scope, existing, fld.GetName(), fld.GetNumber())}
}
fieldTags[fld.GetNumber()] = fld.GetName()
// check reserved ranges
r := sort.Search(len(rsvd), func(index int) bool { return rsvd[index].end > fld.GetNumber() })
if r < len(rsvd) && rsvd[r].start <= fld.GetNumber() {
return ErrorWithSourcePos{Pos: fn.fieldTag().start(), Underlying: fmt.Errorf("%s: field %s is using tag %d which is in reserved range %d to %d", scope, fld.GetName(), fld.GetNumber(), rsvd[r].start, rsvd[r].end-1)}
}
// and check extension ranges
e := sort.Search(len(exts), func(index int) bool { return exts[index].end > fld.GetNumber() })
if e < len(exts) && exts[e].start <= fld.GetNumber() {
return ErrorWithSourcePos{Pos: fn.fieldTag().start(), Underlying: fmt.Errorf("%s: field %s is using tag %d which is in extension range %d to %d", scope, fld.GetName(), fld.GetNumber(), exts[e].start, exts[e].end-1)}
}
}
return nil
}
func validateEnum(res *parseResult, isProto3 bool, prefix string, ed *dpb.EnumDescriptorProto) error {
scope := fmt.Sprintf("enum %s%s", prefix, ed.GetName())
if index, err := findOption(res, scope, ed.Options.GetUninterpretedOption(), "allow_alias"); err != nil {
return err
} else if index >= 0 {
opt := ed.Options.UninterpretedOption[index]
ed.Options.UninterpretedOption = removeOption(ed.Options.UninterpretedOption, index)
valid := false
if opt.IdentifierValue != nil {
if opt.GetIdentifierValue() == "true" {
ed.Options.AllowAlias = proto.Bool(true)
valid = true
} else if opt.GetIdentifierValue() == "false" {
ed.Options.AllowAlias = proto.Bool(false)
valid = true
}
}
if !valid {
optNode := res.getOptionNode(opt)
return ErrorWithSourcePos{Pos: optNode.getValue().start(), Underlying: fmt.Errorf("%s: expecting bool value for allow_alias option", scope)}
}
}
if isProto3 && ed.Value[0].GetNumber() != 0 {
evNode := res.getEnumValueNode(ed.Value[0])
return ErrorWithSourcePos{Pos: evNode.getNumber().start(), Underlying: fmt.Errorf("%s: proto3 requires that first value in enum have numeric value of 0", scope)}
}
if !ed.Options.GetAllowAlias() {
// make sure all value numbers are distinct
vals := map[int32]string{}
for _, evd := range ed.Value {
if existing := vals[evd.GetNumber()]; existing != "" {
evNode := res.getEnumValueNode(evd)
return ErrorWithSourcePos{Pos: evNode.getNumber().start(), Underlying: fmt.Errorf("%s: values %s and %s both have the same numeric value %d; use allow_alias option if intentional", scope, existing, evd.GetName(), evd.GetNumber())}
}
vals[evd.GetNumber()] = evd.GetName()
}
}
// reserved ranges should not overlap
rsvd := make(tagRanges, len(ed.ReservedRange))
for i, r := range ed.ReservedRange {
n := res.getEnumReservedRangeNode(r)
rsvd[i] = tagRange{start: r.GetStart(), end: r.GetEnd(), node: n}
}
sort.Sort(rsvd)
for i := 1; i < len(rsvd); i++ {
if rsvd[i].start <= rsvd[i-1].end {
return ErrorWithSourcePos{Pos: rsvd[i].node.start(), Underlying: fmt.Errorf("%s: reserved ranges overlap: %d to %d and %d to %d", scope, rsvd[i-1].start, rsvd[i-1].end, rsvd[i].start, rsvd[i].end)}
}
}
// now, check that fields don't re-use tags and don't try to use extension
// or reserved ranges or reserved names
rsvdNames := map[string]struct{}{}
for _, n := range ed.ReservedName {
rsvdNames[n] = struct{}{}
}
for _, ev := range ed.Value {
evn := res.getEnumValueNode(ev)
if _, ok := rsvdNames[ev.GetName()]; ok {
return ErrorWithSourcePos{Pos: evn.getName().start(), Underlying: fmt.Errorf("%s: value %s is using a reserved name", scope, ev.GetName())}
}
// check reserved ranges
r := sort.Search(len(rsvd), func(index int) bool { return rsvd[index].end >= ev.GetNumber() })
if r < len(rsvd) && rsvd[r].start <= ev.GetNumber() {
return ErrorWithSourcePos{Pos: evn.getNumber().start(), Underlying: fmt.Errorf("%s: value %s is using number %d which is in reserved range %d to %d", scope, ev.GetName(), ev.GetNumber(), rsvd[r].start, rsvd[r].end)}
}
}
return nil
}
func validateField(res *parseResult, isProto3 bool, prefix string, fld *dpb.FieldDescriptorProto) error {
scope := fmt.Sprintf("field %s%s", prefix, fld.GetName())
node := res.getFieldNode(fld)
if isProto3 {
if fld.GetType() == dpb.FieldDescriptorProto_TYPE_GROUP {
n := node.(*groupNode)
return ErrorWithSourcePos{Pos: n.groupKeyword.start(), Underlying: fmt.Errorf("%s: groups are not allowed in proto3", scope)}
}
if fld.Label != nil && fld.GetLabel() != dpb.FieldDescriptorProto_LABEL_REPEATED {
return ErrorWithSourcePos{Pos: node.fieldLabel().start(), Underlying: fmt.Errorf("%s: field has label %v, but proto3 should omit labels other than 'repeated'", scope, fld.GetLabel())}
}
if index, err := findOption(res, scope, fld.Options.GetUninterpretedOption(), "default"); err != nil {
return err
} else if index >= 0 {
optNode := res.getOptionNode(fld.Options.GetUninterpretedOption()[index])
return ErrorWithSourcePos{Pos: optNode.getName().start(), Underlying: fmt.Errorf("%s: default values are not allowed in proto3", scope)}
}
} else {
if fld.Label == nil && fld.OneofIndex == nil {
return ErrorWithSourcePos{Pos: node.fieldName().start(), Underlying: fmt.Errorf("%s: field has no label, but proto2 must indicate 'optional' or 'required'", scope)}
}
if fld.GetExtendee() != "" && fld.Label != nil && fld.GetLabel() == dpb.FieldDescriptorProto_LABEL_REQUIRED {
return ErrorWithSourcePos{Pos: node.fieldLabel().start(), Underlying: fmt.Errorf("%s: extension fields cannot be 'required'", scope)}
}
}
// finally, set any missing label to optional
if fld.Label == nil {
fld.Label = dpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum()
}
return nil
}
func findOption(res *parseResult, scope string, opts []*dpb.UninterpretedOption, name string) (int, error) {
found := -1
for i, opt := range opts {
if len(opt.Name) != 1 {
continue
}
if opt.Name[0].GetIsExtension() || opt.Name[0].GetNamePart() != name {
continue
}
if found >= 0 {
optNode := res.getOptionNode(opt)
return -1, ErrorWithSourcePos{Pos: optNode.getName().start(), Underlying: fmt.Errorf("%s: option %s cannot be defined more than once", scope, name)}
}
found = i
}
return found, nil
}
func removeOption(uo []*dpb.UninterpretedOption, indexToRemove int) []*dpb.UninterpretedOption {
if indexToRemove == 0 {
return uo[1:]
} else if int(indexToRemove) == len(uo)-1 {
return uo[:len(uo)-1]
} else {
return append(uo[:indexToRemove], uo[indexToRemove+1:]...)
}
}
type tagRange struct {
start int32
end int32
node rangeDecl
}
type tagRanges []tagRange
func (r tagRanges) Len() int {
return len(r)
}
func (r tagRanges) Less(i, j int) bool {
return r[i].start < r[j].start ||
(r[i].start == r[j].start && r[i].end < r[j].end)
}
func (r tagRanges) Swap(i, j int) {
r[i], r[j] = r[j], r[i]
}