exportdata.go (12356B)
1 // Copyright 2011 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 // This file should be kept in sync with $GOROOT/src/internal/exportdata/exportdata.go. 6 // This file also additionally implements FindExportData for gcexportdata.NewReader. 7 8 package gcimporter 9 10 import ( 11 "bufio" 12 "bytes" 13 "errors" 14 "fmt" 15 "go/build" 16 "io" 17 "os" 18 "os/exec" 19 "path/filepath" 20 "strings" 21 "sync" 22 ) 23 24 // FindExportData positions the reader r at the beginning of the 25 // export data section of an underlying cmd/compile created archive 26 // file by reading from it. The reader must be positioned at the 27 // start of the file before calling this function. 28 // This returns the length of the export data in bytes. 29 // 30 // This function is needed by [gcexportdata.Read], which must 31 // accept inputs produced by the last two releases of cmd/compile, 32 // plus tip. 33 func FindExportData(r *bufio.Reader) (size int64, err error) { 34 arsize, err := FindPackageDefinition(r) 35 if err != nil { 36 return 37 } 38 size = int64(arsize) 39 40 objapi, headers, err := ReadObjectHeaders(r) 41 if err != nil { 42 return 43 } 44 size -= int64(len(objapi)) 45 for _, h := range headers { 46 size -= int64(len(h)) 47 } 48 49 // Check for the binary export data section header "$$B\n". 50 // TODO(taking): Unify with ReadExportDataHeader so that it stops at the 'u' instead of reading 51 line, err := r.ReadSlice('\n') 52 if err != nil { 53 return 54 } 55 hdr := string(line) 56 if hdr != "$$B\n" { 57 err = fmt.Errorf("unknown export data header: %q", hdr) 58 return 59 } 60 size -= int64(len(hdr)) 61 62 // For files with a binary export data header "$$B\n", 63 // these are always terminated by an end-of-section marker "\n$$\n". 64 // So the last bytes must always be this constant. 65 // 66 // The end-of-section marker is not a part of the export data itself. 67 // Do not include these in size. 68 // 69 // It would be nice to have sanity check that the final bytes after 70 // the export data are indeed the end-of-section marker. The split 71 // of gcexportdata.NewReader and gcexportdata.Read make checking this 72 // ugly so gcimporter gives up enforcing this. The compiler and go/types 73 // importer do enforce this, which seems good enough. 74 const endofsection = "\n$$\n" 75 size -= int64(len(endofsection)) 76 77 if size < 0 { 78 err = fmt.Errorf("invalid size (%d) in the archive file: %d bytes remain without section headers (recompile package)", arsize, size) 79 return 80 } 81 82 return 83 } 84 85 // ReadUnified reads the contents of the unified export data from a reader r 86 // that contains the contents of a GC-created archive file. 87 // 88 // On success, the reader will be positioned after the end-of-section marker "\n$$\n". 89 // 90 // Supported GC-created archive files have 4 layers of nesting: 91 // - An archive file containing a package definition file. 92 // - The package definition file contains headers followed by a data section. 93 // Headers are lines (≤ 4kb) that do not start with "$$". 94 // - The data section starts with "$$B\n" followed by export data followed 95 // by an end of section marker "\n$$\n". (The section start "$$\n" is no 96 // longer supported.) 97 // - The export data starts with a format byte ('u') followed by the <data> in 98 // the given format. (See ReadExportDataHeader for older formats.) 99 // 100 // Putting this together, the bytes in a GC-created archive files are expected 101 // to look like the following. 102 // See cmd/internal/archive for more details on ar file headers. 103 // 104 // | <!arch>\n | ar file signature 105 // | __.PKGDEF...size...\n | ar header for __.PKGDEF including size. 106 // | go object <...>\n | objabi header 107 // | <optional headers>\n | other headers such as build id 108 // | $$B\n | binary format marker 109 // | u<data>\n | unified export <data> 110 // | $$\n | end-of-section marker 111 // | [optional padding] | padding byte (0x0A) if size is odd 112 // | [ar file header] | other ar files 113 // | [ar file data] | 114 func ReadUnified(r *bufio.Reader) (data []byte, err error) { 115 // We historically guaranteed headers at the default buffer size (4096) work. 116 // This ensures we can use ReadSlice throughout. 117 const minBufferSize = 4096 118 r = bufio.NewReaderSize(r, minBufferSize) 119 120 size, err := FindPackageDefinition(r) 121 if err != nil { 122 return 123 } 124 n := size 125 126 objapi, headers, err := ReadObjectHeaders(r) 127 if err != nil { 128 return 129 } 130 n -= len(objapi) 131 for _, h := range headers { 132 n -= len(h) 133 } 134 135 hdrlen, err := ReadExportDataHeader(r) 136 if err != nil { 137 return 138 } 139 n -= hdrlen 140 141 // size also includes the end of section marker. Remove that many bytes from the end. 142 const marker = "\n$$\n" 143 n -= len(marker) 144 145 if n < 0 { 146 err = fmt.Errorf("invalid size (%d) in the archive file: %d bytes remain without section headers (recompile package)", size, n) 147 return 148 } 149 150 // Read n bytes from buf. 151 data = make([]byte, n) 152 _, err = io.ReadFull(r, data) 153 if err != nil { 154 return 155 } 156 157 // Check for marker at the end. 158 var suffix [len(marker)]byte 159 _, err = io.ReadFull(r, suffix[:]) 160 if err != nil { 161 return 162 } 163 if s := string(suffix[:]); s != marker { 164 err = fmt.Errorf("read %q instead of end-of-section marker (%q)", s, marker) 165 return 166 } 167 168 return 169 } 170 171 // FindPackageDefinition positions the reader r at the beginning of a package 172 // definition file ("__.PKGDEF") within a GC-created archive by reading 173 // from it, and returns the size of the package definition file in the archive. 174 // 175 // The reader must be positioned at the start of the archive file before calling 176 // this function, and "__.PKGDEF" is assumed to be the first file in the archive. 177 // 178 // See cmd/internal/archive for details on the archive format. 179 func FindPackageDefinition(r *bufio.Reader) (size int, err error) { 180 // Uses ReadSlice to limit risk of malformed inputs. 181 182 // Read first line to make sure this is an object file. 183 line, err := r.ReadSlice('\n') 184 if err != nil { 185 err = fmt.Errorf("can't find export data (%v)", err) 186 return 187 } 188 189 // Is the first line an archive file signature? 190 if string(line) != "!<arch>\n" { 191 err = fmt.Errorf("not the start of an archive file (%q)", line) 192 return 193 } 194 195 // package export block should be first 196 size = readArchiveHeader(r, "__.PKGDEF") 197 if size <= 0 { 198 err = fmt.Errorf("not a package file") 199 return 200 } 201 202 return 203 } 204 205 // ReadObjectHeaders reads object headers from the reader. Object headers are 206 // lines that do not start with an end-of-section marker "$$". The first header 207 // is the objabi header. On success, the reader will be positioned at the beginning 208 // of the end-of-section marker. 209 // 210 // It returns an error if any header does not fit in r.Size() bytes. 211 func ReadObjectHeaders(r *bufio.Reader) (objapi string, headers []string, err error) { 212 // line is a temporary buffer for headers. 213 // Use bounded reads (ReadSlice, Peek) to limit risk of malformed inputs. 214 var line []byte 215 216 // objapi header should be the first line 217 if line, err = r.ReadSlice('\n'); err != nil { 218 err = fmt.Errorf("can't find export data (%v)", err) 219 return 220 } 221 objapi = string(line) 222 223 // objapi header begins with "go object ". 224 if !strings.HasPrefix(objapi, "go object ") { 225 err = fmt.Errorf("not a go object file: %s", objapi) 226 return 227 } 228 229 // process remaining object header lines 230 for { 231 // check for an end of section marker "$$" 232 line, err = r.Peek(2) 233 if err != nil { 234 return 235 } 236 if string(line) == "$$" { 237 return // stop 238 } 239 240 // read next header 241 line, err = r.ReadSlice('\n') 242 if err != nil { 243 return 244 } 245 headers = append(headers, string(line)) 246 } 247 } 248 249 // ReadExportDataHeader reads the export data header and format from r. 250 // It returns the number of bytes read, or an error if the format is no longer 251 // supported or it failed to read. 252 // 253 // The only currently supported format is binary export data in the 254 // unified export format. 255 func ReadExportDataHeader(r *bufio.Reader) (n int, err error) { 256 // Read export data header. 257 line, err := r.ReadSlice('\n') 258 if err != nil { 259 return 260 } 261 262 hdr := string(line) 263 switch hdr { 264 case "$$\n": 265 err = fmt.Errorf("old textual export format no longer supported (recompile package)") 266 return 267 268 case "$$B\n": 269 var format byte 270 format, err = r.ReadByte() 271 if err != nil { 272 return 273 } 274 // The unified export format starts with a 'u'. 275 switch format { 276 case 'u': 277 default: 278 // Older no longer supported export formats include: 279 // indexed export format which started with an 'i'; and 280 // the older binary export format which started with a 'c', 281 // 'd', or 'v' (from "version"). 282 err = fmt.Errorf("binary export format %q is no longer supported (recompile package)", format) 283 return 284 } 285 286 default: 287 err = fmt.Errorf("unknown export data header: %q", hdr) 288 return 289 } 290 291 n = len(hdr) + 1 // + 1 is for 'u' 292 return 293 } 294 295 // FindPkg returns the filename and unique package id for an import 296 // path based on package information provided by build.Import (using 297 // the build.Default build.Context). A relative srcDir is interpreted 298 // relative to the current working directory. 299 // 300 // FindPkg is only used in tests within x/tools. 301 func FindPkg(path, srcDir string) (filename, id string, err error) { 302 // TODO(taking): Move internal/exportdata.FindPkg into its own file, 303 // and then this copy into a _test package. 304 if path == "" { 305 return "", "", errors.New("path is empty") 306 } 307 308 var noext string 309 switch { 310 default: 311 // "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x" 312 // Don't require the source files to be present. 313 if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282 314 srcDir = abs 315 } 316 var bp *build.Package 317 bp, err = build.Import(path, srcDir, build.FindOnly|build.AllowBinary) 318 if bp.PkgObj == "" { 319 if bp.Goroot && bp.Dir != "" { 320 filename, err = lookupGorootExport(bp.Dir) 321 if err == nil { 322 _, err = os.Stat(filename) 323 } 324 if err == nil { 325 return filename, bp.ImportPath, nil 326 } 327 } 328 goto notfound 329 } else { 330 noext = strings.TrimSuffix(bp.PkgObj, ".a") 331 } 332 id = bp.ImportPath 333 334 case build.IsLocalImport(path): 335 // "./x" -> "/this/directory/x.ext", "/this/directory/x" 336 noext = filepath.Join(srcDir, path) 337 id = noext 338 339 case filepath.IsAbs(path): 340 // for completeness only - go/build.Import 341 // does not support absolute imports 342 // "/x" -> "/x.ext", "/x" 343 noext = path 344 id = path 345 } 346 347 if false { // for debugging 348 if path != id { 349 fmt.Printf("%s -> %s\n", path, id) 350 } 351 } 352 353 // try extensions 354 for _, ext := range pkgExts { 355 filename = noext + ext 356 f, statErr := os.Stat(filename) 357 if statErr == nil && !f.IsDir() { 358 return filename, id, nil 359 } 360 if err == nil { 361 err = statErr 362 } 363 } 364 365 notfound: 366 if err == nil { 367 return "", path, fmt.Errorf("can't find import: %q", path) 368 } 369 return "", path, fmt.Errorf("can't find import: %q: %w", path, err) 370 } 371 372 var pkgExts = [...]string{".a", ".o"} // a file from the build cache will have no extension 373 374 var exportMap sync.Map // package dir → func() (string, error) 375 376 // lookupGorootExport returns the location of the export data 377 // (normally found in the build cache, but located in GOROOT/pkg 378 // in prior Go releases) for the package located in pkgDir. 379 // 380 // (We use the package's directory instead of its import path 381 // mainly to simplify handling of the packages in src/vendor 382 // and cmd/vendor.) 383 // 384 // lookupGorootExport is only used in tests within x/tools. 385 func lookupGorootExport(pkgDir string) (string, error) { 386 f, ok := exportMap.Load(pkgDir) 387 if !ok { 388 var ( 389 listOnce sync.Once 390 exportPath string 391 err error 392 ) 393 f, _ = exportMap.LoadOrStore(pkgDir, func() (string, error) { 394 listOnce.Do(func() { 395 cmd := exec.Command(filepath.Join(build.Default.GOROOT, "bin", "go"), "list", "-export", "-f", "{{.Export}}", pkgDir) 396 cmd.Dir = build.Default.GOROOT 397 cmd.Env = append(os.Environ(), "PWD="+cmd.Dir, "GOROOT="+build.Default.GOROOT) 398 var output []byte 399 output, err = cmd.Output() 400 if err != nil { 401 if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 { 402 err = errors.New(string(ee.Stderr)) 403 } 404 return 405 } 406 407 exports := strings.Split(string(bytes.TrimSpace(output)), "\n") 408 if len(exports) != 1 { 409 err = fmt.Errorf("go list reported %d exports; expected 1", len(exports)) 410 return 411 } 412 413 exportPath = exports[0] 414 }) 415 416 return exportPath, err 417 }) 418 } 419 420 return f.(func() (string, error))() 421 }