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