xref: /aosp_15_r20/external/toolchain-utils/compiler_wrapper/clang_tidy_flag.go (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
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