blob: 538820c3c10dbb0f979d283f3323f7d93e825a71 [file] [log] [blame]
khenaidooefff76e2021-12-15 16:51:30 -05001package desc
2
3import (
4 "errors"
5 "fmt"
6 "strings"
7
8 dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
9
10 "github.com/jhump/protoreflect/desc/internal"
11 intn "github.com/jhump/protoreflect/internal"
12)
13
14// CreateFileDescriptor instantiates a new file descriptor for the given descriptor proto.
15// The file's direct dependencies must be provided. If the given dependencies do not include
16// all of the file's dependencies or if the contents of the descriptors are internally
17// inconsistent (e.g. contain unresolvable symbols) then an error is returned.
18func CreateFileDescriptor(fd *dpb.FileDescriptorProto, deps ...*FileDescriptor) (*FileDescriptor, error) {
19 return createFileDescriptor(fd, deps, nil)
20}
21
22func createFileDescriptor(fd *dpb.FileDescriptorProto, deps []*FileDescriptor, r *ImportResolver) (*FileDescriptor, error) {
23 ret := &FileDescriptor{
24 proto: fd,
25 symbols: map[string]Descriptor{},
26 fieldIndex: map[string]map[int32]*FieldDescriptor{},
27 }
28 pkg := fd.GetPackage()
29
30 // populate references to file descriptor dependencies
31 files := map[string]*FileDescriptor{}
32 for _, f := range deps {
33 files[f.proto.GetName()] = f
34 }
35 ret.deps = make([]*FileDescriptor, len(fd.GetDependency()))
36 for i, d := range fd.GetDependency() {
37 resolved := r.ResolveImport(fd.GetName(), d)
38 ret.deps[i] = files[resolved]
39 if ret.deps[i] == nil {
40 if resolved != d {
41 ret.deps[i] = files[d]
42 }
43 if ret.deps[i] == nil {
44 return nil, intn.ErrNoSuchFile(d)
45 }
46 }
47 }
48 ret.publicDeps = make([]*FileDescriptor, len(fd.GetPublicDependency()))
49 for i, pd := range fd.GetPublicDependency() {
50 ret.publicDeps[i] = ret.deps[pd]
51 }
52 ret.weakDeps = make([]*FileDescriptor, len(fd.GetWeakDependency()))
53 for i, wd := range fd.GetWeakDependency() {
54 ret.weakDeps[i] = ret.deps[wd]
55 }
56 ret.isProto3 = fd.GetSyntax() == "proto3"
57
58 // populate all tables of child descriptors
59 for _, m := range fd.GetMessageType() {
60 md, n := createMessageDescriptor(ret, ret, pkg, m, ret.symbols)
61 ret.symbols[n] = md
62 ret.messages = append(ret.messages, md)
63 }
64 for _, e := range fd.GetEnumType() {
65 ed, n := createEnumDescriptor(ret, ret, pkg, e, ret.symbols)
66 ret.symbols[n] = ed
67 ret.enums = append(ret.enums, ed)
68 }
69 for _, ex := range fd.GetExtension() {
70 exd, n := createFieldDescriptor(ret, ret, pkg, ex)
71 ret.symbols[n] = exd
72 ret.extensions = append(ret.extensions, exd)
73 }
74 for _, s := range fd.GetService() {
75 sd, n := createServiceDescriptor(ret, pkg, s, ret.symbols)
76 ret.symbols[n] = sd
77 ret.services = append(ret.services, sd)
78 }
79
80 ret.sourceInfo = internal.CreateSourceInfoMap(fd)
81 ret.sourceInfoRecomputeFunc = ret.recomputeSourceInfo
82
83 // now we can resolve all type references and source code info
84 scopes := []scope{fileScope(ret)}
85 path := make([]int32, 1, 8)
86 path[0] = internal.File_messagesTag
87 for i, md := range ret.messages {
88 if err := md.resolve(append(path, int32(i)), scopes); err != nil {
89 return nil, err
90 }
91 }
92 path[0] = internal.File_enumsTag
93 for i, ed := range ret.enums {
94 ed.resolve(append(path, int32(i)))
95 }
96 path[0] = internal.File_extensionsTag
97 for i, exd := range ret.extensions {
98 if err := exd.resolve(append(path, int32(i)), scopes); err != nil {
99 return nil, err
100 }
101 }
102 path[0] = internal.File_servicesTag
103 for i, sd := range ret.services {
104 if err := sd.resolve(append(path, int32(i)), scopes); err != nil {
105 return nil, err
106 }
107 }
108
109 return ret, nil
110}
111
112// CreateFileDescriptors constructs a set of descriptors, one for each of the
113// given descriptor protos. The given set of descriptor protos must include all
114// transitive dependencies for every file.
115func CreateFileDescriptors(fds []*dpb.FileDescriptorProto) (map[string]*FileDescriptor, error) {
116 return createFileDescriptors(fds, nil)
117}
118
119func createFileDescriptors(fds []*dpb.FileDescriptorProto, r *ImportResolver) (map[string]*FileDescriptor, error) {
120 if len(fds) == 0 {
121 return nil, nil
122 }
123 files := map[string]*dpb.FileDescriptorProto{}
124 resolved := map[string]*FileDescriptor{}
125 var name string
126 for _, fd := range fds {
127 name = fd.GetName()
128 files[name] = fd
129 }
130 for _, fd := range fds {
131 _, err := createFromSet(fd.GetName(), r, nil, files, resolved)
132 if err != nil {
133 return nil, err
134 }
135 }
136 return resolved, nil
137}
138
139// ToFileDescriptorSet creates a FileDescriptorSet proto that contains all of the given
140// file descriptors and their transitive dependencies. The files are topologically sorted
141// so that a file will always appear after its dependencies.
142func ToFileDescriptorSet(fds ...*FileDescriptor) *dpb.FileDescriptorSet {
143 var fdps []*dpb.FileDescriptorProto
144 addAllFiles(fds, &fdps, map[string]struct{}{})
145 return &dpb.FileDescriptorSet{File: fdps}
146}
147
148func addAllFiles(src []*FileDescriptor, results *[]*dpb.FileDescriptorProto, seen map[string]struct{}) {
149 for _, fd := range src {
150 if _, ok := seen[fd.GetName()]; ok {
151 continue
152 }
153 seen[fd.GetName()] = struct{}{}
154 addAllFiles(fd.GetDependencies(), results, seen)
155 *results = append(*results, fd.AsFileDescriptorProto())
156 }
157}
158
159// CreateFileDescriptorFromSet creates a descriptor from the given file descriptor set. The
160// set's *last* file will be the returned descriptor. The set's remaining files must comprise
161// the full set of transitive dependencies of that last file. This is the same format and
162// order used by protoc when emitting a FileDescriptorSet file with an invocation like so:
163// protoc --descriptor_set_out=./test.protoset --include_imports -I. test.proto
164func CreateFileDescriptorFromSet(fds *dpb.FileDescriptorSet) (*FileDescriptor, error) {
165 return createFileDescriptorFromSet(fds, nil)
166}
167
168func createFileDescriptorFromSet(fds *dpb.FileDescriptorSet, r *ImportResolver) (*FileDescriptor, error) {
169 result, err := createFileDescriptorsFromSet(fds, r)
170 if err != nil {
171 return nil, err
172 }
173 files := fds.GetFile()
174 lastFilename := files[len(files)-1].GetName()
175 return result[lastFilename], nil
176}
177
178// CreateFileDescriptorsFromSet creates file descriptors from the given file descriptor set.
179// The returned map includes all files in the set, keyed b name. The set must include the
180// full set of transitive dependencies for all files therein or else a link error will occur
181// and be returned instead of the slice of descriptors. This is the same format used by
182// protoc when a FileDescriptorSet file with an invocation like so:
183// protoc --descriptor_set_out=./test.protoset --include_imports -I. test.proto
184func CreateFileDescriptorsFromSet(fds *dpb.FileDescriptorSet) (map[string]*FileDescriptor, error) {
185 return createFileDescriptorsFromSet(fds, nil)
186}
187
188func createFileDescriptorsFromSet(fds *dpb.FileDescriptorSet, r *ImportResolver) (map[string]*FileDescriptor, error) {
189 files := fds.GetFile()
190 if len(files) == 0 {
191 return nil, errors.New("file descriptor set is empty")
192 }
193 return createFileDescriptors(files, r)
194}
195
196// createFromSet creates a descriptor for the given filename. It recursively
197// creates descriptors for the given file's dependencies.
198func createFromSet(filename string, r *ImportResolver, seen []string, files map[string]*dpb.FileDescriptorProto, resolved map[string]*FileDescriptor) (*FileDescriptor, error) {
199 for _, s := range seen {
200 if filename == s {
201 return nil, fmt.Errorf("cycle in imports: %s", strings.Join(append(seen, filename), " -> "))
202 }
203 }
204 seen = append(seen, filename)
205
206 if d, ok := resolved[filename]; ok {
207 return d, nil
208 }
209 fdp := files[filename]
210 if fdp == nil {
211 return nil, intn.ErrNoSuchFile(filename)
212 }
213 deps := make([]*FileDescriptor, len(fdp.GetDependency()))
214 for i, depName := range fdp.GetDependency() {
215 resolvedDep := r.ResolveImport(filename, depName)
216 dep, err := createFromSet(resolvedDep, r, seen, files, resolved)
217 if _, ok := err.(intn.ErrNoSuchFile); ok && resolvedDep != depName {
218 dep, err = createFromSet(depName, r, seen, files, resolved)
219 }
220 if err != nil {
221 return nil, err
222 }
223 deps[i] = dep
224 }
225 d, err := createFileDescriptor(fdp, deps, r)
226 if err != nil {
227 return nil, err
228 }
229 resolved[filename] = d
230 return d, nil
231}