aboutsummaryrefslogtreecommitdiff
path: root/compiler_wrapper/crash_builds.go
diff options
context:
space:
mode:
Diffstat (limited to 'compiler_wrapper/crash_builds.go')
-rw-r--r--compiler_wrapper/crash_builds.go154
1 files changed, 154 insertions, 0 deletions
diff --git a/compiler_wrapper/crash_builds.go b/compiler_wrapper/crash_builds.go
new file mode 100644
index 00000000..76a5412a
--- /dev/null
+++ b/compiler_wrapper/crash_builds.go
@@ -0,0 +1,154 @@
+// Copyright 2022 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "regexp"
+)
+
+// ** HEY YOU, PERSON READING THIS! **
+//
+// Are you a dev who wants to make this work locally? Awesome! Please note that this **only** works
+// for Clang. If that's OK, here's a checklist for you:
+// [ ] Set `shouldUseCrashBuildsHeuristic = true` below.
+// [ ] If you want this heuristic to operate during `src_configure` (rare), also set
+// `allowAutoCrashInConfigure` to true.
+// [ ] Modify `shouldAutocrashPostExec` to return `true` when the compiler's output/flags match what
+// you want to crash on, and `false` otherwise.
+// [ ] Run `./install_compiler_wrapper.sh` to install the updated wrapper.
+// [ ] Run whatever command reproduces the error.
+//
+// If you need to make changes to your heuristic, repeat the above steps starting at
+// `./install_compiler_wrapper.sh` until things seem to do what you want.
+const (
+ // Set this to true to use autocrashing logic.
+ shouldUseCrashBuildsHeuristic = false
+ // Set this to true to allow `shouldAutocrashPostExec` to check+crash configure steps.
+ allowAutoCrashInConfigure = false
+)
+
+// shouldAutocrashPostExec returns true if we should automatically crash the compiler. This is
+// called after the compiler is run. If it returns true, we'll re-execute the compiler with the bit
+// of extra code necessary to crash it.
+func shouldAutocrashPostExec(env env, cfg *config, originalCmd *command, runInfo compilerExecInfo) bool {
+ // ** TODO, DEAR READER: ** Fill this in. Below are a few `if false {` blocks that should
+ // work for common use-cases. You're encouraged to change them to `if true {` if they suit
+ // your needs.
+
+ // Return true if `error: some error message` is contained in the run's stderr.
+ if false {
+ return bytes.Contains(runInfo.stderr, []byte("error: some error message"))
+ }
+
+ // Return true if `foo.c:${line_number}: error: some error message` appears in the run's
+ // stderr. Otherwise, return false.
+ if false {
+ r := regexp.MustCompile(`foo\.c:\d+: error: some error message`)
+ return r.Match(runInfo.stderr)
+ }
+
+ // Return true if there's a `-fjust-give-up` flag in the compiler's invocation.
+ if false {
+ for _, flag := range originalCmd.Args {
+ if flag == "-fjust-give-up" {
+ return true
+ }
+ }
+
+ return false
+ }
+
+ panic("Please fill in `shouldAutocrashPostExec` with meaningful logic.")
+}
+
+type compilerExecInfo struct {
+ exitCode int
+ stdout, stderr []byte
+}
+
+// ** Below here are implementation details. If all you want is autocrashing behavior, you don't
+// need to keep reading. **
+const (
+ autocrashProgramLine = "\n#pragma clang __debug parser_crash"
+)
+
+type buildWithAutocrashPredicates struct {
+ allowInConfigure bool
+ shouldAutocrash func(env, *config, *command, compilerExecInfo) bool
+}
+
+func buildWithAutocrash(env env, cfg *config, originalCmd *command) (exitCode int, err error) {
+ return buildWithAutocrashImpl(env, cfg, originalCmd, buildWithAutocrashPredicates{
+ allowInConfigure: allowAutoCrashInConfigure,
+ shouldAutocrash: shouldAutocrashPostExec,
+ })
+}
+
+func buildWithAutocrashImpl(env env, cfg *config, originalCmd *command, preds buildWithAutocrashPredicates) (exitCode int, err error) {
+ stdinBuffer := (*bytes.Buffer)(nil)
+ subprocStdin := io.Reader(nil)
+ invocationUsesStdinAsAFile := needStdinTee(originalCmd)
+ if invocationUsesStdinAsAFile {
+ stdinBuffer = &bytes.Buffer{}
+ if _, err := stdinBuffer.ReadFrom(env.stdin()); err != nil {
+ return 0, wrapErrorwithSourceLocf(err, "prebuffering stdin")
+ }
+ subprocStdin = stdinBuffer
+ } else {
+ subprocStdin = env.stdin()
+ }
+
+ stdoutBuffer := &bytes.Buffer{}
+ stderrBuffer := &bytes.Buffer{}
+ exitCode, err = wrapSubprocessErrorWithSourceLoc(originalCmd,
+ env.run(originalCmd, subprocStdin, stdoutBuffer, stderrBuffer))
+ if err != nil {
+ return 0, err
+ }
+
+ autocrashAllowed := preds.allowInConfigure || !isInConfigureStage(env)
+ crash := autocrashAllowed && preds.shouldAutocrash(env, cfg, originalCmd, compilerExecInfo{
+ exitCode: exitCode,
+ stdout: stdoutBuffer.Bytes(),
+ stderr: stderrBuffer.Bytes(),
+ })
+ if !crash {
+ stdoutBuffer.WriteTo(env.stdout())
+ stderrBuffer.WriteTo(env.stderr())
+ return exitCode, nil
+ }
+
+ fmt.Fprintln(env.stderr(), "** Autocrash requested; crashing the compiler...**")
+
+ // `stdinBuffer == nil` implies that `-` wasn't used as a flag. If `-` isn't used as a
+ // flag, clang will ignore stdin. We want to write our #pragma to stdin, since we can't
+ // reasonably modify the files we're currently compiling.
+ if stdinBuffer == nil {
+ newArgs := []string{}
+ // Clang can't handle `-o ${target}` when handed multiple input files. Since
+ // we expect to crash before emitting anything, remove `-o ${file}` entirely.
+ for i, e := 0, len(originalCmd.Args); i < e; i++ {
+ a := originalCmd.Args[i]
+ if a == "-o" {
+ // Skip the -o here, then skip the following arg in the loop header.
+ i++
+ } else {
+ newArgs = append(newArgs, a)
+ }
+ }
+ // And now add args that instruct clang to read from stdin. In this case, we also
+ // need to tell Clang what language the file is written in; C is as good as anything
+ // for this.
+ originalCmd.Args = append(newArgs, "-x", "c", "-")
+ stdinBuffer = &bytes.Buffer{}
+ }
+
+ stdinBuffer.WriteString(autocrashProgramLine)
+ return wrapSubprocessErrorWithSourceLoc(originalCmd,
+ env.run(originalCmd, stdinBuffer, env.stdout(), env.stderr()))
+}