| // 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())) |
| } |