// 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" "encoding/binary" "fmt" "hash/fnv" "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", 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", 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", 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 { h := fnv.New128a() h.Write(blob) fmt.Fprintf(os.Stderr, "=+= writing blob %d len %d at off=%d hash %s\n", k, len(blob), off2, fmt.Sprintf("%x", h.Sum(nil))) } if _, err = m.w.Write(blob); err != nil { return fmt.Errorf("error writing %s: %v", 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", m.mfname, err) } return nil }