blob: 147fb36943e00dde3225d3e5e0aae4386cf985db [file] [log] [blame]
// 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()))
}