mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
internal/coverage: add coverage meta-data encoder
Add a new package with APIs for encoding coverage meta-data. This provides support for accumulating information about each function during the compilation process, and then encoding and emitting a payload for a coverage meta-data symbol. Not yet connected to the rest of the coverage machinery (that will appear in a later patch). Updates #51430. Change-Id: I61054ce87f205b25fb1bfedaa740fd7425c34de4 Reviewed-on: https://go-review.googlesource.com/c/go/+/353453 Run-TryBot: Than McIntosh <thanm@google.com> Reviewed-by: David Chase <drchase@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
parent
a3434b71a3
commit
f951f697c4
6 changed files with 839 additions and 1 deletions
|
|
@ -40,7 +40,8 @@ var depsRules = `
|
|||
# No dependencies allowed for any of these packages.
|
||||
NONE
|
||||
< constraints, container/list, container/ring,
|
||||
internal/cfg, internal/cpu, internal/goarch,
|
||||
internal/cfg, internal/cpu, internal/coverage,
|
||||
internal/coverage/uleb128, internal/goarch,
|
||||
internal/goexperiment, internal/goos,
|
||||
internal/goversion, internal/nettrace,
|
||||
unicode/utf8, unicode/utf16, unicode,
|
||||
|
|
@ -547,6 +548,13 @@ var depsRules = `
|
|||
|
||||
FMT
|
||||
< internal/diff, internal/txtar;
|
||||
|
||||
FMT, io, internal/coverage/uleb128
|
||||
< internal/coverage/stringtab;
|
||||
|
||||
FMT, encoding/binary, internal/coverage, internal/coverage/stringtab,
|
||||
io, os, bufio, crypto/md5
|
||||
< internal/coverage/encodemeta;
|
||||
`
|
||||
|
||||
// listStdPkgs returns the same list of packages as "go list std".
|
||||
|
|
|
|||
374
src/internal/coverage/defs.go
Normal file
374
src/internal/coverage/defs.go
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
// Copyright 2022 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 coverage
|
||||
|
||||
// Types and constants related to the output files files written
|
||||
// by code coverage tooling. When a coverage-instrumented binary
|
||||
// is run, it emits two output files: a meta-data output file, and
|
||||
// a counter data output file.
|
||||
|
||||
//.....................................................................
|
||||
//
|
||||
// Meta-data definitions:
|
||||
//
|
||||
// The meta-data file is composed of a file header, a series of
|
||||
// meta-data blobs/sections (one per instrumented package), and an offsets
|
||||
// area storing the offsets of each section. Format of the meta-data
|
||||
// file looks like:
|
||||
//
|
||||
// --header----------
|
||||
// | magic: [4]byte magic string
|
||||
// | version
|
||||
// | total length of meta-data file in bytes
|
||||
// | numPkgs: number of package entries in file
|
||||
// | hash: [16]byte hash of entire meta-data payload
|
||||
// | offset to string table section
|
||||
// | length of string table
|
||||
// | number of entries in string table
|
||||
// | counter mode
|
||||
// | counter granularity
|
||||
// --package offsets table------
|
||||
// <offset to pkg 0>
|
||||
// <offset to pkg 1>
|
||||
// ...
|
||||
// --package lengths table------
|
||||
// <length of pkg 0>
|
||||
// <length of pkg 1>
|
||||
// ...
|
||||
// --string table------
|
||||
// <uleb128 len> 8
|
||||
// <data> "somestring"
|
||||
// ...
|
||||
// --package payloads------
|
||||
// <meta-symbol for pkg 0>
|
||||
// <meta-symbol for pkg 1>
|
||||
// ...
|
||||
//
|
||||
// Each package payload is a stand-alone blob emitted by the compiler,
|
||||
// and does not depend on anything else in the meta-data file. In
|
||||
// particular, each blob has it's own string table. Note that the
|
||||
// file-level string table is expected to be very short (most strings
|
||||
// will be in the meta-data blobs themselves).
|
||||
|
||||
// CovMetaMagic holds the magic string for a meta-data file.
|
||||
var CovMetaMagic = [4]byte{'\x00', '\x63', '\x76', '\x6d'}
|
||||
|
||||
// MetaFilePref is a prefix used when emitting meta-data files; these
|
||||
// files are of the form "covmeta.<hash>", where hash is a hash
|
||||
// computed from the hashes of all the package meta-data symbols in
|
||||
// the program.
|
||||
const MetaFilePref = "covmeta"
|
||||
|
||||
// MetaFileVersion contains the current (most recent) meta-data file version.
|
||||
const MetaFileVersion = 1
|
||||
|
||||
// MetaFileHeader stores file header information for a meta-data file.
|
||||
type MetaFileHeader struct {
|
||||
Magic [4]byte
|
||||
Version uint32
|
||||
TotalLength uint64
|
||||
Entries uint64
|
||||
MetaFileHash [16]byte
|
||||
StrTabOffset uint32
|
||||
StrTabLength uint32
|
||||
CMode CounterMode
|
||||
CGranularity CounterGranularity
|
||||
_ [6]byte // padding
|
||||
}
|
||||
|
||||
// MetaSymbolHeader stores header information for a single
|
||||
// meta-data blob, e.g. the coverage meta-data payload
|
||||
// computed for a given Go package.
|
||||
type MetaSymbolHeader struct {
|
||||
Length uint32 // size of meta-symbol payload in bytes
|
||||
PkgName uint32 // string table index
|
||||
PkgPath uint32 // string table index
|
||||
ModulePath uint32 // string table index
|
||||
MetaHash [16]byte
|
||||
_ byte // currently unused
|
||||
_ [3]byte // padding
|
||||
NumFiles uint32
|
||||
NumFuncs uint32
|
||||
}
|
||||
|
||||
const CovMetaHeaderSize = 16 + 4 + 4 + 4 + 4 + 4 + 4 + 4 // keep in sync with above
|
||||
|
||||
// As an example, consider the following Go package:
|
||||
//
|
||||
// 01: package p
|
||||
// 02:
|
||||
// 03: var v, w, z int
|
||||
// 04:
|
||||
// 05: func small(x, y int) int {
|
||||
// 06: v++
|
||||
// 07: // comment
|
||||
// 08: if y == 0 {
|
||||
// 09: return x
|
||||
// 10: }
|
||||
// 11: return (x << 1) ^ (9 / y)
|
||||
// 12: }
|
||||
// 13:
|
||||
// 14: func Medium(q, r int) int {
|
||||
// 15: s1 := small(q, r)
|
||||
// 16: z += s1
|
||||
// 17: s2 := small(r, q)
|
||||
// 18: w -= s2
|
||||
// 19: return w + z
|
||||
// 20: }
|
||||
//
|
||||
// The meta-data blob for the single package above might look like the
|
||||
// following:
|
||||
//
|
||||
// -- MetaSymbolHeader header----------
|
||||
// | size: size of this blob in bytes
|
||||
// | packagepath: <path to p>
|
||||
// | modulepath: <modpath for p>
|
||||
// | nfiles: 1
|
||||
// | nfunctions: 2
|
||||
// --func offsets table------
|
||||
// <offset to func 0>
|
||||
// <offset to func 1>
|
||||
// --string table (contains all files and functions)------
|
||||
// | <uleb128 len> 4
|
||||
// | <data> "p.go"
|
||||
// | <uleb128 len> 5
|
||||
// | <data> "small"
|
||||
// | <uleb128 len> 6
|
||||
// | <data> "Medium"
|
||||
// --func 0------
|
||||
// | <uleb128> num units: 3
|
||||
// | <uleb128> func name: S1 (index into string table)
|
||||
// | <uleb128> file: S0 (index into string table)
|
||||
// | <unit 0>: S0 L6 L8 2
|
||||
// | <unit 1>: S0 L9 L9 1
|
||||
// | <unit 2>: S0 L11 L11 1
|
||||
// --func 1------
|
||||
// | <uleb128> num units: 1
|
||||
// | <uleb128> func name: S2 (index into string table)
|
||||
// | <uleb128> file: S0 (index into string table)
|
||||
// | <unit 0>: S0 L15 L19 5
|
||||
// ---end-----------
|
||||
|
||||
// The following types and constants used by the meta-data encoder/decoder.
|
||||
|
||||
// FuncDesc encapsulates the meta-data definitions for a single Go function.
|
||||
// This version assumes that we're looking at a function before inlining;
|
||||
// if we want to capture a post-inlining view of the world, the
|
||||
// representations of source positions would need to be a good deal more
|
||||
// complicated.
|
||||
type FuncDesc struct {
|
||||
Funcname string
|
||||
Srcfile string
|
||||
Units []CoverableUnit
|
||||
Lit bool // true if this is a function literal
|
||||
}
|
||||
|
||||
// CoverableUnit describes the source characteristics of a single
|
||||
// program unit for which we want to gather coverage info. Coverable
|
||||
// units are either "simple" or "intraline"; a "simple" coverable unit
|
||||
// corresponds to a basic block (region of straight-line code with no
|
||||
// jumps or control transfers). An "intraline" unit corresponds to a
|
||||
// logical clause nested within some other simple unit. A simple unit
|
||||
// will have a zero Parent value; for an intraline unit NxStmts will
|
||||
// be zero and and Parent will be set to 1 plus the index of the
|
||||
// containing simple statement. Example:
|
||||
//
|
||||
// L7: q := 1
|
||||
// L8: x := (y == 101 || launch() == false)
|
||||
// L9: r := x * 2
|
||||
//
|
||||
// For the code above we would have three simple units (one for each
|
||||
// line), then an intraline unit describing the "launch() == false"
|
||||
// clause in line 8, with Parent pointing to the index of the line 8
|
||||
// unit in the units array.
|
||||
//
|
||||
// Note: in the initial version of the coverage revamp, only simple
|
||||
// units will be in use.
|
||||
type CoverableUnit struct {
|
||||
StLine, StCol uint32
|
||||
EnLine, EnCol uint32
|
||||
NxStmts uint32
|
||||
Parent uint32
|
||||
}
|
||||
|
||||
// CounterMode tracks the "flavor" of the coverage counters being
|
||||
// used in a given coverage-instrumented program.
|
||||
type CounterMode uint8
|
||||
|
||||
const (
|
||||
CtrModeInvalid CounterMode = iota
|
||||
CtrModeSet // "set" mode
|
||||
CtrModeCount // "count" mode
|
||||
CtrModeAtomic // "atomic" mode
|
||||
CtrModeRegOnly // registration-only pseudo-mode
|
||||
CtrModeTestMain // testmain pseudo-mode
|
||||
)
|
||||
|
||||
func (cm CounterMode) String() string {
|
||||
switch cm {
|
||||
case CtrModeSet:
|
||||
return "set"
|
||||
case CtrModeCount:
|
||||
return "count"
|
||||
case CtrModeAtomic:
|
||||
return "atomic"
|
||||
case CtrModeRegOnly:
|
||||
return "regonly"
|
||||
case CtrModeTestMain:
|
||||
return "testmain"
|
||||
}
|
||||
return "<invalid>"
|
||||
}
|
||||
|
||||
func ParseCounterMode(mode string) CounterMode {
|
||||
var cm CounterMode
|
||||
switch mode {
|
||||
case "set":
|
||||
cm = CtrModeSet
|
||||
case "count":
|
||||
cm = CtrModeCount
|
||||
case "atomic":
|
||||
cm = CtrModeAtomic
|
||||
case "regonly":
|
||||
cm = CtrModeRegOnly
|
||||
case "testmain":
|
||||
cm = CtrModeTestMain
|
||||
default:
|
||||
cm = CtrModeInvalid
|
||||
}
|
||||
return cm
|
||||
}
|
||||
|
||||
// CounterGranularity tracks the granularity of the coverage counters being
|
||||
// used in a given coverage-instrumented program.
|
||||
type CounterGranularity uint8
|
||||
|
||||
const (
|
||||
CtrGranularityInvalid CounterGranularity = iota
|
||||
CtrGranularityPerBlock
|
||||
CtrGranularityPerFunc
|
||||
)
|
||||
|
||||
func (cm CounterGranularity) String() string {
|
||||
switch cm {
|
||||
case CtrGranularityPerBlock:
|
||||
return "perblock"
|
||||
case CtrGranularityPerFunc:
|
||||
return "perfunc"
|
||||
}
|
||||
return "<invalid>"
|
||||
}
|
||||
|
||||
//.....................................................................
|
||||
//
|
||||
// Counter data definitions:
|
||||
//
|
||||
|
||||
// A counter data file is composed of a file header followed by one or
|
||||
// more "segments" (each segment representing a given run or partial
|
||||
// run of a give binary) followed by a footer.
|
||||
|
||||
// CovCounterMagic holds the magic string for a coverage counter-data file.
|
||||
var CovCounterMagic = [4]byte{'\x00', '\x63', '\x77', '\x6d'}
|
||||
|
||||
// CounterFileVersion stores the most recent counter data file version.
|
||||
const CounterFileVersion = 1
|
||||
|
||||
// CounterFileHeader stores files header information for a counter-data file.
|
||||
type CounterFileHeader struct {
|
||||
Magic [4]byte
|
||||
Version uint32
|
||||
MetaHash [16]byte
|
||||
CFlavor CounterFlavor
|
||||
BigEndian bool
|
||||
_ [6]byte // padding
|
||||
}
|
||||
|
||||
// CounterSegmentHeader encapsulates information about a specific
|
||||
// segment in a counter data file, which at the moment contains
|
||||
// counters data from a single execution of a coverage-instrumented
|
||||
// program. Following the segment header will be the string table and
|
||||
// args table, and then (possibly) padding bytes to bring the byte
|
||||
// size of the preamble up to a multiple of 4. Immediately following
|
||||
// that will be the counter payloads.
|
||||
//
|
||||
// The "args" section of a segment is used to store annotations
|
||||
// describing where the counter data came from; this section is
|
||||
// basically a series of key-value pairs (can be thought of as an
|
||||
// encoded 'map[string]string'). At the moment we only write os.Args()
|
||||
// data to this section, using pairs of the form "argc=<integer>",
|
||||
// "argv0=<os.Args[0]>", "argv1=<os.Args[1]>", and so on. In the
|
||||
// future the args table may also include things like GOOS/GOARCH
|
||||
// values, and/or tags indicating which tests were run to generate the
|
||||
// counter data.
|
||||
type CounterSegmentHeader struct {
|
||||
FcnEntries uint64
|
||||
StrTabLen uint32
|
||||
ArgsLen uint32
|
||||
}
|
||||
|
||||
// CounterFileFooter appears at the tail end of a counter data file,
|
||||
// and stores the number of segments it contains.
|
||||
type CounterFileFooter struct {
|
||||
Magic [4]byte
|
||||
_ [4]byte // padding
|
||||
NumSegments uint32
|
||||
_ [4]byte // padding
|
||||
}
|
||||
|
||||
// CounterFilePref is the file prefix used when emitting coverage data
|
||||
// output files. CounterFileTemplate describes the format of the file
|
||||
// name: prefix followed by meta-file hash followed by process ID
|
||||
// followed by emit UnixNanoTime.
|
||||
const CounterFilePref = "covcounters"
|
||||
const CounterFileTempl = "%s.%x.%d.%d"
|
||||
const CounterFileRegexp = `^%s\.(\S+)\.(\d+)\.(\d+)+$`
|
||||
|
||||
// CounterFlavor describes how function and counters are
|
||||
// stored/represented in the counter section of the file.
|
||||
type CounterFlavor uint8
|
||||
|
||||
const (
|
||||
// "Raw" representation: all values (pkg ID, func ID, num counters,
|
||||
// and counters themselves) are stored as uint32's.
|
||||
CtrRaw CounterFlavor = iota + 1
|
||||
|
||||
// "ULeb" representation: all values (pkg ID, func ID, num counters,
|
||||
// and counters themselves) are stored with ULEB128 encoding.
|
||||
CtrULeb128
|
||||
)
|
||||
|
||||
func Round4(x int) int {
|
||||
return (x + 3) &^ 3
|
||||
}
|
||||
|
||||
//.....................................................................
|
||||
//
|
||||
// Runtime counter data definitions.
|
||||
//
|
||||
|
||||
// At runtime within a coverage-instrumented program, the "counters"
|
||||
// object we associated with instrumented function can be thought of
|
||||
// as a struct of the following form:
|
||||
//
|
||||
// struct {
|
||||
// numCtrs uint32
|
||||
// pkgid uint32
|
||||
// funcid uint32
|
||||
// counterArray [numBlocks]uint32
|
||||
// }
|
||||
//
|
||||
// where "numCtrs" is the number of blocks / coverable units within the
|
||||
// function, "pkgid" is the unique index assigned to this package by
|
||||
// the runtime, "funcid" is the index of this function within its containing
|
||||
// packge, and "counterArray" stores the actual counters.
|
||||
//
|
||||
// The counter variable itself is created not as a struct but as a flat
|
||||
// array of uint32's; we then use the offsets below to index into it.
|
||||
|
||||
const NumCtrsOffset = 0
|
||||
const PkgIdOffset = 1
|
||||
const FuncIdOffset = 2
|
||||
const FirstCtrOffset = 3
|
||||
215
src/internal/coverage/encodemeta/encode.go
Normal file
215
src/internal/coverage/encodemeta/encode.go
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
// 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 encodemeta
|
||||
|
||||
// This package contains APIs and helpers for encoding the meta-data
|
||||
// "blob" for a single Go package, created when coverage
|
||||
// instrumentation is turned on.
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash"
|
||||
"internal/coverage"
|
||||
"internal/coverage/stringtab"
|
||||
"internal/coverage/uleb128"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type CoverageMetaDataBuilder struct {
|
||||
stab stringtab.Writer
|
||||
funcs []funcDesc
|
||||
tmp []byte // temp work slice
|
||||
h hash.Hash
|
||||
pkgpath uint32
|
||||
pkgname uint32
|
||||
modpath uint32
|
||||
debug bool
|
||||
werr error
|
||||
}
|
||||
|
||||
func NewCoverageMetaDataBuilder(pkgpath string, pkgname string, modulepath string) (*CoverageMetaDataBuilder, error) {
|
||||
if pkgpath == "" {
|
||||
return nil, fmt.Errorf("invalid empty package path")
|
||||
}
|
||||
x := &CoverageMetaDataBuilder{
|
||||
tmp: make([]byte, 0, 256),
|
||||
h: md5.New(),
|
||||
}
|
||||
x.stab.InitWriter()
|
||||
x.stab.Lookup("")
|
||||
x.pkgpath = x.stab.Lookup(pkgpath)
|
||||
x.pkgname = x.stab.Lookup(pkgname)
|
||||
x.modpath = x.stab.Lookup(modulepath)
|
||||
io.WriteString(x.h, pkgpath)
|
||||
io.WriteString(x.h, pkgname)
|
||||
io.WriteString(x.h, modulepath)
|
||||
return x, nil
|
||||
}
|
||||
|
||||
func h32(x uint32, h hash.Hash, tmp []byte) {
|
||||
tmp = tmp[:0]
|
||||
tmp = append(tmp, []byte{0, 0, 0, 0}...)
|
||||
binary.LittleEndian.PutUint32(tmp, x)
|
||||
h.Write(tmp)
|
||||
}
|
||||
|
||||
type funcDesc struct {
|
||||
encoded []byte
|
||||
}
|
||||
|
||||
// AddFunc registers a new function with the meta data builder.
|
||||
func (b *CoverageMetaDataBuilder) AddFunc(f coverage.FuncDesc) uint {
|
||||
hashFuncDesc(b.h, &f, b.tmp)
|
||||
fd := funcDesc{}
|
||||
b.tmp = b.tmp[:0]
|
||||
b.tmp = uleb128.AppendUleb128(b.tmp, uint(len(f.Units)))
|
||||
b.tmp = uleb128.AppendUleb128(b.tmp, uint(b.stab.Lookup(f.Funcname)))
|
||||
b.tmp = uleb128.AppendUleb128(b.tmp, uint(b.stab.Lookup(f.Srcfile)))
|
||||
for _, u := range f.Units {
|
||||
b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.StLine))
|
||||
b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.StCol))
|
||||
b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.EnLine))
|
||||
b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.EnCol))
|
||||
b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.NxStmts))
|
||||
}
|
||||
lit := uint(0)
|
||||
if f.Lit {
|
||||
lit = 1
|
||||
}
|
||||
b.tmp = uleb128.AppendUleb128(b.tmp, lit)
|
||||
fd.encoded = make([]byte, len(b.tmp))
|
||||
copy(fd.encoded, b.tmp)
|
||||
rv := uint(len(b.funcs))
|
||||
b.funcs = append(b.funcs, fd)
|
||||
return rv
|
||||
}
|
||||
|
||||
func (b *CoverageMetaDataBuilder) emitFuncOffsets(w io.WriteSeeker, off int64) int64 {
|
||||
nFuncs := len(b.funcs)
|
||||
var foff int64 = coverage.CovMetaHeaderSize + int64(b.stab.Size()) + int64(nFuncs)*4
|
||||
for idx := 0; idx < nFuncs; idx++ {
|
||||
b.wrUint32(w, uint32(foff))
|
||||
foff += int64(len(b.funcs[idx].encoded))
|
||||
}
|
||||
return off + (int64(len(b.funcs)) * 4)
|
||||
}
|
||||
|
||||
func (b *CoverageMetaDataBuilder) emitFunc(w io.WriteSeeker, off int64, f funcDesc) (int64, error) {
|
||||
ew := len(f.encoded)
|
||||
if nw, err := w.Write(f.encoded); err != nil {
|
||||
return 0, err
|
||||
} else if ew != nw {
|
||||
return 0, fmt.Errorf("short write emitting coverage meta-data")
|
||||
}
|
||||
return off + int64(ew), nil
|
||||
}
|
||||
|
||||
func (b *CoverageMetaDataBuilder) reportWriteError(err error) {
|
||||
if b.werr != nil {
|
||||
b.werr = err
|
||||
}
|
||||
}
|
||||
|
||||
func (b *CoverageMetaDataBuilder) wrUint32(w io.WriteSeeker, v uint32) {
|
||||
b.tmp = b.tmp[:0]
|
||||
b.tmp = append(b.tmp, []byte{0, 0, 0, 0}...)
|
||||
binary.LittleEndian.PutUint32(b.tmp, v)
|
||||
if nw, err := w.Write(b.tmp); err != nil {
|
||||
b.reportWriteError(err)
|
||||
} else if nw != 4 {
|
||||
b.reportWriteError(fmt.Errorf("short write"))
|
||||
}
|
||||
}
|
||||
|
||||
// Emit writes the meta-data accumulated so far in this builder to 'w'.
|
||||
// Returns a hash of the meta-data payload and an error.
|
||||
func (b *CoverageMetaDataBuilder) Emit(w io.WriteSeeker) ([16]byte, error) {
|
||||
// Emit header. Length will initially be zero, we'll
|
||||
// back-patch it later.
|
||||
var digest [16]byte
|
||||
copy(digest[:], b.h.Sum(nil))
|
||||
mh := coverage.MetaSymbolHeader{
|
||||
// hash and length initially zero, will be back-patched
|
||||
PkgPath: uint32(b.pkgpath),
|
||||
PkgName: uint32(b.pkgname),
|
||||
ModulePath: uint32(b.modpath),
|
||||
NumFiles: uint32(b.stab.Nentries()),
|
||||
NumFuncs: uint32(len(b.funcs)),
|
||||
MetaHash: digest,
|
||||
}
|
||||
if b.debug {
|
||||
fmt.Fprintf(os.Stderr, "=-= writing header: %+v\n", mh)
|
||||
}
|
||||
if err := binary.Write(w, binary.LittleEndian, mh); err != nil {
|
||||
return digest, fmt.Errorf("error writing meta-file header: %v\n", err)
|
||||
}
|
||||
off := int64(coverage.CovMetaHeaderSize)
|
||||
|
||||
// Write function offsets section
|
||||
off = b.emitFuncOffsets(w, off)
|
||||
|
||||
// Check for any errors up to this point.
|
||||
if b.werr != nil {
|
||||
return digest, b.werr
|
||||
}
|
||||
|
||||
// Write string table.
|
||||
if err := b.stab.Write(w); err != nil {
|
||||
return digest, err
|
||||
}
|
||||
off += int64(b.stab.Size())
|
||||
|
||||
// Write functions
|
||||
for _, f := range b.funcs {
|
||||
var err error
|
||||
off, err = b.emitFunc(w, off, f)
|
||||
if err != nil {
|
||||
return digest, err
|
||||
}
|
||||
}
|
||||
|
||||
// Back-patch the length.
|
||||
totalLength := uint32(off)
|
||||
if _, err := w.Seek(0, os.SEEK_SET); err != nil {
|
||||
return digest, err
|
||||
}
|
||||
b.wrUint32(w, totalLength)
|
||||
if b.werr != nil {
|
||||
return digest, b.werr
|
||||
}
|
||||
return digest, nil
|
||||
}
|
||||
|
||||
// HashFuncDesc computes an md5 sum of a coverage.FuncDesc and returns
|
||||
// a digest for it.
|
||||
func HashFuncDesc(f *coverage.FuncDesc) [16]byte {
|
||||
h := md5.New()
|
||||
tmp := make([]byte, 0, 32)
|
||||
hashFuncDesc(h, f, tmp)
|
||||
var r [16]byte
|
||||
copy(r[:], h.Sum(nil))
|
||||
return r
|
||||
}
|
||||
|
||||
// hashFuncDesc incorporates a given function 'f' into the hash 'h'.
|
||||
func hashFuncDesc(h hash.Hash, f *coverage.FuncDesc, tmp []byte) {
|
||||
io.WriteString(h, f.Funcname)
|
||||
io.WriteString(h, f.Srcfile)
|
||||
for _, u := range f.Units {
|
||||
h32(u.StLine, h, tmp)
|
||||
h32(u.StCol, h, tmp)
|
||||
h32(u.EnLine, h, tmp)
|
||||
h32(u.EnCol, h, tmp)
|
||||
h32(u.NxStmts, h, tmp)
|
||||
}
|
||||
lit := uint32(0)
|
||||
if f.Lit {
|
||||
lit = 1
|
||||
}
|
||||
h32(lit, h, tmp)
|
||||
}
|
||||
132
src/internal/coverage/encodemeta/encodefile.go
Normal file
132
src/internal/coverage/encodemeta/encodefile.go
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
// 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 encodemeta
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/md5"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"internal/coverage"
|
||||
"internal/coverage/stringtab"
|
||||
"io"
|
||||
"os"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// This package contains APIs and helpers for writing out a meta-data
|
||||
// file (composed of a file header, offsets/lengths, and then a series of
|
||||
// meta-data blobs emitted by the compiler, one per Go package).
|
||||
|
||||
type CoverageMetaFileWriter struct {
|
||||
stab stringtab.Writer
|
||||
mfname string
|
||||
w *bufio.Writer
|
||||
tmp []byte
|
||||
debug bool
|
||||
}
|
||||
|
||||
func NewCoverageMetaFileWriter(mfname string, w io.Writer) *CoverageMetaFileWriter {
|
||||
r := &CoverageMetaFileWriter{
|
||||
mfname: mfname,
|
||||
w: bufio.NewWriter(w),
|
||||
tmp: make([]byte, 64),
|
||||
}
|
||||
r.stab.InitWriter()
|
||||
r.stab.Lookup("")
|
||||
return r
|
||||
}
|
||||
|
||||
func (m *CoverageMetaFileWriter) Write(finalHash [16]byte, blobs [][]byte, mode coverage.CounterMode, granularity coverage.CounterGranularity) error {
|
||||
mhsz := uint64(unsafe.Sizeof(coverage.MetaFileHeader{}))
|
||||
stSize := m.stab.Size()
|
||||
stOffset := mhsz + uint64(16*len(blobs))
|
||||
preambleLength := stOffset + uint64(stSize)
|
||||
|
||||
if m.debug {
|
||||
fmt.Fprintf(os.Stderr, "=+= sizeof(MetaFileHeader)=%d\n", mhsz)
|
||||
fmt.Fprintf(os.Stderr, "=+= preambleLength=%d stSize=%d\n", preambleLength, stSize)
|
||||
}
|
||||
|
||||
// Compute total size
|
||||
tlen := preambleLength
|
||||
for i := 0; i < len(blobs); i++ {
|
||||
tlen += uint64(len(blobs[i]))
|
||||
}
|
||||
|
||||
// Emit header
|
||||
mh := coverage.MetaFileHeader{
|
||||
Magic: coverage.CovMetaMagic,
|
||||
Version: coverage.MetaFileVersion,
|
||||
TotalLength: tlen,
|
||||
Entries: uint64(len(blobs)),
|
||||
MetaFileHash: finalHash,
|
||||
StrTabOffset: uint32(stOffset),
|
||||
StrTabLength: stSize,
|
||||
CMode: mode,
|
||||
CGranularity: granularity,
|
||||
}
|
||||
var err error
|
||||
if err = binary.Write(m.w, binary.LittleEndian, mh); err != nil {
|
||||
return fmt.Errorf("error writing %s: %v\n", m.mfname, err)
|
||||
}
|
||||
|
||||
if m.debug {
|
||||
fmt.Fprintf(os.Stderr, "=+= len(blobs) is %d\n", mh.Entries)
|
||||
}
|
||||
|
||||
// Emit package offsets section followed by package lengths section.
|
||||
off := preambleLength
|
||||
off2 := mhsz
|
||||
buf := make([]byte, 8)
|
||||
for _, blob := range blobs {
|
||||
binary.LittleEndian.PutUint64(buf, off)
|
||||
if _, err = m.w.Write(buf); err != nil {
|
||||
return fmt.Errorf("error writing %s: %v\n", m.mfname, err)
|
||||
}
|
||||
if m.debug {
|
||||
fmt.Fprintf(os.Stderr, "=+= pkg offset %d 0x%x\n", off, off)
|
||||
}
|
||||
off += uint64(len(blob))
|
||||
off2 += 8
|
||||
}
|
||||
for _, blob := range blobs {
|
||||
bl := uint64(len(blob))
|
||||
binary.LittleEndian.PutUint64(buf, bl)
|
||||
if _, err = m.w.Write(buf); err != nil {
|
||||
return fmt.Errorf("error writing %s: %v\n", m.mfname, err)
|
||||
}
|
||||
if m.debug {
|
||||
fmt.Fprintf(os.Stderr, "=+= pkg len %d 0x%x\n", bl, bl)
|
||||
}
|
||||
off2 += 8
|
||||
}
|
||||
|
||||
// Emit string table
|
||||
if err = m.stab.Write(m.w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now emit blobs themselves.
|
||||
for k, blob := range blobs {
|
||||
if m.debug {
|
||||
fmt.Fprintf(os.Stderr, "=+= writing blob %d len %d at off=%d hash %s\n", k, len(blob), off2, fmt.Sprintf("%x", md5.Sum(blob)))
|
||||
}
|
||||
if _, err = m.w.Write(blob); err != nil {
|
||||
return fmt.Errorf("error writing %s: %v\n", m.mfname, err)
|
||||
}
|
||||
if m.debug {
|
||||
fmt.Fprintf(os.Stderr, "=+= wrote package payload of %d bytes\n",
|
||||
len(blob))
|
||||
}
|
||||
off2 += uint64(len(blob))
|
||||
}
|
||||
|
||||
// Flush writer, and we're done.
|
||||
if err = m.w.Flush(); err != nil {
|
||||
return fmt.Errorf("error writing %s: %v\n", m.mfname, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
89
src/internal/coverage/stringtab/stringtab.go
Normal file
89
src/internal/coverage/stringtab/stringtab.go
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright 2022 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 stringtab
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"internal/coverage/uleb128"
|
||||
"io"
|
||||
)
|
||||
|
||||
// This package implements a string table writer utility for use in
|
||||
// emitting coverage meta-data and counter-data files.
|
||||
|
||||
type Writer struct {
|
||||
stab map[string]uint32
|
||||
strs []string
|
||||
tmp []byte
|
||||
frozen bool
|
||||
}
|
||||
|
||||
// InitWriter initializes a stringtab.Writer.
|
||||
func (stw *Writer) InitWriter() {
|
||||
stw.stab = make(map[string]uint32)
|
||||
stw.tmp = make([]byte, 64)
|
||||
}
|
||||
|
||||
// Nentries returns the number of strings interned so far.
|
||||
func (stw *Writer) Nentries() uint32 {
|
||||
return uint32(len(stw.strs))
|
||||
}
|
||||
|
||||
// Lookup looks up string 's' in the writer's table, adding
|
||||
// a new entry if need be, and returning an index into the table.
|
||||
func (stw *Writer) Lookup(s string) uint32 {
|
||||
if idx, ok := stw.stab[s]; ok {
|
||||
return idx
|
||||
}
|
||||
idx := uint32(len(stw.strs))
|
||||
stw.stab[s] = idx
|
||||
stw.strs = append(stw.strs, s)
|
||||
return idx
|
||||
}
|
||||
|
||||
// Size computes the memory in bytes needed for the serialized
|
||||
// version of a stringtab.Writer.
|
||||
func (stw *Writer) Size() uint32 {
|
||||
rval := uint32(0)
|
||||
stw.tmp = stw.tmp[:0]
|
||||
stw.tmp = uleb128.AppendUleb128(stw.tmp, uint(len(stw.strs)))
|
||||
rval += uint32(len(stw.tmp))
|
||||
for _, s := range stw.strs {
|
||||
stw.tmp = stw.tmp[:0]
|
||||
slen := uint(len(s))
|
||||
stw.tmp = uleb128.AppendUleb128(stw.tmp, slen)
|
||||
rval += uint32(len(stw.tmp)) + uint32(slen)
|
||||
}
|
||||
return rval
|
||||
}
|
||||
|
||||
// Write writes the string table in serialized form to the specified
|
||||
// io.Writer.
|
||||
func (stw *Writer) Write(w io.Writer) error {
|
||||
wr128 := func(v uint) error {
|
||||
stw.tmp = stw.tmp[:0]
|
||||
stw.tmp = uleb128.AppendUleb128(stw.tmp, v)
|
||||
if nw, err := w.Write(stw.tmp); err != nil {
|
||||
return fmt.Errorf("writing string table: %v", err)
|
||||
} else if nw != len(stw.tmp) {
|
||||
return fmt.Errorf("short write emitting stringtab uleb")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := wr128(uint(len(stw.strs))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range stw.strs {
|
||||
if err := wr128(uint(len(s))); err != nil {
|
||||
return err
|
||||
}
|
||||
if nw, err := w.Write([]byte(s)); err != nil {
|
||||
return fmt.Errorf("writing string table: %v\n", err)
|
||||
} else if nw != len([]byte(s)) {
|
||||
return fmt.Errorf("short write emitting stringtab")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
20
src/internal/coverage/uleb128/uleb128.go
Normal file
20
src/internal/coverage/uleb128/uleb128.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// 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 uleb128
|
||||
|
||||
func AppendUleb128(b []byte, v uint) []byte {
|
||||
for {
|
||||
c := uint8(v & 0x7f)
|
||||
v >>= 7
|
||||
if v != 0 {
|
||||
c |= 0x80
|
||||
}
|
||||
b = append(b, c)
|
||||
if c&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue