decodefile.mx raw

   1  // Copyright 2021 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 decodemeta
   6  
   7  // This package contains APIs and helpers for reading and decoding
   8  // meta-data output files emitted by the runtime when a
   9  // coverage-instrumented binary executes. A meta-data file contains
  10  // top-level info (counter mode, number of packages) and then a
  11  // separate self-contained meta-data section for each Go package.
  12  
  13  import (
  14  	"bufio"
  15  	"encoding/binary"
  16  	"fmt"
  17  	"hash/fnv"
  18  	"internal/coverage"
  19  	"internal/coverage/slicereader"
  20  	"internal/coverage/stringtab"
  21  	"io"
  22  	"os"
  23  )
  24  
  25  // CoverageMetaFileReader provides state and methods for reading
  26  // a meta-data file from a code coverage run.
  27  type CoverageMetaFileReader struct {
  28  	f          *os.File
  29  	hdr        coverage.MetaFileHeader
  30  	tmp        []byte
  31  	pkgOffsets []uint64
  32  	pkgLengths []uint64
  33  	strtab     *stringtab.Reader
  34  	fileRdr    *bufio.Reader
  35  	fileView   []byte
  36  	debug      bool
  37  }
  38  
  39  // NewCoverageMetaFileReader returns a new helper object for reading
  40  // the coverage meta-data output file 'f'. The param 'fileView' is a
  41  // read-only slice containing the contents of 'f' obtained by mmap'ing
  42  // the file read-only; 'fileView' may be nil, in which case the helper
  43  // will read the contents of the file using regular file Read
  44  // operations.
  45  func NewCoverageMetaFileReader(f *os.File, fileView []byte) (*CoverageMetaFileReader, error) {
  46  	r := &CoverageMetaFileReader{
  47  		f:        f,
  48  		fileView: fileView,
  49  		tmp:      []byte{:256},
  50  	}
  51  
  52  	if err := r.readFileHeader(); err != nil {
  53  		return nil, err
  54  	}
  55  	return r, nil
  56  }
  57  
  58  func (r *CoverageMetaFileReader) readFileHeader() error {
  59  	var err error
  60  
  61  	r.fileRdr = bufio.NewReader(r.f)
  62  
  63  	// Read file header.
  64  	if err := binary.Read(r.fileRdr, binary.LittleEndian, &r.hdr); err != nil {
  65  		return err
  66  	}
  67  
  68  	// Verify magic string
  69  	m := r.hdr.Magic
  70  	g := coverage.CovMetaMagic
  71  	if m[0] != g[0] || m[1] != g[1] || m[2] != g[2] || m[3] != g[3] {
  72  		return fmt.Errorf("invalid meta-data file magic string")
  73  	}
  74  
  75  	// Vet the version. If this is a meta-data file from the future,
  76  	// we won't be able to read it.
  77  	if r.hdr.Version > coverage.MetaFileVersion {
  78  		return fmt.Errorf("meta-data file withn unknown version %d (expected %d)", r.hdr.Version, coverage.MetaFileVersion)
  79  	}
  80  
  81  	// Read package offsets for good measure
  82  	r.pkgOffsets = []uint64{:r.hdr.Entries}
  83  	for i := uint64(0); i < r.hdr.Entries; i++ {
  84  		if r.pkgOffsets[i], err = r.rdUint64(); err != nil {
  85  			return err
  86  		}
  87  		if r.pkgOffsets[i] > r.hdr.TotalLength {
  88  			return fmt.Errorf("insane pkg offset %d: %d > totlen %d",
  89  				i, r.pkgOffsets[i], r.hdr.TotalLength)
  90  		}
  91  	}
  92  	r.pkgLengths = []uint64{:r.hdr.Entries}
  93  	for i := uint64(0); i < r.hdr.Entries; i++ {
  94  		if r.pkgLengths[i], err = r.rdUint64(); err != nil {
  95  			return err
  96  		}
  97  		if r.pkgLengths[i] > r.hdr.TotalLength {
  98  			return fmt.Errorf("insane pkg length %d: %d > totlen %d",
  99  				i, r.pkgLengths[i], r.hdr.TotalLength)
 100  		}
 101  	}
 102  
 103  	// Read string table.
 104  	b := []byte{:r.hdr.StrTabLength}
 105  	nr, err := r.fileRdr.Read(b)
 106  	if err != nil {
 107  		return err
 108  	}
 109  	if nr != int(r.hdr.StrTabLength) {
 110  		return fmt.Errorf("error: short read on string table")
 111  	}
 112  	slr := slicereader.NewReader(b, false /* not readonly */)
 113  	r.strtab = stringtab.NewReader(slr)
 114  	r.strtab.Read()
 115  
 116  	if r.debug {
 117  		fmt.Fprintf(os.Stderr, "=-= read-in header is: %+v\n", *r)
 118  	}
 119  
 120  	return nil
 121  }
 122  
 123  func (r *CoverageMetaFileReader) rdUint64() (uint64, error) {
 124  	r.tmp = r.tmp[:0]
 125  	r.tmp = append(r.tmp, []byte{:8}...)
 126  	n, err := r.fileRdr.Read(r.tmp)
 127  	if err != nil {
 128  		return 0, err
 129  	}
 130  	if n != 8 {
 131  		return 0, fmt.Errorf("premature end of file on read")
 132  	}
 133  	v := binary.LittleEndian.Uint64(r.tmp)
 134  	return v, nil
 135  }
 136  
 137  // NumPackages returns the number of packages for which this file
 138  // contains meta-data.
 139  func (r *CoverageMetaFileReader) NumPackages() uint64 {
 140  	return r.hdr.Entries
 141  }
 142  
 143  // CounterMode returns the counter mode (set, count, atomic) used
 144  // when building for coverage for the program that produce this
 145  // meta-data file.
 146  func (r *CoverageMetaFileReader) CounterMode() coverage.CounterMode {
 147  	return r.hdr.CMode
 148  }
 149  
 150  // CounterGranularity returns the counter granularity (single counter per
 151  // function, or counter per block) selected when building for coverage
 152  // for the program that produce this meta-data file.
 153  func (r *CoverageMetaFileReader) CounterGranularity() coverage.CounterGranularity {
 154  	return r.hdr.CGranularity
 155  }
 156  
 157  // FileHash returns the hash computed for all of the package meta-data
 158  // blobs. Coverage counter data files refer to this hash, and the
 159  // hash will be encoded into the meta-data file name.
 160  func (r *CoverageMetaFileReader) FileHash() [16]byte {
 161  	return r.hdr.MetaFileHash
 162  }
 163  
 164  // GetPackageDecoder requests a decoder object for the package within
 165  // the meta-data file whose index is 'pkIdx'. If the
 166  // CoverageMetaFileReader was set up with a read-only file view, a
 167  // pointer into that file view will be returned, otherwise the buffer
 168  // 'payloadbuf' will be written to (or if it is not of sufficient
 169  // size, a new buffer will be allocated). Return value is the decoder,
 170  // a byte slice with the encoded meta-data, and an error.
 171  func (r *CoverageMetaFileReader) GetPackageDecoder(pkIdx uint32, payloadbuf []byte) (*CoverageMetaDataDecoder, []byte, error) {
 172  	pp, err := r.GetPackagePayload(pkIdx, payloadbuf)
 173  	if r.debug {
 174  		h := fnv.New128a()
 175  		h.Write(pp)
 176  		fmt.Fprintf(os.Stderr, "=-= pkidx=%d payload length is %d hash=%s\n",
 177  			pkIdx, len(pp), fmt.Sprintf("%x", h.Sum(nil)))
 178  	}
 179  	if err != nil {
 180  		return nil, nil, err
 181  	}
 182  	mdd, err := NewCoverageMetaDataDecoder(pp, r.fileView != nil)
 183  	if err != nil {
 184  		return nil, nil, err
 185  	}
 186  	return mdd, pp, nil
 187  }
 188  
 189  // GetPackagePayload returns the raw (encoded) meta-data payload for the
 190  // package with index 'pkIdx'. As with GetPackageDecoder, if the
 191  // CoverageMetaFileReader was set up with a read-only file view, a
 192  // pointer into that file view will be returned, otherwise the buffer
 193  // 'payloadbuf' will be written to (or if it is not of sufficient
 194  // size, a new buffer will be allocated). Return value is the decoder,
 195  // a byte slice with the encoded meta-data, and an error.
 196  func (r *CoverageMetaFileReader) GetPackagePayload(pkIdx uint32, payloadbuf []byte) ([]byte, error) {
 197  
 198  	// Determine correct offset/length.
 199  	if uint64(pkIdx) >= r.hdr.Entries {
 200  		return nil, fmt.Errorf("GetPackagePayload: illegal pkg index %d", pkIdx)
 201  	}
 202  	off := r.pkgOffsets[pkIdx]
 203  	len := r.pkgLengths[pkIdx]
 204  
 205  	if r.debug {
 206  		fmt.Fprintf(os.Stderr, "=-= for pk %d, off=%d len=%d\n", pkIdx, off, len)
 207  	}
 208  
 209  	if r.fileView != nil {
 210  		return r.fileView[off : off+len], nil
 211  	}
 212  
 213  	payload := payloadbuf[:0]
 214  	if cap(payload) < int(len) {
 215  		payload = []byte{:0:len}
 216  	}
 217  	payload = append(payload, []byte{:len}...)
 218  	if _, err := r.f.Seek(int64(off), io.SeekStart); err != nil {
 219  		return nil, err
 220  	}
 221  	if _, err := io.ReadFull(r.f, payload); err != nil {
 222  		return nil, err
 223  	}
 224  	return payload, nil
 225  }
 226