• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, fixesDir string, extraTidyFlags []string, crashArtifactsDir string) error {
102	if err := os.MkdirAll(fixesDir, 0777); err != nil {
103		return fmt.Errorf("creating fixes directory at %q: %v", fixesDir, err)
104	}
105
106	f, err := ioutil.TempFile(fixesDir, "lints-")
107	if err != nil {
108		return fmt.Errorf("making tempfile for tidy: %v", err)
109	}
110	f.Close()
111
112	// `f` is an 'anchor'; it ensures we won't create a similarly-named file in the future.
113	// Hence, we can't delete it.
114	fixesFilePath := f.Name() + ".yaml"
115	fixesMetadataPath := f.Name() + ".json"
116
117	extraTidyFlags = append(extraTidyFlags, "--export-fixes="+fixesFilePath, "--header-filter=.*")
118	clangTidyCmd, err := calcClangTidyInvocation(env, clangCmd, cSrcFile, extraTidyFlags...)
119	if err != nil {
120		return fmt.Errorf("calculating tidy invocation: %v", err)
121	}
122
123	stdstreams := &strings.Builder{}
124	// Note: We pass nil as stdin as we checked before that the compiler
125	// was invoked with a source file argument.
126	exitCode, err := wrapSubprocessErrorWithSourceLoc(clangTidyCmd,
127		env.run(clangTidyCmd, nil, stdstreams, stdstreams))
128	if err != nil {
129		return err
130	}
131
132	type crashOutput struct {
133		CrashReproducerPath string `json:"crash_reproducer_path"`
134		Stdstreams          string `json:"stdstreams"`
135	}
136
137	type metadata struct {
138		Args        []string     `json:"args"`
139		CrashOutput *crashOutput `json:"crash_output"`
140		Executable  string       `json:"executable"`
141		ExitCode    int          `json:"exit_code"`
142		LintTarget  string       `json:"lint_target"`
143		Stdstreams  string       `json:"stdstreams"`
144		Wd          string       `json:"wd"`
145	}
146
147	meta := &metadata{
148		Args:        clangTidyCmd.Args,
149		CrashOutput: nil,
150		Executable:  clangTidyCmd.Path,
151		ExitCode:    exitCode,
152		LintTarget:  cSrcFile,
153		Stdstreams:  stdstreams.String(),
154		Wd:          env.getwd(),
155	}
156
157	// Sometimes, clang-tidy crashes. Unfortunately, these don't get funnelled through the
158	// standard clang crash machinery. :(. Try to work with our own.
159	if crashArtifactsDir != "" && strings.Contains(meta.Stdstreams, clangTidyCrashSubstring) {
160		tidyCrashArtifacts := path.Join(crashArtifactsDir, "clang-tidy")
161		if err := os.MkdirAll(tidyCrashArtifacts, 0777); err != nil {
162			return fmt.Errorf("creating crash artifacts directory at %q: %v", tidyCrashArtifacts, err)
163		}
164
165		f, err := ioutil.TempFile(tidyCrashArtifacts, "crash-")
166		if err != nil {
167			return fmt.Errorf("making tempfile for crash output: %v", err)
168		}
169		f.Close()
170
171		reproCmd := &command{}
172		*reproCmd = *clangCmd
173		reproCmd.Args = append(reproCmd.Args, "-E", "-o", f.Name())
174
175		reproOut := &strings.Builder{}
176		_, err = wrapSubprocessErrorWithSourceLoc(reproCmd, env.run(reproCmd, nil, reproOut, reproOut))
177		if err != nil {
178			return fmt.Errorf("attempting to produce a clang-tidy crash reproducer: %v", err)
179		}
180		meta.CrashOutput = &crashOutput{
181			CrashReproducerPath: f.Name(),
182			Stdstreams:          reproOut.String(),
183		}
184	}
185
186	f, err = os.Create(fixesMetadataPath)
187	if err != nil {
188		return fmt.Errorf("creating fixes metadata: %v", err)
189	}
190
191	if err := json.NewEncoder(f).Encode(meta); err != nil {
192		return fmt.Errorf("writing fixes metadata: %v", err)
193	}
194
195	if err := f.Close(); err != nil {
196		return fmt.Errorf("finalizing fixes metadata: %v", err)
197	}
198	return nil
199}
200
201func runClangTidy(env env, clangCmd *command, cSrcFile string, extraTidyFlags []string) error {
202	extraTidyFlags = append(extraTidyFlags,
203		"-checks="+strings.Join([]string{
204			"*",
205			"-bugprone-narrowing-conversions",
206			"-cppcoreguidelines-*",
207			"-fuchsia-*",
208			"-google-readability*",
209			"-google-runtime-references",
210			"-hicpp-*",
211			"-llvm-*",
212			"-misc-non-private-member-variables-in-classes",
213			"-misc-unused-parameters",
214			"-modernize-*",
215			"-readability-*",
216		}, ","))
217	clangTidyCmd, err := calcClangTidyInvocation(env, clangCmd, cSrcFile, extraTidyFlags...)
218	if err != nil {
219		return fmt.Errorf("calculating clang-tidy invocation: %v", err)
220	}
221
222	// Note: We pass nil as stdin as we checked before that the compiler
223	// was invoked with a source file argument.
224	exitCode, err := wrapSubprocessErrorWithSourceLoc(clangTidyCmd,
225		env.run(clangTidyCmd, nil, env.stdout(), env.stderr()))
226	if err == nil && exitCode != 0 {
227		// Note: We continue on purpose when clang-tidy fails
228		// to maintain compatibility with the previous wrapper.
229		fmt.Fprint(env.stderr(), "clang-tidy failed")
230	}
231	return err
232}
233
234func hasAtLeastOneSuffix(s string, suffixes []string) bool {
235	for _, suffix := range suffixes {
236		if strings.HasSuffix(s, suffix) {
237			return true
238		}
239	}
240	return false
241}
242