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