Source file src/internal/coverage/decodemeta/decodefile.go

     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:      make([]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 = make([]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 = make([]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 := make([]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, make([]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 = make([]byte, 0, len)
   216  	}
   217  	payload = append(payload, make([]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  

View as plain text