// Copyright 2019 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package main import ( "encoding/json" "fmt" "io/ioutil" "os" "path" "path/filepath" "strings" ) 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 "", clangTidyFlags, tidyModeNone } srcFileSuffixes := []string{ ".c", ".cc", ".cpp", ".C", ".cxx", ".c++", } cSrcFile = "" srcSuffix := "" lastArg := "" for _, arg := range builder.args { if lastArg != "-o" { for _, suffix := range srcFileSuffixes { if strings.HasSuffix(arg.value, suffix) { srcSuffix = suffix cSrcFile = arg.value break } } } lastArg = arg.value } 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 nil, err } clangTidyPath := filepath.Join(filepath.Dir(clangCmd.Path), "clang-tidy") 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 // was invoked with a source file argument. exitCode, err := wrapSubprocessErrorWithSourceLoc(clangTidyCmd, env.run(clangTidyCmd, nil, env.stdout(), env.stderr())) if err == nil && exitCode != 0 { // Note: We continue on purpose when clang-tidy fails // to maintain compatibility with the previous wrapper. fmt.Fprint(env.stderr(), "clang-tidy failed") } return err } func hasAtLeastOneSuffix(s string, suffixes []string) bool { for _, suffix := range suffixes { if strings.HasSuffix(s, suffix) { return true } } return false }