github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/coverage/decodemeta/decodefile.go (about)

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