diff options
Diffstat (limited to 'compiler_wrapper/clang_tidy_flag.go')
-rw-r--r-- | compiler_wrapper/clang_tidy_flag.go | 225 |
1 files changed, 184 insertions, 41 deletions
diff --git a/compiler_wrapper/clang_tidy_flag.go b/compiler_wrapper/clang_tidy_flag.go index 40a5bdbe..01387fd6 100644 --- a/compiler_wrapper/clang_tidy_flag.go +++ b/compiler_wrapper/clang_tidy_flag.go @@ -5,15 +5,39 @@ package main import ( + "encoding/json" "fmt" + "io/ioutil" + "os" + "path" "path/filepath" "strings" ) -func processClangTidyFlags(builder *commandBuilder) (cSrcFile string, useClangTidy bool) { +type useTidyMode int + +const clangTidyCrashSubstring = "PLEASE submit a bug report" + +const ( + tidyModeNone useTidyMode = iota + tidyModeAll + tidyModeTricium +) + +func processClangTidyFlags(builder *commandBuilder) (cSrcFile string, clangTidyFlags []string, mode useTidyMode) { + builder.transformArgs(func(arg builderArg) string { + const prefix = "-clang-tidy-flag=" + if !strings.HasPrefix(arg.value, prefix) { + return arg.value + } + + clangTidyFlags = append(clangTidyFlags, arg.value[len(prefix):]) + return "" + }) + withTidy, _ := builder.env.getenv("WITH_TIDY") if withTidy == "" { - return "", false + return "", clangTidyFlags, tidyModeNone } srcFileSuffixes := []string{ ".c", @@ -24,58 +48,177 @@ func processClangTidyFlags(builder *commandBuilder) (cSrcFile string, useClangTi ".c++", } cSrcFile = "" + srcSuffix := "" lastArg := "" for _, arg := range builder.args { - if hasAtLeastOneSuffix(arg.value, srcFileSuffixes) && lastArg != "-o" { - cSrcFile = arg.value + if lastArg != "-o" { + for _, suffix := range srcFileSuffixes { + if strings.HasSuffix(arg.value, suffix) { + srcSuffix = suffix + cSrcFile = arg.value + break + } + } } lastArg = arg.value } - useClangTidy = cSrcFile != "" - return cSrcFile, useClangTidy -} -func runClangTidy(env env, clangCmd *command, cSrcFile string) error { - defaultTidyChecks := strings.Join([]string{ - "*", - "google*", - "-bugprone-narrowing-conversions", - "-cppcoreguidelines-*", - "-fuchsia-*", - "-google-build-using-namespace", - "-google-default-arguments", - "-google-explicit-constructor", - "-google-readability*", - "-google-runtime-int", - "-google-runtime-references", - "-hicpp-avoid-c-arrays", - "-hicpp-braces-around-statements", - "-hicpp-no-array-decay", - "-hicpp-signed-bitwise", - "-hicpp-uppercase-literal-suffix", - "-hicpp-use-auto", - "-llvm-namespace-comment", - "-misc-non-private-member-variables-in-classes", - "-misc-unused-parameters", - "-modernize-*", - "-readability-*", - }, ",") + if cSrcFile == "" { + return "", clangTidyFlags, tidyModeNone + } + if withTidy == "tricium" { + // Files generated from protobufs can result in _many_ clang-tidy complaints, and aren't + // worth linting in general. Don't. + if strings.HasSuffix(cSrcFile, ".pb"+srcSuffix) { + mode = tidyModeNone + } else { + mode = tidyModeTricium + } + } else { + mode = tidyModeAll + } + return cSrcFile, clangTidyFlags, mode +} + +func calcClangTidyInvocation(env env, clangCmd *command, cSrcFile string, tidyFlags ...string) (*command, error) { resourceDir, err := getClangResourceDir(env, clangCmd.Path) if err != nil { - return err + return nil, err } clangTidyPath := filepath.Join(filepath.Dir(clangCmd.Path), "clang-tidy") - clangTidyCmd := &command{ - Path: clangTidyPath, - Args: append([]string{ - "-checks=" + defaultTidyChecks, - cSrcFile, - "--", - "-resource-dir=" + resourceDir, - }, clangCmd.Args...), + args := append([]string{}, tidyFlags...) + args = append(args, cSrcFile, "--", "-resource-dir="+resourceDir) + args = append(args, clangCmd.Args...) + return &command{ + Path: clangTidyPath, + Args: args, EnvUpdates: clangCmd.EnvUpdates, + }, nil +} + +func runClangTidyForTricium(env env, clangCmd *command, cSrcFile, fixesDir string, extraTidyFlags []string, crashArtifactsDir string) error { + if err := os.MkdirAll(fixesDir, 0777); err != nil { + return fmt.Errorf("creating fixes directory at %q: %v", fixesDir, err) + } + + f, err := ioutil.TempFile(fixesDir, "lints-") + if err != nil { + return fmt.Errorf("making tempfile for tidy: %v", err) + } + f.Close() + + // `f` is an 'anchor'; it ensures we won't create a similarly-named file in the future. + // Hence, we can't delete it. + fixesFilePath := f.Name() + ".yaml" + fixesMetadataPath := f.Name() + ".json" + + // FIXME(gbiv): Remove `-checks=*` when testing is complete; we should defer to .clang-tidy + // files, which are both more expressive and more approachable than `-checks=*`. + extraTidyFlags = append(extraTidyFlags, "-checks=*", "--export-fixes="+fixesFilePath) + clangTidyCmd, err := calcClangTidyInvocation(env, clangCmd, cSrcFile, extraTidyFlags...) + if err != nil { + return fmt.Errorf("calculating tidy invocation: %v", err) + } + + stdstreams := &strings.Builder{} + // Note: We pass nil as stdin as we checked before that the compiler + // was invoked with a source file argument. + exitCode, err := wrapSubprocessErrorWithSourceLoc(clangTidyCmd, + env.run(clangTidyCmd, nil, stdstreams, stdstreams)) + if err != nil { + return err + } + + type crashOutput struct { + CrashReproducerPath string `json:"crash_reproducer_path"` + Stdstreams string `json:"stdstreams"` + } + + type metadata struct { + Args []string `json:"args"` + CrashOutput *crashOutput `json:"crash_output"` + Executable string `json:"executable"` + ExitCode int `json:"exit_code"` + LintTarget string `json:"lint_target"` + Stdstreams string `json:"stdstreams"` + Wd string `json:"wd"` + } + + meta := &metadata{ + Args: clangTidyCmd.Args, + CrashOutput: nil, + Executable: clangTidyCmd.Path, + ExitCode: exitCode, + LintTarget: cSrcFile, + Stdstreams: stdstreams.String(), + Wd: env.getwd(), + } + + // Sometimes, clang-tidy crashes. Unfortunately, these don't get funnelled through the + // standard clang crash machinery. :(. Try to work with our own. + if crashArtifactsDir != "" && strings.Contains(meta.Stdstreams, clangTidyCrashSubstring) { + tidyCrashArtifacts := path.Join(crashArtifactsDir, "clang-tidy") + if err := os.MkdirAll(tidyCrashArtifacts, 0777); err != nil { + return fmt.Errorf("creating crash artifacts directory at %q: %v", tidyCrashArtifacts, err) + } + + f, err := ioutil.TempFile(tidyCrashArtifacts, "crash-") + if err != nil { + return fmt.Errorf("making tempfile for crash output: %v", err) + } + f.Close() + + reproCmd := &command{} + *reproCmd = *clangCmd + reproCmd.Args = append(reproCmd.Args, "-E", "-o", f.Name()) + + reproOut := &strings.Builder{} + _, err = wrapSubprocessErrorWithSourceLoc(reproCmd, env.run(reproCmd, nil, reproOut, reproOut)) + if err != nil { + return fmt.Errorf("attempting to produce a clang-tidy crash reproducer: %v", err) + } + meta.CrashOutput = &crashOutput{ + CrashReproducerPath: f.Name(), + Stdstreams: reproOut.String(), + } + } + + f, err = os.Create(fixesMetadataPath) + if err != nil { + return fmt.Errorf("creating fixes metadata: %v", err) + } + + if err := json.NewEncoder(f).Encode(meta); err != nil { + return fmt.Errorf("writing fixes metadata: %v", err) + } + + if err := f.Close(); err != nil { + return fmt.Errorf("finalizing fixes metadata: %v", err) + } + return nil +} + +func runClangTidy(env env, clangCmd *command, cSrcFile string, extraTidyFlags []string) error { + extraTidyFlags = append(extraTidyFlags, + "-checks="+strings.Join([]string{ + "*", + "-bugprone-narrowing-conversions", + "-cppcoreguidelines-*", + "-fuchsia-*", + "-google-readability*", + "-google-runtime-references", + "-hicpp-*", + "-llvm-*", + "-misc-non-private-member-variables-in-classes", + "-misc-unused-parameters", + "-modernize-*", + "-readability-*", + }, ",")) + clangTidyCmd, err := calcClangTidyInvocation(env, clangCmd, cSrcFile, extraTidyFlags...) + if err != nil { + return fmt.Errorf("calculating clang-tidy invocation: %v", err) } // Note: We pass nil as stdin as we checked before that the compiler |