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