From: Than McIntosh Date: Wed, 29 Sep 2021 20:41:49 +0000 (-0400) Subject: internal/coverage: add coverage meta-data encoder X-Git-Tag: go1.20rc1~932 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=f951f697c45ace2f00dccd8d2533463b6538dc36;p=gostls13.git 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 Reviewed-by: David Chase TryBot-Result: Gopher Robot --- diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index efd28dfc21..2d88053382 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -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". diff --git a/src/internal/coverage/defs.go b/src/internal/coverage/defs.go new file mode 100644 index 0000000000..4ffbb91bb8 --- /dev/null +++ b/src/internal/coverage/defs.go @@ -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------ +// +// +// ... +// --package lengths table------ +// +// +// ... +// --string table------ +// 8 +// "somestring" +// ... +// --package payloads------ +// +// +// ... +// +// 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.", 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: +// | modulepath: +// | nfiles: 1 +// | nfunctions: 2 +// --func offsets table------ +// +// +// --string table (contains all files and functions)------ +// | 4 +// | "p.go" +// | 5 +// | "small" +// | 6 +// | "Medium" +// --func 0------ +// | num units: 3 +// | func name: S1 (index into string table) +// | file: S0 (index into string table) +// | : S0 L6 L8 2 +// | : S0 L9 L9 1 +// | : S0 L11 L11 1 +// --func 1------ +// | num units: 1 +// | func name: S2 (index into string table) +// | file: S0 (index into string table) +// | : 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 "" +} + +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 "" +} + +//..................................................................... +// +// 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=", +// "argv0=", "argv1=", 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 diff --git a/src/internal/coverage/encodemeta/encode.go b/src/internal/coverage/encodemeta/encode.go new file mode 100644 index 0000000000..a01e6fc9ea --- /dev/null +++ b/src/internal/coverage/encodemeta/encode.go @@ -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) +} diff --git a/src/internal/coverage/encodemeta/encodefile.go b/src/internal/coverage/encodemeta/encodefile.go new file mode 100644 index 0000000000..d6e0938566 --- /dev/null +++ b/src/internal/coverage/encodemeta/encodefile.go @@ -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 +} diff --git a/src/internal/coverage/stringtab/stringtab.go b/src/internal/coverage/stringtab/stringtab.go new file mode 100644 index 0000000000..2aba3321be --- /dev/null +++ b/src/internal/coverage/stringtab/stringtab.go @@ -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 +} diff --git a/src/internal/coverage/uleb128/uleb128.go b/src/internal/coverage/uleb128/uleb128.go new file mode 100644 index 0000000000..e5cd92a03e --- /dev/null +++ b/src/internal/coverage/uleb128/uleb128.go @@ -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 +}