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