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
5package encodemeta
6
7import (
8	"bufio"
9	"crypto/md5"
10	"encoding/binary"
11	"fmt"
12	"internal/coverage"
13	"internal/coverage/stringtab"
14	"io"
15	"os"
16	"unsafe"
17)
18
19// This package contains APIs and helpers for writing out a meta-data
20// file (composed of a file header, offsets/lengths, and then a series of
21// meta-data blobs emitted by the compiler, one per Go package).
22
23type CoverageMetaFileWriter struct {
24	stab   stringtab.Writer
25	mfname string
26	w      *bufio.Writer
27	tmp    []byte
28	debug  bool
29}
30
31func NewCoverageMetaFileWriter(mfname string, w io.Writer) *CoverageMetaFileWriter {
32	r := &CoverageMetaFileWriter{
33		mfname: mfname,
34		w:      bufio.NewWriter(w),
35		tmp:    make([]byte, 64),
36	}
37	r.stab.InitWriter()
38	r.stab.Lookup("")
39	return r
40}
41
42func (m *CoverageMetaFileWriter) Write(finalHash [16]byte, blobs [][]byte, mode coverage.CounterMode, granularity coverage.CounterGranularity) error {
43	mhsz := uint64(unsafe.Sizeof(coverage.MetaFileHeader{}))
44	stSize := m.stab.Size()
45	stOffset := mhsz + uint64(16*len(blobs))
46	preambleLength := stOffset + uint64(stSize)
47
48	if m.debug {
49		fmt.Fprintf(os.Stderr, "=+= sizeof(MetaFileHeader)=%d\n", mhsz)
50		fmt.Fprintf(os.Stderr, "=+= preambleLength=%d stSize=%d\n", preambleLength, stSize)
51	}
52
53	// Compute total size
54	tlen := preambleLength
55	for i := 0; i < len(blobs); i++ {
56		tlen += uint64(len(blobs[i]))
57	}
58
59	// Emit header
60	mh := coverage.MetaFileHeader{
61		Magic:        coverage.CovMetaMagic,
62		Version:      coverage.MetaFileVersion,
63		TotalLength:  tlen,
64		Entries:      uint64(len(blobs)),
65		MetaFileHash: finalHash,
66		StrTabOffset: uint32(stOffset),
67		StrTabLength: stSize,
68		CMode:        mode,
69		CGranularity: granularity,
70	}
71	var err error
72	if err = binary.Write(m.w, binary.LittleEndian, mh); err != nil {
73		return fmt.Errorf("error writing %s: %v", m.mfname, err)
74	}
75
76	if m.debug {
77		fmt.Fprintf(os.Stderr, "=+= len(blobs) is %d\n", mh.Entries)
78	}
79
80	// Emit package offsets section followed by package lengths section.
81	off := preambleLength
82	off2 := mhsz
83	buf := make([]byte, 8)
84	for _, blob := range blobs {
85		binary.LittleEndian.PutUint64(buf, off)
86		if _, err = m.w.Write(buf); err != nil {
87			return fmt.Errorf("error writing %s: %v", m.mfname, err)
88		}
89		if m.debug {
90			fmt.Fprintf(os.Stderr, "=+= pkg offset %d 0x%x\n", off, off)
91		}
92		off += uint64(len(blob))
93		off2 += 8
94	}
95	for _, blob := range blobs {
96		bl := uint64(len(blob))
97		binary.LittleEndian.PutUint64(buf, bl)
98		if _, err = m.w.Write(buf); err != nil {
99			return fmt.Errorf("error writing %s: %v", m.mfname, err)
100		}
101		if m.debug {
102			fmt.Fprintf(os.Stderr, "=+= pkg len %d 0x%x\n", bl, bl)
103		}
104		off2 += 8
105	}
106
107	// Emit string table
108	if err = m.stab.Write(m.w); err != nil {
109		return err
110	}
111
112	// Now emit blobs themselves.
113	for k, blob := range blobs {
114		if m.debug {
115			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)))
116		}
117		if _, err = m.w.Write(blob); err != nil {
118			return fmt.Errorf("error writing %s: %v", m.mfname, err)
119		}
120		if m.debug {
121			fmt.Fprintf(os.Stderr, "=+= wrote package payload of %d bytes\n",
122				len(blob))
123		}
124		off2 += uint64(len(blob))
125	}
126
127	// Flush writer, and we're done.
128	if err = m.w.Flush(); err != nil {
129		return fmt.Errorf("error writing %s: %v", m.mfname, err)
130	}
131	return nil
132}
133