]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/go/internal/modindex/scan.go
cmd/go, go/build: parse directives in file headers
[gostls13.git] / src / cmd / go / internal / modindex / scan.go
1 // Copyright 2022 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package modindex
6
7 import (
8         "cmd/go/internal/base"
9         "cmd/go/internal/fsys"
10         "cmd/go/internal/str"
11         "encoding/json"
12         "errors"
13         "fmt"
14         "go/build"
15         "go/doc"
16         "go/scanner"
17         "go/token"
18         "io/fs"
19         "path/filepath"
20         "strings"
21 )
22
23 // moduleWalkErr returns filepath.SkipDir if the directory isn't relevant
24 // when indexing a module or generating a filehash, ErrNotIndexed,
25 // if the module shouldn't be indexed, and nil otherwise.
26 func moduleWalkErr(root string, path string, info fs.FileInfo, err error) error {
27         if err != nil {
28                 return ErrNotIndexed
29         }
30         // stop at module boundaries
31         if info.IsDir() && path != root {
32                 if fi, err := fsys.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
33                         return filepath.SkipDir
34                 }
35         }
36         if info.Mode()&fs.ModeSymlink != 0 {
37                 if target, err := fsys.Stat(path); err == nil && target.IsDir() {
38                         // return an error to make the module hash invalid.
39                         // Symlink directories in modules are tricky, so we won't index
40                         // modules that contain them.
41                         // TODO(matloob): perhaps don't return this error if the symlink leads to
42                         // a directory with a go.mod file.
43                         return ErrNotIndexed
44                 }
45         }
46         return nil
47 }
48
49 // indexModule indexes the module at the given directory and returns its
50 // encoded representation. It returns ErrNotIndexed if the module can't
51 // be indexed because it contains symlinks.
52 func indexModule(modroot string) ([]byte, error) {
53         fsys.Trace("indexModule", modroot)
54         var packages []*rawPackage
55
56         // If the root itself is a symlink to a directory,
57         // we want to follow it (see https://go.dev/issue/50807).
58         // Add a trailing separator to force that to happen.
59         root := str.WithFilePathSeparator(modroot)
60         err := fsys.Walk(root, func(path string, info fs.FileInfo, err error) error {
61                 if err := moduleWalkErr(root, path, info, err); err != nil {
62                         return err
63                 }
64
65                 if !info.IsDir() {
66                         return nil
67                 }
68                 if !strings.HasPrefix(path, root) {
69                         panic(fmt.Errorf("path %v in walk doesn't have modroot %v as prefix", path, modroot))
70                 }
71                 rel := path[len(root):]
72                 packages = append(packages, importRaw(modroot, rel))
73                 return nil
74         })
75         if err != nil {
76                 return nil, err
77         }
78         return encodeModuleBytes(packages), nil
79 }
80
81 // indexPackage indexes the package at the given directory and returns its
82 // encoded representation. It returns ErrNotIndexed if the package can't
83 // be indexed.
84 func indexPackage(modroot, pkgdir string) []byte {
85         fsys.Trace("indexPackage", pkgdir)
86         p := importRaw(modroot, relPath(pkgdir, modroot))
87         return encodePackageBytes(p)
88 }
89
90 // rawPackage holds the information from each package that's needed to
91 // fill a build.Package once the context is available.
92 type rawPackage struct {
93         error string
94         dir   string // directory containing package sources, relative to the module root
95
96         // Source files
97         sourceFiles []*rawFile
98 }
99
100 type parseError struct {
101         ErrorList   *scanner.ErrorList
102         ErrorString string
103 }
104
105 // parseErrorToString converts the error from parsing the file into a string
106 // representation. A nil error is converted to an empty string, and all other
107 // errors are converted to a JSON-marshalled parseError struct, with ErrorList
108 // set for errors of type scanner.ErrorList, and ErrorString set to the error's
109 // string representation for all other errors.
110 func parseErrorToString(err error) string {
111         if err == nil {
112                 return ""
113         }
114         var p parseError
115         if e, ok := err.(scanner.ErrorList); ok {
116                 p.ErrorList = &e
117         } else {
118                 p.ErrorString = e.Error()
119         }
120         s, err := json.Marshal(p)
121         if err != nil {
122                 panic(err) // This should be impossible because scanner.Error contains only strings and ints.
123         }
124         return string(s)
125 }
126
127 // parseErrorFromString converts a string produced by parseErrorToString back
128 // to an error.  An empty string is converted to a nil error, and all
129 // other strings are expected to be JSON-marshalled parseError structs.
130 // The two functions are meant to preserve the structure of an
131 // error of type scanner.ErrorList in a round trip, but may not preserve the
132 // structure of other errors.
133 func parseErrorFromString(s string) error {
134         if s == "" {
135                 return nil
136         }
137         var p parseError
138         if err := json.Unmarshal([]byte(s), &p); err != nil {
139                 base.Fatalf(`go: invalid parse error value in index: %q. This indicates a corrupted index. Run "go clean -cache" to reset the module cache.`, s)
140         }
141         if p.ErrorList != nil {
142                 return *p.ErrorList
143         }
144         return errors.New(p.ErrorString)
145 }
146
147 // rawFile is the struct representation of the file holding all
148 // information in its fields.
149 type rawFile struct {
150         error      string
151         parseError string
152
153         name                 string
154         synopsis             string // doc.Synopsis of package comment... Compute synopsis on all of these?
155         pkgName              string
156         ignoreFile           bool   // starts with _ or . or should otherwise always be ignored
157         binaryOnly           bool   // cannot be rebuilt from source (has //go:binary-only-package comment)
158         cgoDirectives        string // the #cgo directive lines in the comment on import "C"
159         goBuildConstraint    string
160         plusBuildConstraints []string
161         imports              []rawImport
162         embeds               []embed
163         directives           []build.Directive
164 }
165
166 type rawImport struct {
167         path     string
168         position token.Position
169 }
170
171 type embed struct {
172         pattern  string
173         position token.Position
174 }
175
176 // importRaw fills the rawPackage from the package files in srcDir.
177 // dir is the package's path relative to the modroot.
178 func importRaw(modroot, reldir string) *rawPackage {
179         p := &rawPackage{
180                 dir: reldir,
181         }
182
183         absdir := filepath.Join(modroot, reldir)
184
185         // We still haven't checked
186         // that p.dir directory exists. This is the right time to do that check.
187         // We can't do it earlier, because we want to gather partial information for the
188         // non-nil *build.Package returned when an error occurs.
189         // We need to do this before we return early on FindOnly flag.
190         if !isDir(absdir) {
191                 // package was not found
192                 p.error = fmt.Errorf("cannot find package in:\n\t%s", absdir).Error()
193                 return p
194         }
195
196         entries, err := fsys.ReadDir(absdir)
197         if err != nil {
198                 p.error = err.Error()
199                 return p
200         }
201
202         fset := token.NewFileSet()
203         for _, d := range entries {
204                 if d.IsDir() {
205                         continue
206                 }
207                 if d.Mode()&fs.ModeSymlink != 0 {
208                         if isDir(filepath.Join(absdir, d.Name())) {
209                                 // Symlinks to directories are not source files.
210                                 continue
211                         }
212                 }
213
214                 name := d.Name()
215                 ext := nameExt(name)
216
217                 if strings.HasPrefix(name, "_") || strings.HasPrefix(name, ".") {
218                         continue
219                 }
220                 info, err := getFileInfo(absdir, name, fset)
221                 if err == errNonSource {
222                         // not a source or object file. completely ignore in the index
223                         continue
224                 } else if err != nil {
225                         p.sourceFiles = append(p.sourceFiles, &rawFile{name: name, error: err.Error()})
226                         continue
227                 } else if info == nil {
228                         p.sourceFiles = append(p.sourceFiles, &rawFile{name: name, ignoreFile: true})
229                         continue
230                 }
231                 rf := &rawFile{
232                         name:                 name,
233                         goBuildConstraint:    info.goBuildConstraint,
234                         plusBuildConstraints: info.plusBuildConstraints,
235                         binaryOnly:           info.binaryOnly,
236                         directives:           info.directives,
237                 }
238                 if info.parsed != nil {
239                         rf.pkgName = info.parsed.Name.Name
240                 }
241
242                 // Going to save the file. For non-Go files, can stop here.
243                 p.sourceFiles = append(p.sourceFiles, rf)
244                 if ext != ".go" {
245                         continue
246                 }
247
248                 if info.parseErr != nil {
249                         rf.parseError = parseErrorToString(info.parseErr)
250                         // Fall through: we might still have a partial AST in info.Parsed,
251                         // and we want to list files with parse errors anyway.
252                 }
253
254                 if info.parsed != nil && info.parsed.Doc != nil {
255                         rf.synopsis = doc.Synopsis(info.parsed.Doc.Text())
256                 }
257
258                 var cgoDirectives []string
259                 for _, imp := range info.imports {
260                         if imp.path == "C" {
261                                 cgoDirectives = append(cgoDirectives, extractCgoDirectives(imp.doc.Text())...)
262                         }
263                         rf.imports = append(rf.imports, rawImport{path: imp.path, position: fset.Position(imp.pos)})
264                 }
265                 rf.cgoDirectives = strings.Join(cgoDirectives, "\n")
266                 for _, emb := range info.embeds {
267                         rf.embeds = append(rf.embeds, embed{emb.pattern, emb.pos})
268                 }
269
270         }
271         return p
272 }
273
274 // extractCgoDirectives filters only the lines containing #cgo directives from the input,
275 // which is the comment on import "C".
276 func extractCgoDirectives(doc string) []string {
277         var out []string
278         for _, line := range strings.Split(doc, "\n") {
279                 // Line is
280                 //      #cgo [GOOS/GOARCH...] LDFLAGS: stuff
281                 //
282                 line = strings.TrimSpace(line)
283                 if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') {
284                         continue
285                 }
286
287                 out = append(out, line)
288         }
289         return out
290 }