diff options
Diffstat (limited to 'compiler_wrapper/crash_builds.go')
-rw-r--r-- | compiler_wrapper/crash_builds.go | 154 |
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())) +} |