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 "os" 22 "os/exec" 23 "path/filepath" 24 "strings" 25 26 "../cause" 27 "../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/Common": true, 63 "src/Main": true, 64 "src/OpenGL": true, 65 "src/Renderer": true, 66 "src/Shader": true, 67 "src/System": true, 68 } 69 70 // Gather all the source files to include them even if there is no coverage 71 // information produced for these files. This highlights files that aren't 72 // even compiled. 73 cov := Coverage{} 74 allFiles := map[string]struct{}{} 75 filepath.Walk(filepath.Join(e.RootDir, "src"), func(path string, info os.FileInfo, err error) error { 76 if err != nil { 77 return err 78 } 79 rel, err := filepath.Rel(e.RootDir, path) 80 if err != nil || ignorePaths[rel] { 81 return filepath.SkipDir 82 } 83 if !info.IsDir() { 84 switch filepath.Ext(path) { 85 case ".h", ".c", ".cc", ".cpp", ".hpp": 86 if _, seen := allFiles[rel]; !seen { 87 cov.Files = append(cov.Files, File{Path: rel}) 88 } 89 } 90 } 91 return nil 92 }) 93 return &cov 94} 95 96// Import uses the llvm-profdata and llvm-cov tools to import the coverage 97// information from a .profraw file. 98func (e Env) Import(profrawPath string) (*Coverage, error) { 99 profdata := profrawPath + ".profdata" 100 101 if err := exec.Command(e.LLVM.Profdata(), "merge", "-sparse", profrawPath, "-output", profdata).Run(); err != nil { 102 return nil, cause.Wrap(err, "llvm-profdata errored") 103 } 104 defer os.Remove(profdata) 105 106 if e.TurboCov == "" { 107 args := []string{ 108 "export", 109 e.ExePath, 110 "-instr-profile=" + profdata, 111 "-format=text", 112 } 113 if e.LLVM.Version.GreaterEqual(llvm.Version{Major: 9}) { 114 // LLVM 9 has new flags that omit stuff we don't care about. 115 args = append(args, 116 "-skip-expansions", 117 "-skip-functions", 118 ) 119 } 120 121 data, err := exec.Command(e.LLVM.Cov(), args...).Output() 122 if err != nil { 123 return nil, cause.Wrap(err, "llvm-cov errored: %v", string(err.(*exec.ExitError).Stderr)) 124 } 125 cov, err := e.parseCov(data) 126 if err != nil { 127 return nil, cause.Wrap(err, "Couldn't parse coverage json data") 128 } 129 return cov, nil 130 } 131 132 data, err := exec.Command(e.TurboCov, e.ExePath, profdata).Output() 133 if err != nil { 134 return nil, cause.Wrap(err, "turbo-cov errored: %v", string(err.(*exec.ExitError).Stderr)) 135 } 136 cov, err := e.parseTurboCov(data) 137 if err != nil { 138 return nil, cause.Wrap(err, "Couldn't process turbo-cov output") 139 } 140 141 return cov, nil 142} 143 144func appendSpan(spans []Span, span Span) []Span { 145 if c := len(spans); c > 0 && spans[c-1].End == span.Start { 146 spans[c-1].End = span.End 147 } else { 148 spans = append(spans, span) 149 } 150 return spans 151} 152 153// https://clang.llvm.org/docs/SourceBasedCodeCoverage.html 154// https://stackoverflow.com/a/56792192 155func (e Env) parseCov(raw []byte) (*Coverage, error) { 156 // line int, col int, count int64, hasCount bool, isRegionEntry bool 157 type segment []interface{} 158 159 type file struct { 160 // expansions ignored 161 Name string `json:"filename"` 162 Segments []segment `json:"segments"` 163 // summary ignored 164 } 165 166 type data struct { 167 Files []file `json:"files"` 168 } 169 170 root := struct { 171 Data []data `json:"data"` 172 }{} 173 err := json.NewDecoder(bytes.NewReader(raw)).Decode(&root) 174 if err != nil { 175 return nil, err 176 } 177 178 c := &Coverage{Files: make([]File, 0, len(root.Data[0].Files))} 179 for _, f := range root.Data[0].Files { 180 relpath, err := filepath.Rel(e.RootDir, f.Name) 181 if err != nil { 182 return nil, err 183 } 184 if strings.HasPrefix(relpath, "..") { 185 continue 186 } 187 file := File{Path: relpath} 188 for sIdx := 0; sIdx+1 < len(f.Segments); sIdx++ { 189 start := Location{(int)(f.Segments[sIdx][0].(float64)), (int)(f.Segments[sIdx][1].(float64))} 190 end := Location{(int)(f.Segments[sIdx+1][0].(float64)), (int)(f.Segments[sIdx+1][1].(float64))} 191 if covered := f.Segments[sIdx][2].(float64) != 0; covered { 192 file.Covered = appendSpan(file.Covered, Span{start, end}) 193 } else { 194 file.Uncovered = appendSpan(file.Uncovered, Span{start, end}) 195 } 196 } 197 if len(file.Covered) > 0 { 198 c.Files = append(c.Files, file) 199 } 200 } 201 202 return c, nil 203} 204 205func (e Env) parseTurboCov(data []byte) (*Coverage, error) { 206 u32 := func() uint32 { 207 out := binary.LittleEndian.Uint32(data) 208 data = data[4:] 209 return out 210 } 211 u8 := func() uint8 { 212 out := data[0] 213 data = data[1:] 214 return out 215 } 216 str := func() string { 217 len := u32() 218 out := data[:len] 219 data = data[len:] 220 return string(out) 221 } 222 223 numFiles := u32() 224 c := &Coverage{Files: make([]File, 0, numFiles)} 225 for i := 0; i < int(numFiles); i++ { 226 path := str() 227 relpath, err := filepath.Rel(e.RootDir, path) 228 if err != nil { 229 return nil, err 230 } 231 if strings.HasPrefix(relpath, "..") { 232 continue 233 } 234 235 file := File{Path: relpath} 236 237 type segment struct { 238 location Location 239 count int 240 covered bool 241 } 242 243 numSegements := u32() 244 segments := make([]segment, numSegements) 245 for j := range segments { 246 segment := &segments[j] 247 segment.location.Line = int(u32()) 248 segment.location.Column = int(u32()) 249 segment.count = int(u32()) 250 segment.covered = u8() != 0 251 } 252 253 for sIdx := 0; sIdx+1 < len(segments); sIdx++ { 254 start := segments[sIdx].location 255 end := segments[sIdx+1].location 256 if segments[sIdx].covered { 257 if segments[sIdx].count > 0 { 258 file.Covered = appendSpan(file.Covered, Span{start, end}) 259 } else { 260 file.Uncovered = appendSpan(file.Uncovered, Span{start, end}) 261 } 262 } 263 } 264 265 if len(file.Covered) > 0 { 266 c.Files = append(c.Files, file) 267 } 268 } 269 270 return c, nil 271} 272 273// Path is a tree node path formed from a list of strings 274type Path []string 275