xref: /aosp_15_r20/external/toolchain-utils/compiler_wrapper/crash_builds.go (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1*760c253cSXin Li// Copyright 2022 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	"bytes"
9*760c253cSXin Li	"fmt"
10*760c253cSXin Li	"io"
11*760c253cSXin Li	"regexp"
12*760c253cSXin Li)
13*760c253cSXin Li
14*760c253cSXin Li// ** HEY YOU, PERSON READING THIS! **
15*760c253cSXin Li//
16*760c253cSXin Li// Are you a dev who wants to make this work locally? Awesome! Please note that this **only** works
17*760c253cSXin Li// for Clang. If that's OK, here's a checklist for you:
18*760c253cSXin Li// [ ] Set `shouldUseCrashBuildsHeuristic = true` below.
19*760c253cSXin Li// [ ] If you want this heuristic to operate during `src_configure` (rare), also set
20*760c253cSXin Li// `allowAutoCrashInConfigure` to true.
21*760c253cSXin Li// [ ] Modify `shouldAutocrashPostExec` to return `true` when the compiler's output/flags match what
22*760c253cSXin Li// you want to crash on, and `false` otherwise.
23*760c253cSXin Li// [ ] Run `./install_compiler_wrapper.sh` to install the updated wrapper.
24*760c253cSXin Li// [ ] Run whatever command reproduces the error.
25*760c253cSXin Li//
26*760c253cSXin Li// If you need to make changes to your heuristic, repeat the above steps starting at
27*760c253cSXin Li// `./install_compiler_wrapper.sh` until things seem to do what you want.
28*760c253cSXin Liconst (
29*760c253cSXin Li	// Set this to true to use autocrashing logic.
30*760c253cSXin Li	shouldUseCrashBuildsHeuristic = false
31*760c253cSXin Li	// Set this to true to allow `shouldAutocrashPostExec` to check+crash configure steps.
32*760c253cSXin Li	allowAutoCrashInConfigure = false
33*760c253cSXin Li)
34*760c253cSXin Li
35*760c253cSXin Li// shouldAutocrashPostExec returns true if we should automatically crash the compiler. This is
36*760c253cSXin Li// called after the compiler is run. If it returns true, we'll re-execute the compiler with the bit
37*760c253cSXin Li// of extra code necessary to crash it.
38*760c253cSXin Lifunc shouldAutocrashPostExec(env env, cfg *config, originalCmd *command, runInfo compilerExecInfo) bool {
39*760c253cSXin Li	// ** TODO, DEAR READER: ** Fill this in. Below are a few `if false {` blocks that should
40*760c253cSXin Li	// work for common use-cases. You're encouraged to change them to `if true {` if they suit
41*760c253cSXin Li	// your needs.
42*760c253cSXin Li
43*760c253cSXin Li	// Return true if `error: some error message` is contained in the run's stderr.
44*760c253cSXin Li	if false {
45*760c253cSXin Li		return bytes.Contains(runInfo.stderr, []byte("error: some error message"))
46*760c253cSXin Li	}
47*760c253cSXin Li
48*760c253cSXin Li	// Return true if `foo.c:${line_number}: error: some error message` appears in the run's
49*760c253cSXin Li	// stderr. Otherwise, return false.
50*760c253cSXin Li	if false {
51*760c253cSXin Li		r := regexp.MustCompile(`foo\.c:\d+: error: some error message`)
52*760c253cSXin Li		return r.Match(runInfo.stderr)
53*760c253cSXin Li	}
54*760c253cSXin Li
55*760c253cSXin Li	// Return true if there's a `-fjust-give-up` flag in the compiler's invocation.
56*760c253cSXin Li	if false {
57*760c253cSXin Li		for _, flag := range originalCmd.Args {
58*760c253cSXin Li			if flag == "-fjust-give-up" {
59*760c253cSXin Li				return true
60*760c253cSXin Li			}
61*760c253cSXin Li		}
62*760c253cSXin Li
63*760c253cSXin Li		return false
64*760c253cSXin Li	}
65*760c253cSXin Li
66*760c253cSXin Li	panic("Please fill in `shouldAutocrashPostExec` with meaningful logic.")
67*760c253cSXin Li}
68*760c253cSXin Li
69*760c253cSXin Litype compilerExecInfo struct {
70*760c253cSXin Li	exitCode       int
71*760c253cSXin Li	stdout, stderr []byte
72*760c253cSXin Li}
73*760c253cSXin Li
74*760c253cSXin Li// ** Below here are implementation details. If all you want is autocrashing behavior, you don't
75*760c253cSXin Li// need to keep reading. **
76*760c253cSXin Liconst (
77*760c253cSXin Li	autocrashProgramLine = "\n#pragma clang __debug parser_crash"
78*760c253cSXin Li)
79*760c253cSXin Li
80*760c253cSXin Litype buildWithAutocrashPredicates struct {
81*760c253cSXin Li	allowInConfigure bool
82*760c253cSXin Li	shouldAutocrash  func(env, *config, *command, compilerExecInfo) bool
83*760c253cSXin Li}
84*760c253cSXin Li
85*760c253cSXin Lifunc buildWithAutocrash(env env, cfg *config, originalCmd *command) (exitCode int, err error) {
86*760c253cSXin Li	return buildWithAutocrashImpl(env, cfg, originalCmd, buildWithAutocrashPredicates{
87*760c253cSXin Li		allowInConfigure: allowAutoCrashInConfigure,
88*760c253cSXin Li		shouldAutocrash:  shouldAutocrashPostExec,
89*760c253cSXin Li	})
90*760c253cSXin Li}
91*760c253cSXin Li
92*760c253cSXin Lifunc buildWithAutocrashImpl(env env, cfg *config, originalCmd *command, preds buildWithAutocrashPredicates) (exitCode int, err error) {
93*760c253cSXin Li	stdinBuffer := (*bytes.Buffer)(nil)
94*760c253cSXin Li	subprocStdin := io.Reader(nil)
95*760c253cSXin Li	invocationUsesStdinAsAFile := needStdinTee(originalCmd)
96*760c253cSXin Li	if invocationUsesStdinAsAFile {
97*760c253cSXin Li		stdinBuffer = &bytes.Buffer{}
98*760c253cSXin Li		if _, err := stdinBuffer.ReadFrom(env.stdin()); err != nil {
99*760c253cSXin Li			return 0, wrapErrorwithSourceLocf(err, "prebuffering stdin")
100*760c253cSXin Li		}
101*760c253cSXin Li		subprocStdin = stdinBuffer
102*760c253cSXin Li	} else {
103*760c253cSXin Li		subprocStdin = env.stdin()
104*760c253cSXin Li	}
105*760c253cSXin Li
106*760c253cSXin Li	stdoutBuffer := &bytes.Buffer{}
107*760c253cSXin Li	stderrBuffer := &bytes.Buffer{}
108*760c253cSXin Li	exitCode, err = wrapSubprocessErrorWithSourceLoc(originalCmd,
109*760c253cSXin Li		env.run(originalCmd, subprocStdin, stdoutBuffer, stderrBuffer))
110*760c253cSXin Li	if err != nil {
111*760c253cSXin Li		return 0, err
112*760c253cSXin Li	}
113*760c253cSXin Li
114*760c253cSXin Li	autocrashAllowed := preds.allowInConfigure || !isInConfigureStage(env)
115*760c253cSXin Li	crash := autocrashAllowed && preds.shouldAutocrash(env, cfg, originalCmd, compilerExecInfo{
116*760c253cSXin Li		exitCode: exitCode,
117*760c253cSXin Li		stdout:   stdoutBuffer.Bytes(),
118*760c253cSXin Li		stderr:   stderrBuffer.Bytes(),
119*760c253cSXin Li	})
120*760c253cSXin Li	if !crash {
121*760c253cSXin Li		stdoutBuffer.WriteTo(env.stdout())
122*760c253cSXin Li		stderrBuffer.WriteTo(env.stderr())
123*760c253cSXin Li		return exitCode, nil
124*760c253cSXin Li	}
125*760c253cSXin Li
126*760c253cSXin Li	fmt.Fprintln(env.stderr(), "** Autocrash requested; crashing the compiler...**")
127*760c253cSXin Li
128*760c253cSXin Li	// `stdinBuffer == nil` implies that `-` wasn't used as a flag.  If `-` isn't used as a
129*760c253cSXin Li	// flag, clang will ignore stdin. We want to write our #pragma to stdin, since we can't
130*760c253cSXin Li	// reasonably modify the files we're currently compiling.
131*760c253cSXin Li	if stdinBuffer == nil {
132*760c253cSXin Li		newArgs := []string{}
133*760c253cSXin Li		// Clang can't handle `-o ${target}` when handed multiple input files. Since
134*760c253cSXin Li		// we expect to crash before emitting anything, remove `-o ${file}` entirely.
135*760c253cSXin Li		for i, e := 0, len(originalCmd.Args); i < e; i++ {
136*760c253cSXin Li			a := originalCmd.Args[i]
137*760c253cSXin Li			if a == "-o" {
138*760c253cSXin Li				// Skip the -o here, then skip the following arg in the loop header.
139*760c253cSXin Li				i++
140*760c253cSXin Li			} else {
141*760c253cSXin Li				newArgs = append(newArgs, a)
142*760c253cSXin Li			}
143*760c253cSXin Li		}
144*760c253cSXin Li		// And now add args that instruct clang to read from stdin. In this case, we also
145*760c253cSXin Li		// need to tell Clang what language the file is written in; C is as good as anything
146*760c253cSXin Li		// for this.
147*760c253cSXin Li		originalCmd.Args = append(newArgs, "-x", "c", "-")
148*760c253cSXin Li		stdinBuffer = &bytes.Buffer{}
149*760c253cSXin Li	}
150*760c253cSXin Li
151*760c253cSXin Li	stdinBuffer.WriteString(autocrashProgramLine)
152*760c253cSXin Li	return wrapSubprocessErrorWithSourceLoc(originalCmd,
153*760c253cSXin Li		env.run(originalCmd, stdinBuffer, env.stdout(), env.stderr()))
154*760c253cSXin Li}
155