xref: /aosp_15_r20/external/swiftshader/tests/regres/cov/import.go (revision 03ce13f70fcc45d86ee91b7ee4cab1936a95046e)
1// Copyright 2020 The SwiftShader Authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package cov
16
17import (
18	"bytes"
19	"encoding/binary"
20	"encoding/json"
21	"fmt"
22	"os"
23	"os/exec"
24	"path/filepath"
25	"strings"
26
27	"swiftshader.googlesource.com/SwiftShader/tests/regres/llvm"
28)
29
30// File describes the coverage spans in a single source file.
31type File struct {
32	Path      string
33	Covered   SpanList // Spans with coverage
34	Uncovered SpanList // Compiled spans without coverage
35}
36
37// Coverage describes the coverage spans for all the source files for a single
38// process invocation.
39type Coverage struct {
40	Files []File
41}
42
43// Env holds the enviroment settings for performing coverage processing.
44type Env struct {
45	LLVM     llvm.Toolchain
46	RootDir  string // path to SwiftShader git root directory
47	ExePath  string // path to the executable binary
48	TurboCov string // path to turbo-cov (optional)
49}
50
51// AppendRuntimeEnv returns the environment variables env with the
52// LLVM_PROFILE_FILE environment variable appended.
53func AppendRuntimeEnv(env []string, coverageFile string) []string {
54	return append(env, "LLVM_PROFILE_FILE="+coverageFile)
55}
56
57// AllSourceFiles returns a *Coverage containing all the source files without
58// coverage data. This populates the coverage view with files even if they
59// didn't get compiled.
60func (e Env) AllSourceFiles() *Coverage {
61	var ignorePaths = map[string]bool{
62		"src/System": true,
63	}
64
65	// Gather all the source files to include them even if there is no coverage
66	// information produced for these files. This highlights files that aren't
67	// even compiled.
68	cov := Coverage{}
69	allFiles := map[string]struct{}{}
70	filepath.Walk(filepath.Join(e.RootDir, "src"), func(path string, info os.FileInfo, err error) error {
71		if err != nil {
72			return err
73		}
74		rel, err := filepath.Rel(e.RootDir, path)
75		if err != nil || ignorePaths[rel] {
76			return filepath.SkipDir
77		}
78		if !info.IsDir() {
79			switch filepath.Ext(path) {
80			case ".h", ".c", ".cc", ".cpp", ".hpp":
81				if _, seen := allFiles[rel]; !seen {
82					cov.Files = append(cov.Files, File{Path: rel})
83				}
84			}
85		}
86		return nil
87	})
88	return &cov
89}
90
91// Import uses the llvm-profdata and llvm-cov tools to import the coverage
92// information from a .profraw file.
93func (e Env) Import(profrawPath string) (*Coverage, error) {
94	profdata := profrawPath + ".profdata"
95
96	if err := exec.Command(e.LLVM.Profdata(), "merge", "-sparse", profrawPath, "-output", profdata).Run(); err != nil {
97		return nil, fmt.Errorf("llvm-profdata errored: %w", err)
98	}
99	defer os.Remove(profdata)
100
101	if e.TurboCov == "" {
102		args := []string{
103			"export",
104			e.ExePath,
105			"-instr-profile=" + profdata,
106			"-format=text",
107		}
108		if e.LLVM.Version.GreaterEqual(llvm.Version{Major: 9}) {
109			// LLVM 9 has new flags that omit stuff we don't care about.
110			args = append(args,
111				"-skip-expansions",
112				"-skip-functions",
113			)
114		}
115
116		data, err := exec.Command(e.LLVM.Cov(), args...).Output()
117		if err != nil {
118			return nil, fmt.Errorf("llvm-cov errored: %v\n%v", string(err.(*exec.ExitError).Stderr), err)
119		}
120		cov, err := e.parseCov(data)
121		if err != nil {
122			return nil, fmt.Errorf("failed to parse coverage json data: %w", err)
123		}
124		return cov, nil
125	}
126
127	data, err := exec.Command(e.TurboCov, e.ExePath, profdata).Output()
128	if err != nil {
129		return nil, fmt.Errorf("turbo-cov errored: %v\n%v", string(err.(*exec.ExitError).Stderr), err)
130	}
131	cov, err := e.parseTurboCov(data)
132	if err != nil {
133		return nil, fmt.Errorf("failed to process turbo-cov output: %w", err)
134	}
135
136	return cov, nil
137}
138
139func appendSpan(spans []Span, span Span) []Span {
140	if c := len(spans); c > 0 && spans[c-1].End == span.Start {
141		spans[c-1].End = span.End
142	} else {
143		spans = append(spans, span)
144	}
145	return spans
146}
147
148// https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
149// https://stackoverflow.com/a/56792192
150func (e Env) parseCov(raw []byte) (*Coverage, error) {
151	// line int, col int, count int64, hasCount bool, isRegionEntry bool
152	type segment []interface{}
153
154	type file struct {
155		// expansions ignored
156		Name     string    `json:"filename"`
157		Segments []segment `json:"segments"`
158		// summary ignored
159	}
160
161	type data struct {
162		Files []file `json:"files"`
163	}
164
165	root := struct {
166		Data []data `json:"data"`
167	}{}
168	err := json.NewDecoder(bytes.NewReader(raw)).Decode(&root)
169	if err != nil {
170		return nil, err
171	}
172
173	c := &Coverage{Files: make([]File, 0, len(root.Data[0].Files))}
174	for _, f := range root.Data[0].Files {
175		relpath, err := filepath.Rel(e.RootDir, f.Name)
176		if err != nil {
177			return nil, err
178		}
179		if strings.HasPrefix(relpath, "..") {
180			continue
181		}
182		file := File{Path: relpath}
183		for sIdx := 0; sIdx+1 < len(f.Segments); sIdx++ {
184			start := Location{(int)(f.Segments[sIdx][0].(float64)), (int)(f.Segments[sIdx][1].(float64))}
185			end := Location{(int)(f.Segments[sIdx+1][0].(float64)), (int)(f.Segments[sIdx+1][1].(float64))}
186			if covered := f.Segments[sIdx][2].(float64) != 0; covered {
187				file.Covered = appendSpan(file.Covered, Span{start, end})
188			} else {
189				file.Uncovered = appendSpan(file.Uncovered, Span{start, end})
190			}
191		}
192		if len(file.Covered) > 0 {
193			c.Files = append(c.Files, file)
194		}
195	}
196
197	return c, nil
198}
199
200func (e Env) parseTurboCov(data []byte) (*Coverage, error) {
201	u32 := func() uint32 {
202		out := binary.LittleEndian.Uint32(data)
203		data = data[4:]
204		return out
205	}
206	u8 := func() uint8 {
207		out := data[0]
208		data = data[1:]
209		return out
210	}
211	str := func() string {
212		len := u32()
213		out := data[:len]
214		data = data[len:]
215		return string(out)
216	}
217
218	numFiles := u32()
219	c := &Coverage{Files: make([]File, 0, numFiles)}
220	for i := 0; i < int(numFiles); i++ {
221		path := str()
222		relpath, err := filepath.Rel(e.RootDir, path)
223		if err != nil {
224			return nil, err
225		}
226		if strings.HasPrefix(relpath, "..") {
227			continue
228		}
229
230		file := File{Path: relpath}
231
232		type segment struct {
233			location Location
234			count    int
235			covered  bool
236		}
237
238		numSegements := u32()
239		segments := make([]segment, numSegements)
240		for j := range segments {
241			segment := &segments[j]
242			segment.location.Line = int(u32())
243			segment.location.Column = int(u32())
244			segment.count = int(u32())
245			segment.covered = u8() != 0
246		}
247
248		for sIdx := 0; sIdx+1 < len(segments); sIdx++ {
249			start := segments[sIdx].location
250			end := segments[sIdx+1].location
251			if segments[sIdx].covered {
252				if segments[sIdx].count > 0 {
253					file.Covered = appendSpan(file.Covered, Span{start, end})
254				} else {
255					file.Uncovered = appendSpan(file.Uncovered, Span{start, end})
256				}
257			}
258		}
259
260		if len(file.Covered) > 0 {
261			c.Files = append(c.Files, file)
262		}
263	}
264
265	return c, nil
266}
267
268// Path is a tree node path formed from a list of strings
269type Path []string
270