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