blob: c1509362de6c6d8622fc1514b1c58c9bc07f8eb1 [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001package protoparse
2
3import (
4 "bytes"
5 "fmt"
6 "sort"
7 "strings"
8
9 "github.com/golang/protobuf/proto"
10 dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
11
12 "github.com/jhump/protoreflect/desc"
13 "github.com/jhump/protoreflect/desc/internal"
14)
15
16type linker struct {
17 files map[string]*parseResult
18 descriptorPool map[*dpb.FileDescriptorProto]map[string]proto.Message
19 extensions map[string]map[int32]string
20}
21
22func newLinker(files map[string]*parseResult) *linker {
23 return &linker{files: files}
24}
25
26func (l *linker) linkFiles() (map[string]*desc.FileDescriptor, error) {
27 // First, we put all symbols into a single pool, which lets us ensure there
28 // are no duplicate symbols and will also let us resolve and revise all type
29 // references in next step.
30 if err := l.createDescriptorPool(); err != nil {
31 return nil, err
32 }
33
34 // After we've populated the pool, we can now try to resolve all type
35 // references. All references must be checked for correct type, any fields
36 // with enum types must be corrected (since we parse them as if they are
37 // message references since we don't actually know message or enum until
38 // link time), and references will be re-written to be fully-qualified
39 // references (e.g. start with a dot ".").
40 if err := l.resolveReferences(); err != nil {
41 return nil, err
42 }
43
44 // Now we've validated the descriptors, so we can link them into rich
45 // descriptors. This is a little redundant since that step does similar
46 // checking of symbols. But, without breaking encapsulation (e.g. exporting
47 // a lot of fields from desc package that are currently unexported) or
48 // merging this into the same package, we can't really prevent it.
49 linked, err := l.createdLinkedDescriptors()
50 if err != nil {
51 return nil, err
52 }
53
54 // Now that we have linked descriptors, we can interpret any uninterpreted
55 // options that remain.
56 for _, r := range l.files {
57 fd := linked[r.fd.GetName()]
58 if err := interpretFileOptions(r, richFileDescriptorish{FileDescriptor: fd}); err != nil {
59 return nil, err
60 }
61 }
62
63 return linked, nil
64}
65
66func (l *linker) createDescriptorPool() error {
67 l.descriptorPool = map[*dpb.FileDescriptorProto]map[string]proto.Message{}
68 for _, r := range l.files {
69 fd := r.fd
70 pool := map[string]proto.Message{}
71 l.descriptorPool[fd] = pool
72 prefix := fd.GetPackage()
73 if prefix != "" {
74 prefix += "."
75 }
76 for _, md := range fd.MessageType {
77 if err := addMessageToPool(r, pool, prefix, md); err != nil {
78 return err
79 }
80 }
81 for _, fld := range fd.Extension {
82 if err := addFieldToPool(r, pool, prefix, fld); err != nil {
83 return err
84 }
85 }
86 for _, ed := range fd.EnumType {
87 if err := addEnumToPool(r, pool, prefix, ed); err != nil {
88 return err
89 }
90 }
91 for _, sd := range fd.Service {
92 if err := addServiceToPool(r, pool, prefix, sd); err != nil {
93 return err
94 }
95 }
96 }
97 // try putting everything into a single pool, to ensure there are no duplicates
98 // across files (e.g. same symbol, but declared in two different files)
99 type entry struct {
100 file string
101 msg proto.Message
102 }
103 pool := map[string]entry{}
104 for f, p := range l.descriptorPool {
105 for k, v := range p {
106 if e, ok := pool[k]; ok {
107 desc1 := e.msg
108 file1 := e.file
109 desc2 := v
110 file2 := f.GetName()
111 if file2 < file1 {
112 file1, file2 = file2, file1
113 desc1, desc2 = desc2, desc1
114 }
115 node := l.files[file2].nodes[desc2]
116 return ErrorWithSourcePos{Pos: node.start(), Underlying: fmt.Errorf("duplicate symbol %s: already defined as %s in %q", k, descriptorType(desc1), file1)}
117 }
118 pool[k] = entry{file: f.GetName(), msg: v}
119 }
120 }
121
122 return nil
123}
124
125func addMessageToPool(r *parseResult, pool map[string]proto.Message, prefix string, md *dpb.DescriptorProto) error {
126 fqn := prefix + md.GetName()
127 if err := addToPool(r, pool, fqn, md); err != nil {
128 return err
129 }
130 prefix = fqn + "."
131 for _, fld := range md.Field {
132 if err := addFieldToPool(r, pool, prefix, fld); err != nil {
133 return err
134 }
135 }
136 for _, fld := range md.Extension {
137 if err := addFieldToPool(r, pool, prefix, fld); err != nil {
138 return err
139 }
140 }
141 for _, nmd := range md.NestedType {
142 if err := addMessageToPool(r, pool, prefix, nmd); err != nil {
143 return err
144 }
145 }
146 for _, ed := range md.EnumType {
147 if err := addEnumToPool(r, pool, prefix, ed); err != nil {
148 return err
149 }
150 }
151 return nil
152}
153
154func addFieldToPool(r *parseResult, pool map[string]proto.Message, prefix string, fld *dpb.FieldDescriptorProto) error {
155 fqn := prefix + fld.GetName()
156 return addToPool(r, pool, fqn, fld)
157}
158
159func addEnumToPool(r *parseResult, pool map[string]proto.Message, prefix string, ed *dpb.EnumDescriptorProto) error {
160 fqn := prefix + ed.GetName()
161 if err := addToPool(r, pool, fqn, ed); err != nil {
162 return err
163 }
164 for _, evd := range ed.Value {
165 vfqn := fqn + "." + evd.GetName()
166 if err := addToPool(r, pool, vfqn, evd); err != nil {
167 return err
168 }
169 }
170 return nil
171}
172
173func addServiceToPool(r *parseResult, pool map[string]proto.Message, prefix string, sd *dpb.ServiceDescriptorProto) error {
174 fqn := prefix + sd.GetName()
175 if err := addToPool(r, pool, fqn, sd); err != nil {
176 return err
177 }
178 for _, mtd := range sd.Method {
179 mfqn := fqn + "." + mtd.GetName()
180 if err := addToPool(r, pool, mfqn, mtd); err != nil {
181 return err
182 }
183 }
184 return nil
185}
186
187func addToPool(r *parseResult, pool map[string]proto.Message, fqn string, dsc proto.Message) error {
188 if d, ok := pool[fqn]; ok {
189 node := r.nodes[dsc]
190 return ErrorWithSourcePos{Pos: node.start(), Underlying: fmt.Errorf("duplicate symbol %s: already defined as %s", fqn, descriptorType(d))}
191 }
192 pool[fqn] = dsc
193 return nil
194}
195
196func descriptorType(m proto.Message) string {
197 switch m := m.(type) {
198 case *dpb.DescriptorProto:
199 return "message"
200 case *dpb.DescriptorProto_ExtensionRange:
201 return "extension range"
202 case *dpb.FieldDescriptorProto:
203 if m.GetExtendee() == "" {
204 return "field"
205 } else {
206 return "extension"
207 }
208 case *dpb.EnumDescriptorProto:
209 return "enum"
210 case *dpb.EnumValueDescriptorProto:
211 return "enum value"
212 case *dpb.ServiceDescriptorProto:
213 return "service"
214 case *dpb.MethodDescriptorProto:
215 return "method"
216 case *dpb.FileDescriptorProto:
217 return "file"
218 default:
219 // shouldn't be possible
220 return fmt.Sprintf("%T", m)
221 }
222}
223
224func (l *linker) resolveReferences() error {
225 l.extensions = map[string]map[int32]string{}
226 for _, r := range l.files {
227 fd := r.fd
228 prefix := fd.GetPackage()
229 scopes := []scope{fileScope(fd, l)}
230 if prefix != "" {
231 prefix += "."
232 }
233 if fd.Options != nil {
234 if err := l.resolveOptions(r, fd, "file", fd.GetName(), proto.MessageName(fd.Options), fd.Options.UninterpretedOption, scopes); err != nil {
235 return err
236 }
237 }
238 for _, md := range fd.MessageType {
239 if err := l.resolveMessageTypes(r, fd, prefix, md, scopes); err != nil {
240 return err
241 }
242 }
243 for _, fld := range fd.Extension {
244 if err := l.resolveFieldTypes(r, fd, prefix, fld, scopes); err != nil {
245 return err
246 }
247 }
248 for _, ed := range fd.EnumType {
249 if err := l.resolveEnumTypes(r, fd, prefix, ed, scopes); err != nil {
250 return err
251 }
252 }
253 for _, sd := range fd.Service {
254 if err := l.resolveServiceTypes(r, fd, prefix, sd, scopes); err != nil {
255 return err
256 }
257 }
258 }
259 return nil
260}
261
262func (l *linker) resolveEnumTypes(r *parseResult, fd *dpb.FileDescriptorProto, prefix string, ed *dpb.EnumDescriptorProto, scopes []scope) error {
263 enumFqn := prefix + ed.GetName()
264 if ed.Options != nil {
265 if err := l.resolveOptions(r, fd, "enum", enumFqn, proto.MessageName(ed.Options), ed.Options.UninterpretedOption, scopes); err != nil {
266 return err
267 }
268 }
269 for _, evd := range ed.Value {
270 if evd.Options != nil {
271 evFqn := enumFqn + "." + evd.GetName()
272 if err := l.resolveOptions(r, fd, "enum value", evFqn, proto.MessageName(evd.Options), evd.Options.UninterpretedOption, scopes); err != nil {
273 return err
274 }
275 }
276 }
277 return nil
278}
279
280func (l *linker) resolveMessageTypes(r *parseResult, fd *dpb.FileDescriptorProto, prefix string, md *dpb.DescriptorProto, scopes []scope) error {
281 fqn := prefix + md.GetName()
282 scope := messageScope(fqn, isProto3(fd), l.descriptorPool[fd])
283 scopes = append(scopes, scope)
284 prefix = fqn + "."
285
286 if md.Options != nil {
287 if err := l.resolveOptions(r, fd, "message", fqn, proto.MessageName(md.Options), md.Options.UninterpretedOption, scopes); err != nil {
288 return err
289 }
290 }
291
292 for _, nmd := range md.NestedType {
293 if err := l.resolveMessageTypes(r, fd, prefix, nmd, scopes); err != nil {
294 return err
295 }
296 }
297 for _, ned := range md.EnumType {
298 if err := l.resolveEnumTypes(r, fd, prefix, ned, scopes); err != nil {
299 return err
300 }
301 }
302 for _, fld := range md.Field {
303 if err := l.resolveFieldTypes(r, fd, prefix, fld, scopes); err != nil {
304 return err
305 }
306 }
307 for _, fld := range md.Extension {
308 if err := l.resolveFieldTypes(r, fd, prefix, fld, scopes); err != nil {
309 return err
310 }
311 }
312 for _, er := range md.ExtensionRange {
313 if er.Options != nil {
314 erName := fmt.Sprintf("%s:%d-%d", fqn, er.GetStart(), er.GetEnd()-1)
315 if err := l.resolveOptions(r, fd, "extension range", erName, proto.MessageName(er.Options), er.Options.UninterpretedOption, scopes); err != nil {
316 return err
317 }
318 }
319 }
320 return nil
321}
322
323func (l *linker) resolveFieldTypes(r *parseResult, fd *dpb.FileDescriptorProto, prefix string, fld *dpb.FieldDescriptorProto, scopes []scope) error {
324 thisName := prefix + fld.GetName()
325 scope := fmt.Sprintf("field %s", thisName)
326 node := r.getFieldNode(fld)
327 elemType := "field"
328 if fld.GetExtendee() != "" {
329 fqn, dsc, _ := l.resolve(fd, fld.GetExtendee(), isMessage, scopes)
330 if dsc == nil {
331 return ErrorWithSourcePos{Pos: node.fieldExtendee().start(), Underlying: fmt.Errorf("unknown extendee type %s", fld.GetExtendee())}
332 }
333 extd, ok := dsc.(*dpb.DescriptorProto)
334 if !ok {
335 otherType := descriptorType(dsc)
336 return ErrorWithSourcePos{Pos: node.fieldExtendee().start(), Underlying: fmt.Errorf("extendee is invalid: %s is a %s, not a message", fqn, otherType)}
337 }
338 fld.Extendee = proto.String("." + fqn)
339 // make sure the tag number is in range
340 found := false
341 tag := fld.GetNumber()
342 for _, rng := range extd.ExtensionRange {
343 if tag >= rng.GetStart() && tag < rng.GetEnd() {
344 found = true
345 break
346 }
347 }
348 if !found {
349 return ErrorWithSourcePos{Pos: node.fieldTag().start(), Underlying: fmt.Errorf("%s: tag %d is not in valid range for extended type %s", scope, tag, fqn)}
350 }
351 // make sure tag is not a duplicate
352 usedExtTags := l.extensions[fqn]
353 if usedExtTags == nil {
354 usedExtTags = map[int32]string{}
355 l.extensions[fqn] = usedExtTags
356 }
357 if other := usedExtTags[fld.GetNumber()]; other != "" {
358 return ErrorWithSourcePos{Pos: node.fieldTag().start(), Underlying: fmt.Errorf("%s: duplicate extension: %s and %s are both using tag %d", scope, other, thisName, fld.GetNumber())}
359 }
360 usedExtTags[fld.GetNumber()] = thisName
361 elemType = "extension"
362 }
363
364 if fld.Options != nil {
365 if err := l.resolveOptions(r, fd, elemType, thisName, proto.MessageName(fld.Options), fld.Options.UninterpretedOption, scopes); err != nil {
366 return err
367 }
368 }
369
370 if fld.GetTypeName() == "" {
371 // scalar type; no further resolution required
372 return nil
373 }
374
375 fqn, dsc, proto3 := l.resolve(fd, fld.GetTypeName(), isType, scopes)
376 if dsc == nil {
377 return ErrorWithSourcePos{Pos: node.fieldType().start(), Underlying: fmt.Errorf("%s: unknown type %s", scope, fld.GetTypeName())}
378 }
379 switch dsc := dsc.(type) {
380 case *dpb.DescriptorProto:
381 fld.TypeName = proto.String("." + fqn)
382 case *dpb.EnumDescriptorProto:
383 if fld.GetExtendee() == "" && isProto3(fd) && !proto3 {
384 // fields in a proto3 message cannot refer to proto2 enums
385 return ErrorWithSourcePos{Pos: node.fieldType().start(), Underlying: fmt.Errorf("%s: cannot use proto2 enum %s in a proto3 message", scope, fld.GetTypeName())}
386 }
387 fld.TypeName = proto.String("." + fqn)
388 // the type was tentatively set to message, but now we know it's actually an enum
389 fld.Type = dpb.FieldDescriptorProto_TYPE_ENUM.Enum()
390 default:
391 otherType := descriptorType(dsc)
392 return ErrorWithSourcePos{Pos: node.fieldType().start(), Underlying: fmt.Errorf("%s: invalid type: %s is a %s, not a message or enum", scope, fqn, otherType)}
393 }
394 return nil
395}
396
397func (l *linker) resolveServiceTypes(r *parseResult, fd *dpb.FileDescriptorProto, prefix string, sd *dpb.ServiceDescriptorProto, scopes []scope) error {
398 thisName := prefix + sd.GetName()
399 if sd.Options != nil {
400 if err := l.resolveOptions(r, fd, "service", thisName, proto.MessageName(sd.Options), sd.Options.UninterpretedOption, scopes); err != nil {
401 return err
402 }
403 }
404
405 for _, mtd := range sd.Method {
406 if mtd.Options != nil {
407 if err := l.resolveOptions(r, fd, "method", thisName+"."+mtd.GetName(), proto.MessageName(mtd.Options), mtd.Options.UninterpretedOption, scopes); err != nil {
408 return err
409 }
410 }
411 scope := fmt.Sprintf("method %s.%s", thisName, mtd.GetName())
412 node := r.getMethodNode(mtd)
413 fqn, dsc, _ := l.resolve(fd, mtd.GetInputType(), isMessage, scopes)
414 if dsc == nil {
415 return ErrorWithSourcePos{Pos: node.getInputType().start(), Underlying: fmt.Errorf("%s: unknown request type %s", scope, mtd.GetInputType())}
416 }
417 if _, ok := dsc.(*dpb.DescriptorProto); !ok {
418 otherType := descriptorType(dsc)
419 return ErrorWithSourcePos{Pos: node.getInputType().start(), Underlying: fmt.Errorf("%s: invalid request type: %s is a %s, not a message", scope, fqn, otherType)}
420 }
421 mtd.InputType = proto.String("." + fqn)
422
423 fqn, dsc, _ = l.resolve(fd, mtd.GetOutputType(), isMessage, scopes)
424 if dsc == nil {
425 return ErrorWithSourcePos{Pos: node.getOutputType().start(), Underlying: fmt.Errorf("%s: unknown response type %s", scope, mtd.GetOutputType())}
426 }
427 if _, ok := dsc.(*dpb.DescriptorProto); !ok {
428 otherType := descriptorType(dsc)
429 return ErrorWithSourcePos{Pos: node.getOutputType().start(), Underlying: fmt.Errorf("%s: invalid response type: %s is a %s, not a message", scope, fqn, otherType)}
430 }
431 mtd.OutputType = proto.String("." + fqn)
432 }
433 return nil
434}
435
436func (l *linker) resolveOptions(r *parseResult, fd *dpb.FileDescriptorProto, elemType, elemName, optType string, opts []*dpb.UninterpretedOption, scopes []scope) error {
437 var scope string
438 if elemType != "file" {
439 scope = fmt.Sprintf("%s %s: ", elemType, elemName)
440 }
441 for _, opt := range opts {
442 for _, nm := range opt.Name {
443 if nm.GetIsExtension() {
444 node := r.getOptionNamePartNode(nm)
445 fqn, dsc, _ := l.resolve(fd, nm.GetNamePart(), isField, scopes)
446 if dsc == nil {
447 return ErrorWithSourcePos{Pos: node.start(), Underlying: fmt.Errorf("%sunknown extension %s", scope, nm.GetNamePart())}
448 }
449 if ext, ok := dsc.(*dpb.FieldDescriptorProto); !ok {
450 otherType := descriptorType(dsc)
451 return ErrorWithSourcePos{Pos: node.start(), Underlying: fmt.Errorf("%sinvalid extension: %s is a %s, not an extension", scope, nm.GetNamePart(), otherType)}
452 } else if ext.GetExtendee() == "" {
453 return ErrorWithSourcePos{Pos: node.start(), Underlying: fmt.Errorf("%sinvalid extension: %s is a field but not an extension", scope, nm.GetNamePart())}
454 }
455 nm.NamePart = proto.String("." + fqn)
456 }
457 }
458 }
459 return nil
460}
461
462func (l *linker) resolve(fd *dpb.FileDescriptorProto, name string, allowed func(proto.Message) bool, scopes []scope) (fqn string, element proto.Message, proto3 bool) {
463 if strings.HasPrefix(name, ".") {
464 // already fully-qualified
465 d, proto3 := l.findSymbol(fd, name[1:], false, map[*dpb.FileDescriptorProto]struct{}{})
466 if d != nil {
467 return name[1:], d, proto3
468 }
469 } else {
470 // unqualified, so we look in the enclosing (last) scope first and move
471 // towards outermost (first) scope, trying to resolve the symbol
472 var bestGuess proto.Message
473 var bestGuessFqn string
474 var bestGuessProto3 bool
475 for i := len(scopes) - 1; i >= 0; i-- {
476 fqn, d, proto3 := scopes[i](name)
477 if d != nil {
478 if allowed(d) {
479 return fqn, d, proto3
480 } else if bestGuess == nil {
481 bestGuess = d
482 bestGuessFqn = fqn
483 bestGuessProto3 = proto3
484 }
485 }
486 }
487 // we return best guess, even though it was not an allowed kind of
488 // descriptor, so caller can print a better error message (e.g.
489 // indicating that the name was found but that it's the wrong type)
490 return bestGuessFqn, bestGuess, bestGuessProto3
491 }
492 return "", nil, false
493}
494
495func isField(m proto.Message) bool {
496 _, ok := m.(*dpb.FieldDescriptorProto)
497 return ok
498}
499
500func isMessage(m proto.Message) bool {
501 _, ok := m.(*dpb.DescriptorProto)
502 return ok
503}
504
505func isType(m proto.Message) bool {
506 switch m.(type) {
507 case *dpb.DescriptorProto, *dpb.EnumDescriptorProto:
508 return true
509 }
510 return false
511}
512
513// scope represents a lexical scope in a proto file in which messages and enums
514// can be declared.
515type scope func(symbol string) (fqn string, element proto.Message, proto3 bool)
516
517func fileScope(fd *dpb.FileDescriptorProto, l *linker) scope {
518 // we search symbols in this file, but also symbols in other files that have
519 // the same package as this file or a "parent" package (in protobuf,
520 // packages are a hierarchy like C++ namespaces)
521 prefixes := internal.CreatePrefixList(fd.GetPackage())
522 return func(name string) (string, proto.Message, bool) {
523 for _, prefix := range prefixes {
524 var n string
525 if prefix == "" {
526 n = name
527 } else {
528 n = prefix + "." + name
529 }
530 d, proto3 := l.findSymbol(fd, n, false, map[*dpb.FileDescriptorProto]struct{}{})
531 if d != nil {
532 return n, d, proto3
533 }
534 }
535 return "", nil, false
536 }
537}
538
539func messageScope(messageName string, proto3 bool, filePool map[string]proto.Message) scope {
540 return func(name string) (string, proto.Message, bool) {
541 n := messageName + "." + name
542 if d, ok := filePool[n]; ok {
543 return n, d, proto3
544 }
545 return "", nil, false
546 }
547}
548
549func (l *linker) findSymbol(fd *dpb.FileDescriptorProto, name string, public bool, checked map[*dpb.FileDescriptorProto]struct{}) (element proto.Message, proto3 bool) {
550 if _, ok := checked[fd]; ok {
551 // already checked this one
552 return nil, false
553 }
554 checked[fd] = struct{}{}
555 d := l.descriptorPool[fd][name]
556 if d != nil {
557 return d, isProto3(fd)
558 }
559
560 // When public = false, we are searching only directly imported symbols. But we
561 // also need to search transitive public imports due to semantics of public imports.
562 if public {
563 for _, depIndex := range fd.PublicDependency {
564 dep := fd.Dependency[depIndex]
565 depres := l.files[dep]
566 if depres == nil {
567 // we'll catch this error later
568 continue
569 }
570 if d, proto3 := l.findSymbol(depres.fd, name, true, checked); d != nil {
571 return d, proto3
572 }
573 }
574 } else {
575 for _, dep := range fd.Dependency {
576 depres := l.files[dep]
577 if depres == nil {
578 // we'll catch this error later
579 continue
580 }
581 if d, proto3 := l.findSymbol(depres.fd, name, true, checked); d != nil {
582 return d, proto3
583 }
584 }
585 }
586
587 return nil, false
588}
589
590func isProto3(fd *dpb.FileDescriptorProto) bool {
591 return fd.GetSyntax() == "proto3"
592}
593
594func (l *linker) createdLinkedDescriptors() (map[string]*desc.FileDescriptor, error) {
595 names := make([]string, 0, len(l.files))
596 for name := range l.files {
597 names = append(names, name)
598 }
599 sort.Strings(names)
600 linked := map[string]*desc.FileDescriptor{}
601 for _, name := range names {
602 if _, err := l.linkFile(name, nil, linked); err != nil {
603 return nil, err
604 }
605 }
606 return linked, nil
607}
608
609func (l *linker) linkFile(name string, seen []string, linked map[string]*desc.FileDescriptor) (*desc.FileDescriptor, error) {
610 // check for import cycle
611 for _, s := range seen {
612 if name == s {
613 var msg bytes.Buffer
614 first := true
615 for _, s := range seen {
616 if first {
617 first = false
618 } else {
619 msg.WriteString(" -> ")
620 }
621 fmt.Fprintf(&msg, "%q", s)
622 }
623 fmt.Fprintf(&msg, " -> %q", name)
624 return nil, fmt.Errorf("cycle found in imports: %s", msg.String())
625 }
626 }
627 seen = append(seen, name)
628
629 if lfd, ok := linked[name]; ok {
630 // already linked
631 return lfd, nil
632 }
633 r := l.files[name]
634 if r == nil {
635 importer := seen[len(seen)-2] // len-1 is *this* file, before that is the one that imported it
636 return nil, fmt.Errorf("no descriptor found for %q, imported by %q", name, importer)
637 }
638 var deps []*desc.FileDescriptor
639 for _, dep := range r.fd.Dependency {
640 ldep, err := l.linkFile(dep, seen, linked)
641 if err != nil {
642 return nil, err
643 }
644 deps = append(deps, ldep)
645 }
646 lfd, err := desc.CreateFileDescriptor(r.fd, deps...)
647 if err != nil {
648 return nil, fmt.Errorf("error linking %q: %s", name, err)
649 }
650 linked[name] = lfd
651 return lfd, nil
652}