1// Copyright 2019 The ChromiumOS Authors 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 extraTidyFlags = append(extraTidyFlags, "--export-fixes="+fixesFilePath, "--header-filter=.*") 118 clangTidyCmd, err := calcClangTidyInvocation(env, clangCmd, cSrcFile, extraTidyFlags...) 119 if err != nil { 120 return fmt.Errorf("calculating tidy invocation: %v", err) 121 } 122 123 stdstreams := &strings.Builder{} 124 // Note: We pass nil as stdin as we checked before that the compiler 125 // was invoked with a source file argument. 126 exitCode, err := wrapSubprocessErrorWithSourceLoc(clangTidyCmd, 127 env.run(clangTidyCmd, nil, stdstreams, stdstreams)) 128 if err != nil { 129 return err 130 } 131 132 type crashOutput struct { 133 CrashReproducerPath string `json:"crash_reproducer_path"` 134 Stdstreams string `json:"stdstreams"` 135 } 136 137 type metadata struct { 138 Args []string `json:"args"` 139 CrashOutput *crashOutput `json:"crash_output"` 140 Executable string `json:"executable"` 141 ExitCode int `json:"exit_code"` 142 LintTarget string `json:"lint_target"` 143 Stdstreams string `json:"stdstreams"` 144 Wd string `json:"wd"` 145 } 146 147 meta := &metadata{ 148 Args: clangTidyCmd.Args, 149 CrashOutput: nil, 150 Executable: clangTidyCmd.Path, 151 ExitCode: exitCode, 152 LintTarget: cSrcFile, 153 Stdstreams: stdstreams.String(), 154 Wd: env.getwd(), 155 } 156 157 // Sometimes, clang-tidy crashes. Unfortunately, these don't get funnelled through the 158 // standard clang crash machinery. :(. Try to work with our own. 159 if crashArtifactsDir != "" && strings.Contains(meta.Stdstreams, clangTidyCrashSubstring) { 160 tidyCrashArtifacts := path.Join(crashArtifactsDir, "clang-tidy") 161 if err := os.MkdirAll(tidyCrashArtifacts, 0777); err != nil { 162 return fmt.Errorf("creating crash artifacts directory at %q: %v", tidyCrashArtifacts, err) 163 } 164 165 f, err := ioutil.TempFile(tidyCrashArtifacts, "crash-") 166 if err != nil { 167 return fmt.Errorf("making tempfile for crash output: %v", err) 168 } 169 f.Close() 170 171 reproCmd := &command{} 172 *reproCmd = *clangCmd 173 reproCmd.Args = append(reproCmd.Args, "-E", "-o", f.Name()) 174 175 reproOut := &strings.Builder{} 176 _, err = wrapSubprocessErrorWithSourceLoc(reproCmd, env.run(reproCmd, nil, reproOut, reproOut)) 177 if err != nil { 178 return fmt.Errorf("attempting to produce a clang-tidy crash reproducer: %v", err) 179 } 180 meta.CrashOutput = &crashOutput{ 181 CrashReproducerPath: f.Name(), 182 Stdstreams: reproOut.String(), 183 } 184 } 185 186 f, err = os.Create(fixesMetadataPath) 187 if err != nil { 188 return fmt.Errorf("creating fixes metadata: %v", err) 189 } 190 191 if err := json.NewEncoder(f).Encode(meta); err != nil { 192 return fmt.Errorf("writing fixes metadata: %v", err) 193 } 194 195 if err := f.Close(); err != nil { 196 return fmt.Errorf("finalizing fixes metadata: %v", err) 197 } 198 return nil 199} 200 201func runClangTidy(env env, clangCmd *command, cSrcFile string, extraTidyFlags []string) error { 202 extraTidyFlags = append(extraTidyFlags, 203 "-checks="+strings.Join([]string{ 204 "*", 205 "-bugprone-narrowing-conversions", 206 "-cppcoreguidelines-*", 207 "-fuchsia-*", 208 "-google-readability*", 209 "-google-runtime-references", 210 "-hicpp-*", 211 "-llvm-*", 212 "-misc-non-private-member-variables-in-classes", 213 "-misc-unused-parameters", 214 "-modernize-*", 215 "-readability-*", 216 }, ",")) 217 clangTidyCmd, err := calcClangTidyInvocation(env, clangCmd, cSrcFile, extraTidyFlags...) 218 if err != nil { 219 return fmt.Errorf("calculating clang-tidy invocation: %v", err) 220 } 221 222 // Note: We pass nil as stdin as we checked before that the compiler 223 // was invoked with a source file argument. 224 exitCode, err := wrapSubprocessErrorWithSourceLoc(clangTidyCmd, 225 env.run(clangTidyCmd, nil, env.stdout(), env.stderr())) 226 if err == nil && exitCode != 0 { 227 // Note: We continue on purpose when clang-tidy fails 228 // to maintain compatibility with the previous wrapper. 229 fmt.Fprint(env.stderr(), "clang-tidy failed") 230 } 231 return err 232} 233 234func hasAtLeastOneSuffix(s string, suffixes []string) bool { 235 for _, suffix := range suffixes { 236 if strings.HasSuffix(s, suffix) { 237 return true 238 } 239 } 240 return false 241} 242