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