1// Copyright 2019 The Chromium OS Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package main 6 7import ( 8 "encoding/json" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "path" 13 "path/filepath" 14 "strings" 15) 16 17type useTidyMode int 18 19const clangTidyCrashSubstring = "PLEASE submit a bug report" 20 21const ( 22 tidyModeNone useTidyMode = iota 23 tidyModeAll 24 tidyModeTricium 25) 26 27func processClangTidyFlags(builder *commandBuilder) (cSrcFile string, clangTidyFlags []string, mode useTidyMode) { 28 builder.transformArgs(func(arg builderArg) string { 29 const prefix = "-clang-tidy-flag=" 30 if !strings.HasPrefix(arg.value, prefix) { 31 return arg.value 32 } 33 34 clangTidyFlags = append(clangTidyFlags, arg.value[len(prefix):]) 35 return "" 36 }) 37 38 withTidy, _ := builder.env.getenv("WITH_TIDY") 39 if withTidy == "" { 40 return "", clangTidyFlags, tidyModeNone 41 } 42 srcFileSuffixes := []string{ 43 ".c", 44 ".cc", 45 ".cpp", 46 ".C", 47 ".cxx", 48 ".c++", 49 } 50 cSrcFile = "" 51 srcSuffix := "" 52 lastArg := "" 53 for _, arg := range builder.args { 54 if lastArg != "-o" { 55 for _, suffix := range srcFileSuffixes { 56 if strings.HasSuffix(arg.value, suffix) { 57 srcSuffix = suffix 58 cSrcFile = arg.value 59 break 60 } 61 } 62 } 63 lastArg = arg.value 64 } 65 66 if cSrcFile == "" { 67 return "", clangTidyFlags, tidyModeNone 68 } 69 70 if withTidy == "tricium" { 71 // Files generated from protobufs can result in _many_ clang-tidy complaints, and aren't 72 // worth linting in general. Don't. 73 if strings.HasSuffix(cSrcFile, ".pb"+srcSuffix) { 74 mode = tidyModeNone 75 } else { 76 mode = tidyModeTricium 77 } 78 } else { 79 mode = tidyModeAll 80 } 81 return cSrcFile, clangTidyFlags, mode 82} 83 84func calcClangTidyInvocation(env env, clangCmd *command, cSrcFile string, tidyFlags ...string) (*command, error) { 85 resourceDir, err := getClangResourceDir(env, clangCmd.Path) 86 if err != nil { 87 return nil, err 88 } 89 90 clangTidyPath := filepath.Join(filepath.Dir(clangCmd.Path), "clang-tidy") 91 args := append([]string{}, tidyFlags...) 92 args = append(args, cSrcFile, "--", "-resource-dir="+resourceDir) 93 args = append(args, clangCmd.Args...) 94 return &command{ 95 Path: clangTidyPath, 96 Args: args, 97 EnvUpdates: clangCmd.EnvUpdates, 98 }, nil 99} 100 101func runClangTidyForTricium(env env, clangCmd *command, cSrcFile, fixesDir string, extraTidyFlags []string, crashArtifactsDir string) error { 102 if err := os.MkdirAll(fixesDir, 0777); err != nil { 103 return fmt.Errorf("creating fixes directory at %q: %v", fixesDir, err) 104 } 105 106 f, err := ioutil.TempFile(fixesDir, "lints-") 107 if err != nil { 108 return fmt.Errorf("making tempfile for tidy: %v", err) 109 } 110 f.Close() 111 112 // `f` is an 'anchor'; it ensures we won't create a similarly-named file in the future. 113 // Hence, we can't delete it. 114 fixesFilePath := f.Name() + ".yaml" 115 fixesMetadataPath := f.Name() + ".json" 116 117 // FIXME(gbiv): Remove `-checks=*` when testing is complete; we should defer to .clang-tidy 118 // files, which are both more expressive and more approachable than `-checks=*`. 119 extraTidyFlags = append(extraTidyFlags, "-checks=*", "--export-fixes="+fixesFilePath) 120 clangTidyCmd, err := calcClangTidyInvocation(env, clangCmd, cSrcFile, extraTidyFlags...) 121 if err != nil { 122 return fmt.Errorf("calculating tidy invocation: %v", err) 123 } 124 125 stdstreams := &strings.Builder{} 126 // Note: We pass nil as stdin as we checked before that the compiler 127 // was invoked with a source file argument. 128 exitCode, err := wrapSubprocessErrorWithSourceLoc(clangTidyCmd, 129 env.run(clangTidyCmd, nil, stdstreams, stdstreams)) 130 if err != nil { 131 return err 132 } 133 134 type crashOutput struct { 135 CrashReproducerPath string `json:"crash_reproducer_path"` 136 Stdstreams string `json:"stdstreams"` 137 } 138 139 type metadata struct { 140 Args []string `json:"args"` 141 CrashOutput *crashOutput `json:"crash_output"` 142 Executable string `json:"executable"` 143 ExitCode int `json:"exit_code"` 144 LintTarget string `json:"lint_target"` 145 Stdstreams string `json:"stdstreams"` 146 Wd string `json:"wd"` 147 } 148 149 meta := &metadata{ 150 Args: clangTidyCmd.Args, 151 CrashOutput: nil, 152 Executable: clangTidyCmd.Path, 153 ExitCode: exitCode, 154 LintTarget: cSrcFile, 155 Stdstreams: stdstreams.String(), 156 Wd: env.getwd(), 157 } 158 159 // Sometimes, clang-tidy crashes. Unfortunately, these don't get funnelled through the 160 // standard clang crash machinery. :(. Try to work with our own. 161 if crashArtifactsDir != "" && strings.Contains(meta.Stdstreams, clangTidyCrashSubstring) { 162 tidyCrashArtifacts := path.Join(crashArtifactsDir, "clang-tidy") 163 if err := os.MkdirAll(tidyCrashArtifacts, 0777); err != nil { 164 return fmt.Errorf("creating crash artifacts directory at %q: %v", tidyCrashArtifacts, err) 165 } 166 167 f, err := ioutil.TempFile(tidyCrashArtifacts, "crash-") 168 if err != nil { 169 return fmt.Errorf("making tempfile for crash output: %v", err) 170 } 171 f.Close() 172 173 reproCmd := &command{} 174 *reproCmd = *clangCmd 175 reproCmd.Args = append(reproCmd.Args, "-E", "-o", f.Name()) 176 177 reproOut := &strings.Builder{} 178 _, err = wrapSubprocessErrorWithSourceLoc(reproCmd, env.run(reproCmd, nil, reproOut, reproOut)) 179 if err != nil { 180 return fmt.Errorf("attempting to produce a clang-tidy crash reproducer: %v", err) 181 } 182 meta.CrashOutput = &crashOutput{ 183 CrashReproducerPath: f.Name(), 184 Stdstreams: reproOut.String(), 185 } 186 } 187 188 f, err = os.Create(fixesMetadataPath) 189 if err != nil { 190 return fmt.Errorf("creating fixes metadata: %v", err) 191 } 192 193 if err := json.NewEncoder(f).Encode(meta); err != nil { 194 return fmt.Errorf("writing fixes metadata: %v", err) 195 } 196 197 if err := f.Close(); err != nil { 198 return fmt.Errorf("finalizing fixes metadata: %v", err) 199 } 200 return nil 201} 202 203func runClangTidy(env env, clangCmd *command, cSrcFile string, extraTidyFlags []string) error { 204 extraTidyFlags = append(extraTidyFlags, 205 "-checks="+strings.Join([]string{ 206 "*", 207 "-bugprone-narrowing-conversions", 208 "-cppcoreguidelines-*", 209 "-fuchsia-*", 210 "-google-readability*", 211 "-google-runtime-references", 212 "-hicpp-*", 213 "-llvm-*", 214 "-misc-non-private-member-variables-in-classes", 215 "-misc-unused-parameters", 216 "-modernize-*", 217 "-readability-*", 218 }, ",")) 219 clangTidyCmd, err := calcClangTidyInvocation(env, clangCmd, cSrcFile, extraTidyFlags...) 220 if err != nil { 221 return fmt.Errorf("calculating clang-tidy invocation: %v", err) 222 } 223 224 // Note: We pass nil as stdin as we checked before that the compiler 225 // was invoked with a source file argument. 226 exitCode, err := wrapSubprocessErrorWithSourceLoc(clangTidyCmd, 227 env.run(clangTidyCmd, nil, env.stdout(), env.stderr())) 228 if err == nil && exitCode != 0 { 229 // Note: We continue on purpose when clang-tidy fails 230 // to maintain compatibility with the previous wrapper. 231 fmt.Fprint(env.stderr(), "clang-tidy failed") 232 } 233 return err 234} 235 236func hasAtLeastOneSuffix(s string, suffixes []string) bool { 237 for _, suffix := range suffixes { 238 if strings.HasSuffix(s, suffix) { 239 return true 240 } 241 } 242 return false 243} 244