github.com/bir3/gocompiler@v0.3.205/src/cmd/compile/internal/noder/import.go (about) 1 // Copyright 2009 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 noder 6 7 import ( 8 "errors" 9 "fmt" 10 "github.com/bir3/gocompiler/src/internal/buildcfg" 11 "github.com/bir3/gocompiler/src/internal/pkgbits" 12 "os" 13 pathpkg "path" 14 "runtime" 15 "strings" 16 "unicode" 17 "unicode/utf8" 18 19 "github.com/bir3/gocompiler/src/cmd/compile/internal/base" 20 "github.com/bir3/gocompiler/src/cmd/compile/internal/importer" 21 "github.com/bir3/gocompiler/src/cmd/compile/internal/ir" 22 "github.com/bir3/gocompiler/src/cmd/compile/internal/typecheck" 23 "github.com/bir3/gocompiler/src/cmd/compile/internal/types" 24 "github.com/bir3/gocompiler/src/cmd/compile/internal/types2" 25 "github.com/bir3/gocompiler/src/cmd/internal/archive" 26 "github.com/bir3/gocompiler/src/cmd/internal/bio" 27 "github.com/bir3/gocompiler/src/cmd/internal/goobj" 28 "github.com/bir3/gocompiler/src/cmd/internal/objabi" 29 ) 30 31 type gcimports struct { 32 ctxt *types2.Context 33 packages map[string]*types2.Package 34 } 35 36 func (m *gcimports) Import(path string) (*types2.Package, error) { 37 return m.ImportFrom(path, "" /* no vendoring */, 0) 38 } 39 40 func (m *gcimports) ImportFrom(path, srcDir string, mode types2.ImportMode) (*types2.Package, error) { 41 if mode != 0 { 42 panic("mode must be 0") 43 } 44 45 _, pkg, err := readImportFile(path, typecheck.Target, m.ctxt, m.packages) 46 return pkg, err 47 } 48 49 func isDriveLetter(b byte) bool { 50 return 'a' <= b && b <= 'z' || 'A' <= b && b <= 'Z' 51 } 52 53 // is this path a local name? begins with ./ or ../ or / 54 func islocalname(name string) bool { 55 return strings.HasPrefix(name, "/") || 56 runtime.GOOS == "windows" && len(name) >= 3 && isDriveLetter(name[0]) && name[1] == ':' && name[2] == '/' || 57 strings.HasPrefix(name, "./") || name == "." || 58 strings.HasPrefix(name, "../") || name == ".." 59 } 60 61 func openPackage(path string) (*os.File, error) { 62 if islocalname(path) { 63 if base.Flag.NoLocalImports { 64 return nil, errors.New("local imports disallowed") 65 } 66 67 if base.Flag.Cfg.PackageFile != nil { 68 return os.Open(base.Flag.Cfg.PackageFile[path]) 69 } 70 71 // try .a before .o. important for building libraries: 72 // if there is an array.o in the array.a library, 73 // want to find all of array.a, not just array.o. 74 if file, err := os.Open(fmt.Sprintf("%s.a", path)); err == nil { 75 return file, nil 76 } 77 if file, err := os.Open(fmt.Sprintf("%s.o", path)); err == nil { 78 return file, nil 79 } 80 return nil, errors.New("file not found") 81 } 82 83 // local imports should be canonicalized already. 84 // don't want to see "encoding/../encoding/base64" 85 // as different from "encoding/base64". 86 if q := pathpkg.Clean(path); q != path { 87 return nil, fmt.Errorf("non-canonical import path %q (should be %q)", path, q) 88 } 89 90 if base.Flag.Cfg.PackageFile != nil { 91 return os.Open(base.Flag.Cfg.PackageFile[path]) 92 } 93 94 for _, dir := range base.Flag.Cfg.ImportDirs { 95 if file, err := os.Open(fmt.Sprintf("%s/%s.a", dir, path)); err == nil { 96 return file, nil 97 } 98 if file, err := os.Open(fmt.Sprintf("%s/%s.o", dir, path)); err == nil { 99 return file, nil 100 } 101 } 102 103 if buildcfg.GOROOT != "" { 104 suffix := "" 105 if base.Flag.InstallSuffix != "" { 106 suffix = "_" + base.Flag.InstallSuffix 107 } else if base.Flag.Race { 108 suffix = "_race" 109 } else if base.Flag.MSan { 110 suffix = "_msan" 111 } else if base.Flag.ASan { 112 suffix = "_asan" 113 } 114 115 if file, err := os.Open(fmt.Sprintf("%s/pkg/%s_%s%s/%s.a", buildcfg.GOROOT, buildcfg.GOOS, buildcfg.GOARCH, suffix, path)); err == nil { 116 return file, nil 117 } 118 if file, err := os.Open(fmt.Sprintf("%s/pkg/%s_%s%s/%s.o", buildcfg.GOROOT, buildcfg.GOOS, buildcfg.GOARCH, suffix, path)); err == nil { 119 return file, nil 120 } 121 } 122 return nil, errors.New("file not found") 123 } 124 125 // resolveImportPath resolves an import path as it appears in a Go 126 // source file to the package's full path. 127 func resolveImportPath(path string) (string, error) { 128 // The package name main is no longer reserved, 129 // but we reserve the import path "main" to identify 130 // the main package, just as we reserve the import 131 // path "math" to identify the standard math package. 132 if path == "main" { 133 return "", errors.New("cannot import \"main\"") 134 } 135 136 if base.Ctxt.Pkgpath != "" && path == base.Ctxt.Pkgpath { 137 return "", fmt.Errorf("import %q while compiling that package (import cycle)", path) 138 } 139 140 if mapped, ok := base.Flag.Cfg.ImportMap[path]; ok { 141 path = mapped 142 } 143 144 if islocalname(path) { 145 if path[0] == '/' { 146 return "", errors.New("import path cannot be absolute path") 147 } 148 149 prefix := base.Flag.D 150 if prefix == "" { 151 // Questionable, but when -D isn't specified, historically we 152 // resolve local import paths relative to the directory the 153 // compiler's current directory, not the respective source 154 // file's directory. 155 prefix = base.Ctxt.Pathname 156 } 157 path = pathpkg.Join(prefix, path) 158 159 if err := checkImportPath(path, true); err != nil { 160 return "", err 161 } 162 } 163 164 return path, nil 165 } 166 167 // readImportFile reads the import file for the given package path and 168 // returns its types.Pkg representation. If packages is non-nil, the 169 // types2.Package representation is also returned. 170 func readImportFile(path string, target *ir.Package, env *types2.Context, packages map[string]*types2.Package) (pkg1 *types.Pkg, pkg2 *types2.Package, err error) { 171 path, err = resolveImportPath(path) 172 if err != nil { 173 return 174 } 175 176 if path == "unsafe" { 177 pkg1, pkg2 = types.UnsafePkg, types2.Unsafe 178 179 // TODO(mdempsky): Investigate if this actually matters. Why would 180 // the linker or runtime care whether a package imported unsafe? 181 if !pkg1.Direct { 182 pkg1.Direct = true 183 target.Imports = append(target.Imports, pkg1) 184 } 185 186 return 187 } 188 189 pkg1 = types.NewPkg(path, "") 190 if packages != nil { 191 pkg2 = packages[path] 192 assert(pkg1.Direct == (pkg2 != nil && pkg2.Complete())) 193 } 194 195 if pkg1.Direct { 196 return 197 } 198 pkg1.Direct = true 199 target.Imports = append(target.Imports, pkg1) 200 201 f, err := openPackage(path) 202 if err != nil { 203 return 204 } 205 defer f.Close() 206 207 r, end, err := findExportData(f) 208 if err != nil { 209 return 210 } 211 212 if base.Debug.Export != 0 { 213 fmt.Printf("importing %s (%s)\n", path, f.Name()) 214 } 215 216 c, err := r.ReadByte() 217 if err != nil { 218 return 219 } 220 221 pos := r.Offset() 222 223 // Map export data section into memory as a single large 224 // string. This reduces heap fragmentation and allows returning 225 // individual substrings very efficiently. 226 var data string 227 data, err = base.MapFile(r.File(), pos, end-pos) 228 if err != nil { 229 return 230 } 231 232 switch c { 233 case 'u': 234 if !buildcfg.Experiment.Unified { 235 base.Fatalf("unexpected export data format") 236 } 237 238 // TODO(mdempsky): This seems a bit clunky. 239 data = strings.TrimSuffix(data, "\n$$\n") 240 241 pr := pkgbits.NewPkgDecoder(pkg1.Path, data) 242 243 // Read package descriptors for both types2 and compiler backend. 244 readPackage(newPkgReader(pr), pkg1, false) 245 pkg2 = importer.ReadPackage(env, packages, pr) 246 247 case 'i': 248 if buildcfg.Experiment.Unified { 249 base.Fatalf("unexpected export data format") 250 } 251 252 typecheck.ReadImports(pkg1, data) 253 254 if packages != nil { 255 pkg2, err = importer.ImportData(packages, data, path) 256 if err != nil { 257 return 258 } 259 } 260 261 default: 262 // Indexed format is distinguished by an 'i' byte, 263 // whereas previous export formats started with 'c', 'd', or 'v'. 264 err = fmt.Errorf("unexpected package format byte: %v", c) 265 return 266 } 267 268 err = addFingerprint(path, f, end) 269 return 270 } 271 272 // findExportData returns a *bio.Reader positioned at the start of the 273 // binary export data section, and a file offset for where to stop 274 // reading. 275 func findExportData(f *os.File) (r *bio.Reader, end int64, err error) { 276 r = bio.NewReader(f) 277 278 // check object header 279 line, err := r.ReadString('\n') 280 if err != nil { 281 return 282 } 283 284 if line == "!<arch>\n" { // package archive 285 // package export block should be first 286 sz := int64(archive.ReadHeader(r.Reader, "__.PKGDEF")) 287 if sz <= 0 { 288 err = errors.New("not a package file") 289 return 290 } 291 end = r.Offset() + sz 292 line, err = r.ReadString('\n') 293 if err != nil { 294 return 295 } 296 } else { 297 // Not an archive; provide end of file instead. 298 // TODO(mdempsky): I don't think this happens anymore. 299 var fi os.FileInfo 300 fi, err = f.Stat() 301 if err != nil { 302 return 303 } 304 end = fi.Size() 305 } 306 307 if !strings.HasPrefix(line, "go object ") { 308 err = fmt.Errorf("not a go object file: %s", line) 309 return 310 } 311 if expect := objabi.HeaderString(); line != expect { 312 err = fmt.Errorf("object is [%s] expected [%s]", line, expect) 313 return 314 } 315 316 // process header lines 317 for !strings.HasPrefix(line, "$$") { 318 line, err = r.ReadString('\n') 319 if err != nil { 320 return 321 } 322 } 323 324 // Expect $$B\n to signal binary import format. 325 if line != "$$B\n" { 326 err = errors.New("old export format no longer supported (recompile library)") 327 return 328 } 329 330 return 331 } 332 333 // addFingerprint reads the linker fingerprint included at the end of 334 // the exportdata. 335 func addFingerprint(path string, f *os.File, end int64) error { 336 const eom = "\n$$\n" 337 var fingerprint goobj.FingerprintType 338 339 var buf [len(fingerprint) + len(eom)]byte 340 if _, err := f.ReadAt(buf[:], end-int64(len(buf))); err != nil { 341 return err 342 } 343 344 // Caller should have given us the end position of the export data, 345 // which should end with the "\n$$\n" marker. As a consistency check 346 // to make sure we're reading at the right offset, make sure we 347 // found the marker. 348 if s := string(buf[len(fingerprint):]); s != eom { 349 return fmt.Errorf("expected $$ marker, but found %q", s) 350 } 351 352 copy(fingerprint[:], buf[:]) 353 base.Ctxt.AddImport(path, fingerprint) 354 355 return nil 356 } 357 358 func checkImportPath(path string, allowSpace bool) error { 359 if path == "" { 360 return errors.New("import path is empty") 361 } 362 363 if strings.Contains(path, "\x00") { 364 return errors.New("import path contains NUL") 365 } 366 367 for ri := range base.ReservedImports { 368 if path == ri { 369 return fmt.Errorf("import path %q is reserved and cannot be used", path) 370 } 371 } 372 373 for _, r := range path { 374 switch { 375 case r == utf8.RuneError: 376 return fmt.Errorf("import path contains invalid UTF-8 sequence: %q", path) 377 case r < 0x20 || r == 0x7f: 378 return fmt.Errorf("import path contains control character: %q", path) 379 case r == '\\': 380 return fmt.Errorf("import path contains backslash; use slash: %q", path) 381 case !allowSpace && unicode.IsSpace(r): 382 return fmt.Errorf("import path contains space character: %q", path) 383 case strings.ContainsRune("!\"#$%&'()*,:;<=>?[]^`{|}", r): 384 return fmt.Errorf("import path contains invalid character '%c': %q", r, path) 385 } 386 } 387 388 return nil 389 }