| // Copyright 2021 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package decodemeta |
| |
| // This package contains APIs and helpers for reading and decoding |
| // meta-data output files emitted by the runtime when a |
| // coverage-instrumented binary executes. A meta-data file contains |
| // top-level info (counter mode, number of packages) and then a |
| // separate self-contained meta-data section for each Go package. |
| |
| import ( |
| "bufio" |
| "crypto/md5" |
| "encoding/binary" |
| "fmt" |
| "internal/coverage" |
| "internal/coverage/slicereader" |
| "internal/coverage/stringtab" |
| "io" |
| "os" |
| ) |
| |
| // CoverageMetaFileReader provides state and methods for reading |
| // a meta-data file from a code coverage run. |
| type CoverageMetaFileReader struct { |
| f *os.File |
| hdr coverage.MetaFileHeader |
| tmp []byte |
| pkgOffsets []uint64 |
| pkgLengths []uint64 |
| strtab *stringtab.Reader |
| fileRdr *bufio.Reader |
| fileView []byte |
| debug bool |
| } |
| |
| // NewCoverageMetaFileReader returns a new helper object for reading |
| // the coverage meta-data output file 'f'. The param 'fileView' is a |
| // read-only slice containing the contents of 'f' obtained by mmap'ing |
| // the file read-only; 'fileView' may be nil, in which case the helper |
| // will read the contents of the file using regular file Read |
| // operations. |
| func NewCoverageMetaFileReader(f *os.File, fileView []byte) (*CoverageMetaFileReader, error) { |
| r := &CoverageMetaFileReader{ |
| f: f, |
| fileView: fileView, |
| tmp: make([]byte, 256), |
| } |
| |
| if err := r.readFileHeader(); err != nil { |
| return nil, err |
| } |
| return r, nil |
| } |
| |
| func (r *CoverageMetaFileReader) readFileHeader() error { |
| var err error |
| |
| r.fileRdr = bufio.NewReader(r.f) |
| |
| // Read file header. |
| if err := binary.Read(r.fileRdr, binary.LittleEndian, &r.hdr); err != nil { |
| return err |
| } |
| |
| // Verify magic string |
| m := r.hdr.Magic |
| g := coverage.CovMetaMagic |
| if m[0] != g[0] || m[1] != g[1] || m[2] != g[2] || m[3] != g[3] { |
| return fmt.Errorf("invalid meta-data file magic string") |
| } |
| |
| // Vet the version. If this is a meta-data file from the future, |
| // we won't be able to read it. |
| if r.hdr.Version > coverage.MetaFileVersion { |
| return fmt.Errorf("meta-data file withn unknown version %d (expected %d)", r.hdr.Version, coverage.MetaFileVersion) |
| } |
| |
| // Read package offsets for good measure |
| r.pkgOffsets = make([]uint64, r.hdr.Entries) |
| for i := uint64(0); i < r.hdr.Entries; i++ { |
| if r.pkgOffsets[i], err = r.rdUint64(); err != nil { |
| return err |
| } |
| if r.pkgOffsets[i] > r.hdr.TotalLength { |
| return fmt.Errorf("insane pkg offset %d: %d > totlen %d", |
| i, r.pkgOffsets[i], r.hdr.TotalLength) |
| } |
| } |
| r.pkgLengths = make([]uint64, r.hdr.Entries) |
| for i := uint64(0); i < r.hdr.Entries; i++ { |
| if r.pkgLengths[i], err = r.rdUint64(); err != nil { |
| return err |
| } |
| if r.pkgLengths[i] > r.hdr.TotalLength { |
| return fmt.Errorf("insane pkg length %d: %d > totlen %d", |
| i, r.pkgLengths[i], r.hdr.TotalLength) |
| } |
| } |
| |
| // Read string table. |
| b := make([]byte, r.hdr.StrTabLength) |
| nr, err := r.fileRdr.Read(b) |
| if err != nil { |
| return err |
| } |
| if nr != int(r.hdr.StrTabLength) { |
| return fmt.Errorf("error: short read on string table") |
| } |
| slr := slicereader.NewReader(b, false /* not readonly */) |
| r.strtab = stringtab.NewReader(slr) |
| r.strtab.Read() |
| |
| if r.debug { |
| fmt.Fprintf(os.Stderr, "=-= read-in header is: %+v\n", *r) |
| } |
| |
| return nil |
| } |
| |
| func (r *CoverageMetaFileReader) rdUint64() (uint64, error) { |
| r.tmp = r.tmp[:0] |
| r.tmp = append(r.tmp, make([]byte, 8)...) |
| n, err := r.fileRdr.Read(r.tmp) |
| if err != nil { |
| return 0, err |
| } |
| if n != 8 { |
| return 0, fmt.Errorf("premature end of file on read") |
| } |
| v := binary.LittleEndian.Uint64(r.tmp) |
| return v, nil |
| } |
| |
| // NumPackages returns the number of packages for which this file |
| // contains meta-data. |
| func (r *CoverageMetaFileReader) NumPackages() uint64 { |
| return r.hdr.Entries |
| } |
| |
| // CounterMode returns the counter mode (set, count, atomic) used |
| // when building for coverage for the program that produce this |
| // meta-data file. |
| func (r *CoverageMetaFileReader) CounterMode() coverage.CounterMode { |
| return r.hdr.CMode |
| } |
| |
| // CounterMode returns the counter granularity (single counter per |
| // function, or counter per block) selected when building for coverage |
| // for the program that produce this meta-data file. |
| func (r *CoverageMetaFileReader) CounterGranularity() coverage.CounterGranularity { |
| return r.hdr.CGranularity |
| } |
| |
| // FileHash returns the hash computed for all of the package meta-data |
| // blobs. Coverage counter data files refer to this hash, and the |
| // hash will be encoded into the meta-data file name. |
| func (r *CoverageMetaFileReader) FileHash() [16]byte { |
| return r.hdr.MetaFileHash |
| } |
| |
| // GetPackageDecoder requests a decoder object for the package within |
| // the meta-data file whose index is 'pkIdx'. If the |
| // CoverageMetaFileReader was set up with a read-only file view, a |
| // pointer into that file view will be returned, otherwise the buffer |
| // 'payloadbuf' will be written to (or if it is not of sufficient |
| // size, a new buffer will be allocated). Return value is the decoder, |
| // a byte slice with the encoded meta-data, and an error. |
| func (r *CoverageMetaFileReader) GetPackageDecoder(pkIdx uint32, payloadbuf []byte) (*CoverageMetaDataDecoder, []byte, error) { |
| pp, err := r.GetPackagePayload(pkIdx, payloadbuf) |
| if r.debug { |
| fmt.Fprintf(os.Stderr, "=-= pkidx=%d payload length is %d hash=%s\n", |
| pkIdx, len(pp), fmt.Sprintf("%x", md5.Sum(pp))) |
| } |
| if err != nil { |
| return nil, nil, err |
| } |
| mdd, err := NewCoverageMetaDataDecoder(pp, r.fileView != nil) |
| if err != nil { |
| return nil, nil, err |
| } |
| return mdd, pp, nil |
| } |
| |
| // GetPackagePayload returns the raw (encoded) meta-data payload for the |
| // package with index 'pkIdx'. As with GetPackageDecoder, if the |
| // CoverageMetaFileReader was set up with a read-only file view, a |
| // pointer into that file view will be returned, otherwise the buffer |
| // 'payloadbuf' will be written to (or if it is not of sufficient |
| // size, a new buffer will be allocated). Return value is the decoder, |
| // a byte slice with the encoded meta-data, and an error. |
| func (r *CoverageMetaFileReader) GetPackagePayload(pkIdx uint32, payloadbuf []byte) ([]byte, error) { |
| |
| // Determine correct offset/length. |
| if uint64(pkIdx) >= r.hdr.Entries { |
| return nil, fmt.Errorf("GetPackagePayload: illegal pkg index %d", pkIdx) |
| } |
| off := r.pkgOffsets[pkIdx] |
| len := r.pkgLengths[pkIdx] |
| |
| if r.debug { |
| fmt.Fprintf(os.Stderr, "=-= for pk %d, off=%d len=%d\n", pkIdx, off, len) |
| } |
| |
| if r.fileView != nil { |
| return r.fileView[off : off+len], nil |
| } |
| |
| payload := payloadbuf[:0] |
| if cap(payload) < int(len) { |
| payload = make([]byte, 0, len) |
| } |
| payload = append(payload, make([]byte, len)...) |
| if _, err := r.f.Seek(int64(off), io.SeekStart); err != nil { |
| return nil, err |
| } |
| if _, err := io.ReadFull(r.f, payload); err != nil { |
| return nil, err |
| } |
| return payload, nil |
| } |