github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/coverage/encodemeta/encode.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 encodemeta 6 7 // This package contains APIs and helpers for encoding the meta-data 8 // "blob" for a single Go package, created when coverage 9 // instrumentation is turned on. 10 11 import ( 12 "bytes" 13 "crypto/md5" 14 "encoding/binary" 15 "fmt" 16 "hash" 17 "io" 18 "os" 19 20 "github.com/go-asm/go/coverage" 21 "github.com/go-asm/go/coverage/stringtab" 22 "github.com/go-asm/go/coverage/uleb128" 23 ) 24 25 type CoverageMetaDataBuilder struct { 26 stab stringtab.Writer 27 funcs []funcDesc 28 tmp []byte // temp work slice 29 h hash.Hash 30 pkgpath uint32 31 pkgname uint32 32 modpath uint32 33 debug bool 34 werr error 35 } 36 37 func NewCoverageMetaDataBuilder(pkgpath string, pkgname string, modulepath string) (*CoverageMetaDataBuilder, error) { 38 if pkgpath == "" { 39 return nil, fmt.Errorf("invalid empty package path") 40 } 41 x := &CoverageMetaDataBuilder{ 42 tmp: make([]byte, 0, 256), 43 h: md5.New(), 44 } 45 x.stab.InitWriter() 46 x.stab.Lookup("") 47 x.pkgpath = x.stab.Lookup(pkgpath) 48 x.pkgname = x.stab.Lookup(pkgname) 49 x.modpath = x.stab.Lookup(modulepath) 50 io.WriteString(x.h, pkgpath) 51 io.WriteString(x.h, pkgname) 52 io.WriteString(x.h, modulepath) 53 return x, nil 54 } 55 56 func h32(x uint32, h hash.Hash, tmp []byte) { 57 tmp = tmp[:0] 58 tmp = append(tmp, []byte{0, 0, 0, 0}...) 59 binary.LittleEndian.PutUint32(tmp, x) 60 h.Write(tmp) 61 } 62 63 type funcDesc struct { 64 encoded []byte 65 } 66 67 // AddFunc registers a new function with the meta data builder. 68 func (b *CoverageMetaDataBuilder) AddFunc(f coverage.FuncDesc) uint { 69 hashFuncDesc(b.h, &f, b.tmp) 70 fd := funcDesc{} 71 b.tmp = b.tmp[:0] 72 b.tmp = uleb128.AppendUleb128(b.tmp, uint(len(f.Units))) 73 b.tmp = uleb128.AppendUleb128(b.tmp, uint(b.stab.Lookup(f.Funcname))) 74 b.tmp = uleb128.AppendUleb128(b.tmp, uint(b.stab.Lookup(f.Srcfile))) 75 for _, u := range f.Units { 76 b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.StLine)) 77 b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.StCol)) 78 b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.EnLine)) 79 b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.EnCol)) 80 b.tmp = uleb128.AppendUleb128(b.tmp, uint(u.NxStmts)) 81 } 82 lit := uint(0) 83 if f.Lit { 84 lit = 1 85 } 86 b.tmp = uleb128.AppendUleb128(b.tmp, lit) 87 fd.encoded = bytes.Clone(b.tmp) 88 rv := uint(len(b.funcs)) 89 b.funcs = append(b.funcs, fd) 90 return rv 91 } 92 93 func (b *CoverageMetaDataBuilder) emitFuncOffsets(w io.WriteSeeker, off int64) int64 { 94 nFuncs := len(b.funcs) 95 var foff int64 = coverage.CovMetaHeaderSize + int64(b.stab.Size()) + int64(nFuncs)*4 96 for idx := 0; idx < nFuncs; idx++ { 97 b.wrUint32(w, uint32(foff)) 98 foff += int64(len(b.funcs[idx].encoded)) 99 } 100 return off + (int64(len(b.funcs)) * 4) 101 } 102 103 func (b *CoverageMetaDataBuilder) emitFunc(w io.WriteSeeker, off int64, f funcDesc) (int64, error) { 104 ew := len(f.encoded) 105 if nw, err := w.Write(f.encoded); err != nil { 106 return 0, err 107 } else if ew != nw { 108 return 0, fmt.Errorf("short write emitting coverage meta-data") 109 } 110 return off + int64(ew), nil 111 } 112 113 func (b *CoverageMetaDataBuilder) reportWriteError(err error) { 114 if b.werr != nil { 115 b.werr = err 116 } 117 } 118 119 func (b *CoverageMetaDataBuilder) wrUint32(w io.WriteSeeker, v uint32) { 120 b.tmp = b.tmp[:0] 121 b.tmp = append(b.tmp, []byte{0, 0, 0, 0}...) 122 binary.LittleEndian.PutUint32(b.tmp, v) 123 if nw, err := w.Write(b.tmp); err != nil { 124 b.reportWriteError(err) 125 } else if nw != 4 { 126 b.reportWriteError(fmt.Errorf("short write")) 127 } 128 } 129 130 // Emit writes the meta-data accumulated so far in this builder to 'w'. 131 // Returns a hash of the meta-data payload and an error. 132 func (b *CoverageMetaDataBuilder) Emit(w io.WriteSeeker) ([16]byte, error) { 133 // Emit header. Length will initially be zero, we'll 134 // back-patch it later. 135 var digest [16]byte 136 copy(digest[:], b.h.Sum(nil)) 137 mh := coverage.MetaSymbolHeader{ 138 // hash and length initially zero, will be back-patched 139 PkgPath: uint32(b.pkgpath), 140 PkgName: uint32(b.pkgname), 141 ModulePath: uint32(b.modpath), 142 NumFiles: uint32(b.stab.Nentries()), 143 NumFuncs: uint32(len(b.funcs)), 144 MetaHash: digest, 145 } 146 if b.debug { 147 fmt.Fprintf(os.Stderr, "=-= writing header: %+v\n", mh) 148 } 149 if err := binary.Write(w, binary.LittleEndian, mh); err != nil { 150 return digest, fmt.Errorf("error writing meta-file header: %v", err) 151 } 152 off := int64(coverage.CovMetaHeaderSize) 153 154 // Write function offsets section 155 off = b.emitFuncOffsets(w, off) 156 157 // Check for any errors up to this point. 158 if b.werr != nil { 159 return digest, b.werr 160 } 161 162 // Write string table. 163 if err := b.stab.Write(w); err != nil { 164 return digest, err 165 } 166 off += int64(b.stab.Size()) 167 168 // Write functions 169 for _, f := range b.funcs { 170 var err error 171 off, err = b.emitFunc(w, off, f) 172 if err != nil { 173 return digest, err 174 } 175 } 176 177 // Back-patch the length. 178 totalLength := uint32(off) 179 if _, err := w.Seek(0, io.SeekStart); err != nil { 180 return digest, err 181 } 182 b.wrUint32(w, totalLength) 183 if b.werr != nil { 184 return digest, b.werr 185 } 186 return digest, nil 187 } 188 189 // HashFuncDesc computes an md5 sum of a coverage.FuncDesc and returns 190 // a digest for it. 191 func HashFuncDesc(f *coverage.FuncDesc) [16]byte { 192 h := md5.New() 193 tmp := make([]byte, 0, 32) 194 hashFuncDesc(h, f, tmp) 195 var r [16]byte 196 copy(r[:], h.Sum(nil)) 197 return r 198 } 199 200 // hashFuncDesc incorporates a given function 'f' into the hash 'h'. 201 func hashFuncDesc(h hash.Hash, f *coverage.FuncDesc, tmp []byte) { 202 io.WriteString(h, f.Funcname) 203 io.WriteString(h, f.Srcfile) 204 for _, u := range f.Units { 205 h32(u.StLine, h, tmp) 206 h32(u.StCol, h, tmp) 207 h32(u.EnLine, h, tmp) 208 h32(u.EnCol, h, tmp) 209 h32(u.NxStmts, h, tmp) 210 } 211 lit := uint32(0) 212 if f.Lit { 213 lit = 1 214 } 215 h32(lit, h, tmp) 216 }