1
2
3
4
5
6
7
8 package modindex
9
10 import (
11 "bufio"
12 "bytes"
13 "errors"
14 "fmt"
15 "go/ast"
16 "go/build"
17 "go/parser"
18 "go/scanner"
19 "go/token"
20 "io"
21 "strconv"
22 "strings"
23 "unicode"
24 "unicode/utf8"
25 )
26
27 type importReader struct {
28 b *bufio.Reader
29 buf []byte
30 peek byte
31 err error
32 eof bool
33 nerr int
34 pos token.Position
35 }
36
37 var bom = []byte{0xef, 0xbb, 0xbf}
38
39 func newImportReader(name string, r io.Reader) *importReader {
40 b := bufio.NewReader(r)
41
42
43
44
45 if leadingBytes, err := b.Peek(3); err == nil && bytes.Equal(leadingBytes, bom) {
46 b.Discard(3)
47 }
48 return &importReader{
49 b: b,
50 pos: token.Position{
51 Filename: name,
52 Line: 1,
53 Column: 1,
54 },
55 }
56 }
57
58 func isIdent(c byte) bool {
59 return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf
60 }
61
62 var (
63 errSyntax = errors.New("syntax error")
64 errNUL = errors.New("unexpected NUL in input")
65 )
66
67
68 func (r *importReader) syntaxError() {
69 if r.err == nil {
70 r.err = errSyntax
71 }
72 }
73
74
75
76 func (r *importReader) readByte() byte {
77 c, err := r.b.ReadByte()
78 if err == nil {
79 r.buf = append(r.buf, c)
80 if c == 0 {
81 err = errNUL
82 }
83 }
84 if err != nil {
85 if err == io.EOF {
86 r.eof = true
87 } else if r.err == nil {
88 r.err = err
89 }
90 c = 0
91 }
92 return c
93 }
94
95
96
97 func (r *importReader) readByteNoBuf() byte {
98 var c byte
99 var err error
100 if len(r.buf) > 0 {
101 c = r.buf[0]
102 r.buf = r.buf[1:]
103 } else {
104 c, err = r.b.ReadByte()
105 if err == nil && c == 0 {
106 err = errNUL
107 }
108 }
109
110 if err != nil {
111 if err == io.EOF {
112 r.eof = true
113 } else if r.err == nil {
114 r.err = err
115 }
116 return 0
117 }
118 r.pos.Offset++
119 if c == '\n' {
120 r.pos.Line++
121 r.pos.Column = 1
122 } else {
123 r.pos.Column++
124 }
125 return c
126 }
127
128
129
130 func (r *importReader) peekByte(skipSpace bool) byte {
131 if r.err != nil {
132 if r.nerr++; r.nerr > 10000 {
133 panic("go/build: import reader looping")
134 }
135 return 0
136 }
137
138
139
140
141 c := r.peek
142 if c == 0 {
143 c = r.readByte()
144 }
145 for r.err == nil && !r.eof {
146 if skipSpace {
147
148
149 switch c {
150 case ' ', '\f', '\t', '\r', '\n', ';':
151 c = r.readByte()
152 continue
153
154 case '/':
155 c = r.readByte()
156 if c == '/' {
157 for c != '\n' && r.err == nil && !r.eof {
158 c = r.readByte()
159 }
160 } else if c == '*' {
161 var c1 byte
162 for (c != '*' || c1 != '/') && r.err == nil {
163 if r.eof {
164 r.syntaxError()
165 }
166 c, c1 = c1, r.readByte()
167 }
168 } else {
169 r.syntaxError()
170 }
171 c = r.readByte()
172 continue
173 }
174 }
175 break
176 }
177 r.peek = c
178 return r.peek
179 }
180
181
182 func (r *importReader) nextByte(skipSpace bool) byte {
183 c := r.peekByte(skipSpace)
184 r.peek = 0
185 return c
186 }
187
188 var goEmbed = []byte("go:embed")
189
190
191
192
193 func (r *importReader) findEmbed(first bool) bool {
194
195
196
197
198 startLine := !first
199 var c byte
200 for r.err == nil && !r.eof {
201 c = r.readByteNoBuf()
202 Reswitch:
203 switch c {
204 default:
205 startLine = false
206
207 case '\n':
208 startLine = true
209
210 case ' ', '\t':
211
212
213 case '"':
214 startLine = false
215 for r.err == nil {
216 if r.eof {
217 r.syntaxError()
218 }
219 c = r.readByteNoBuf()
220 if c == '\\' {
221 r.readByteNoBuf()
222 if r.err != nil {
223 r.syntaxError()
224 return false
225 }
226 continue
227 }
228 if c == '"' {
229 c = r.readByteNoBuf()
230 goto Reswitch
231 }
232 }
233 goto Reswitch
234
235 case '`':
236 startLine = false
237 for r.err == nil {
238 if r.eof {
239 r.syntaxError()
240 }
241 c = r.readByteNoBuf()
242 if c == '`' {
243 c = r.readByteNoBuf()
244 goto Reswitch
245 }
246 }
247
248 case '\'':
249 startLine = false
250 for r.err == nil {
251 if r.eof {
252 r.syntaxError()
253 }
254 c = r.readByteNoBuf()
255 if c == '\\' {
256 r.readByteNoBuf()
257 if r.err != nil {
258 r.syntaxError()
259 return false
260 }
261 continue
262 }
263 if c == '\'' {
264 c = r.readByteNoBuf()
265 goto Reswitch
266 }
267 }
268
269 case '/':
270 c = r.readByteNoBuf()
271 switch c {
272 default:
273 startLine = false
274 goto Reswitch
275
276 case '*':
277 var c1 byte
278 for (c != '*' || c1 != '/') && r.err == nil {
279 if r.eof {
280 r.syntaxError()
281 }
282 c, c1 = c1, r.readByteNoBuf()
283 }
284 startLine = false
285
286 case '/':
287 if startLine {
288
289 for i := range goEmbed {
290 c = r.readByteNoBuf()
291 if c != goEmbed[i] {
292 goto SkipSlashSlash
293 }
294 }
295 c = r.readByteNoBuf()
296 if c == ' ' || c == '\t' {
297
298 return true
299 }
300 }
301 SkipSlashSlash:
302 for c != '\n' && r.err == nil && !r.eof {
303 c = r.readByteNoBuf()
304 }
305 startLine = true
306 }
307 }
308 }
309 return false
310 }
311
312
313
314 func (r *importReader) readKeyword(kw string) {
315 r.peekByte(true)
316 for i := 0; i < len(kw); i++ {
317 if r.nextByte(false) != kw[i] {
318 r.syntaxError()
319 return
320 }
321 }
322 if isIdent(r.peekByte(false)) {
323 r.syntaxError()
324 }
325 }
326
327
328
329 func (r *importReader) readIdent() {
330 c := r.peekByte(true)
331 if !isIdent(c) {
332 r.syntaxError()
333 return
334 }
335 for isIdent(r.peekByte(false)) {
336 r.peek = 0
337 }
338 }
339
340
341
342 func (r *importReader) readString() {
343 switch r.nextByte(true) {
344 case '`':
345 for r.err == nil {
346 if r.nextByte(false) == '`' {
347 break
348 }
349 if r.eof {
350 r.syntaxError()
351 }
352 }
353 case '"':
354 for r.err == nil {
355 c := r.nextByte(false)
356 if c == '"' {
357 break
358 }
359 if r.eof || c == '\n' {
360 r.syntaxError()
361 }
362 if c == '\\' {
363 r.nextByte(false)
364 }
365 }
366 default:
367 r.syntaxError()
368 }
369 }
370
371
372
373 func (r *importReader) readImport() {
374 c := r.peekByte(true)
375 if c == '.' {
376 r.peek = 0
377 } else if isIdent(c) {
378 r.readIdent()
379 }
380 r.readString()
381 }
382
383
384
385 func readComments(f io.Reader) ([]byte, error) {
386 r := newImportReader("", f)
387 r.peekByte(true)
388 if r.err == nil && !r.eof {
389
390 r.buf = r.buf[:len(r.buf)-1]
391 }
392 return r.buf, r.err
393 }
394
395
396
397
398
399
400
401
402 func readGoInfo(f io.Reader, info *fileInfo) error {
403 r := newImportReader(info.name, f)
404
405 r.readKeyword("package")
406 r.readIdent()
407 for r.peekByte(true) == 'i' {
408 r.readKeyword("import")
409 if r.peekByte(true) == '(' {
410 r.nextByte(false)
411 for r.peekByte(true) != ')' && r.err == nil {
412 r.readImport()
413 }
414 r.nextByte(false)
415 } else {
416 r.readImport()
417 }
418 }
419
420 info.header = r.buf
421
422
423
424 if r.err == nil && !r.eof {
425 info.header = r.buf[:len(r.buf)-1]
426 }
427
428
429
430 if r.err == errSyntax {
431 r.err = nil
432 for r.err == nil && !r.eof {
433 r.readByte()
434 }
435 info.header = r.buf
436 }
437 if r.err != nil {
438 return r.err
439 }
440
441 if info.fset == nil {
442 return nil
443 }
444
445
446 info.parsed, info.parseErr = parser.ParseFile(info.fset, info.name, info.header, parser.ImportsOnly|parser.ParseComments)
447 if info.parseErr != nil {
448 return nil
449 }
450
451 hasEmbed := false
452 for _, decl := range info.parsed.Decls {
453 d, ok := decl.(*ast.GenDecl)
454 if !ok {
455 continue
456 }
457 for _, dspec := range d.Specs {
458 spec, ok := dspec.(*ast.ImportSpec)
459 if !ok {
460 continue
461 }
462 quoted := spec.Path.Value
463 path, err := strconv.Unquote(quoted)
464 if err != nil {
465 return fmt.Errorf("parser returned invalid quoted string: <%s>", quoted)
466 }
467 if !isValidImport(path) {
468
469
470 info.parseErr = scanner.Error{Pos: info.fset.Position(spec.Pos()), Msg: "invalid import path: " + path}
471 info.imports = nil
472 return nil
473 }
474 if path == "embed" {
475 hasEmbed = true
476 }
477
478 doc := spec.Doc
479 if doc == nil && len(d.Specs) == 1 {
480 doc = d.Doc
481 }
482 info.imports = append(info.imports, fileImport{path, spec.Pos(), doc})
483 }
484 }
485
486
487 for _, group := range info.parsed.Comments {
488 if group.Pos() >= info.parsed.Package {
489 break
490 }
491 for _, c := range group.List {
492 if strings.HasPrefix(c.Text, "//go:") {
493 info.directives = append(info.directives, build.Directive{Text: c.Text, Pos: info.fset.Position(c.Slash)})
494 }
495 }
496 }
497
498
499
500
501
502
503
504
505
506 if hasEmbed {
507 var line []byte
508 for first := true; r.findEmbed(first); first = false {
509 line = line[:0]
510 pos := r.pos
511 for {
512 c := r.readByteNoBuf()
513 if c == '\n' || r.err != nil || r.eof {
514 break
515 }
516 line = append(line, c)
517 }
518
519
520
521 embs, err := parseGoEmbed(string(line), pos)
522 if err == nil {
523 info.embeds = append(info.embeds, embs...)
524 }
525 }
526 }
527
528 return nil
529 }
530
531
532
533
534
535 func isValidImport(s string) bool {
536 const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD"
537 for _, r := range s {
538 if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) {
539 return false
540 }
541 }
542 return s != ""
543 }
544
545
546
547
548
549 func parseGoEmbed(args string, pos token.Position) ([]fileEmbed, error) {
550 trimBytes := func(n int) {
551 pos.Offset += n
552 pos.Column += utf8.RuneCountInString(args[:n])
553 args = args[n:]
554 }
555 trimSpace := func() {
556 trim := strings.TrimLeftFunc(args, unicode.IsSpace)
557 trimBytes(len(args) - len(trim))
558 }
559
560 var list []fileEmbed
561 for trimSpace(); args != ""; trimSpace() {
562 var path string
563 pathPos := pos
564 Switch:
565 switch args[0] {
566 default:
567 i := len(args)
568 for j, c := range args {
569 if unicode.IsSpace(c) {
570 i = j
571 break
572 }
573 }
574 path = args[:i]
575 trimBytes(i)
576
577 case '`':
578 var ok bool
579 path, _, ok = strings.Cut(args[1:], "`")
580 if !ok {
581 return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
582 }
583 trimBytes(1 + len(path) + 1)
584
585 case '"':
586 i := 1
587 for ; i < len(args); i++ {
588 if args[i] == '\\' {
589 i++
590 continue
591 }
592 if args[i] == '"' {
593 q, err := strconv.Unquote(args[:i+1])
594 if err != nil {
595 return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1])
596 }
597 path = q
598 trimBytes(i + 1)
599 break Switch
600 }
601 }
602 if i >= len(args) {
603 return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
604 }
605 }
606
607 if args != "" {
608 r, _ := utf8.DecodeRuneInString(args)
609 if !unicode.IsSpace(r) {
610 return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
611 }
612 }
613 list = append(list, fileEmbed{path, pathPos})
614 }
615 return list, nil
616 }
617
View as plain text