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