1*760c253cSXin Li// Copyright 2022 The ChromiumOS Authors 2*760c253cSXin Li// Use of this source code is governed by a BSD-style license that can be 3*760c253cSXin Li// found in the LICENSE file. 4*760c253cSXin Li 5*760c253cSXin Lipackage main 6*760c253cSXin Li 7*760c253cSXin Liimport ( 8*760c253cSXin Li "bytes" 9*760c253cSXin Li "fmt" 10*760c253cSXin Li "io" 11*760c253cSXin Li "regexp" 12*760c253cSXin Li) 13*760c253cSXin Li 14*760c253cSXin Li// ** HEY YOU, PERSON READING THIS! ** 15*760c253cSXin Li// 16*760c253cSXin Li// Are you a dev who wants to make this work locally? Awesome! Please note that this **only** works 17*760c253cSXin Li// for Clang. If that's OK, here's a checklist for you: 18*760c253cSXin Li// [ ] Set `shouldUseCrashBuildsHeuristic = true` below. 19*760c253cSXin Li// [ ] If you want this heuristic to operate during `src_configure` (rare), also set 20*760c253cSXin Li// `allowAutoCrashInConfigure` to true. 21*760c253cSXin Li// [ ] Modify `shouldAutocrashPostExec` to return `true` when the compiler's output/flags match what 22*760c253cSXin Li// you want to crash on, and `false` otherwise. 23*760c253cSXin Li// [ ] Run `./install_compiler_wrapper.sh` to install the updated wrapper. 24*760c253cSXin Li// [ ] Run whatever command reproduces the error. 25*760c253cSXin Li// 26*760c253cSXin Li// If you need to make changes to your heuristic, repeat the above steps starting at 27*760c253cSXin Li// `./install_compiler_wrapper.sh` until things seem to do what you want. 28*760c253cSXin Liconst ( 29*760c253cSXin Li // Set this to true to use autocrashing logic. 30*760c253cSXin Li shouldUseCrashBuildsHeuristic = false 31*760c253cSXin Li // Set this to true to allow `shouldAutocrashPostExec` to check+crash configure steps. 32*760c253cSXin Li allowAutoCrashInConfigure = false 33*760c253cSXin Li) 34*760c253cSXin Li 35*760c253cSXin Li// shouldAutocrashPostExec returns true if we should automatically crash the compiler. This is 36*760c253cSXin Li// called after the compiler is run. If it returns true, we'll re-execute the compiler with the bit 37*760c253cSXin Li// of extra code necessary to crash it. 38*760c253cSXin Lifunc shouldAutocrashPostExec(env env, cfg *config, originalCmd *command, runInfo compilerExecInfo) bool { 39*760c253cSXin Li // ** TODO, DEAR READER: ** Fill this in. Below are a few `if false {` blocks that should 40*760c253cSXin Li // work for common use-cases. You're encouraged to change them to `if true {` if they suit 41*760c253cSXin Li // your needs. 42*760c253cSXin Li 43*760c253cSXin Li // Return true if `error: some error message` is contained in the run's stderr. 44*760c253cSXin Li if false { 45*760c253cSXin Li return bytes.Contains(runInfo.stderr, []byte("error: some error message")) 46*760c253cSXin Li } 47*760c253cSXin Li 48*760c253cSXin Li // Return true if `foo.c:${line_number}: error: some error message` appears in the run's 49*760c253cSXin Li // stderr. Otherwise, return false. 50*760c253cSXin Li if false { 51*760c253cSXin Li r := regexp.MustCompile(`foo\.c:\d+: error: some error message`) 52*760c253cSXin Li return r.Match(runInfo.stderr) 53*760c253cSXin Li } 54*760c253cSXin Li 55*760c253cSXin Li // Return true if there's a `-fjust-give-up` flag in the compiler's invocation. 56*760c253cSXin Li if false { 57*760c253cSXin Li for _, flag := range originalCmd.Args { 58*760c253cSXin Li if flag == "-fjust-give-up" { 59*760c253cSXin Li return true 60*760c253cSXin Li } 61*760c253cSXin Li } 62*760c253cSXin Li 63*760c253cSXin Li return false 64*760c253cSXin Li } 65*760c253cSXin Li 66*760c253cSXin Li panic("Please fill in `shouldAutocrashPostExec` with meaningful logic.") 67*760c253cSXin Li} 68*760c253cSXin Li 69*760c253cSXin Litype compilerExecInfo struct { 70*760c253cSXin Li exitCode int 71*760c253cSXin Li stdout, stderr []byte 72*760c253cSXin Li} 73*760c253cSXin Li 74*760c253cSXin Li// ** Below here are implementation details. If all you want is autocrashing behavior, you don't 75*760c253cSXin Li// need to keep reading. ** 76*760c253cSXin Liconst ( 77*760c253cSXin Li autocrashProgramLine = "\n#pragma clang __debug parser_crash" 78*760c253cSXin Li) 79*760c253cSXin Li 80*760c253cSXin Litype buildWithAutocrashPredicates struct { 81*760c253cSXin Li allowInConfigure bool 82*760c253cSXin Li shouldAutocrash func(env, *config, *command, compilerExecInfo) bool 83*760c253cSXin Li} 84*760c253cSXin Li 85*760c253cSXin Lifunc buildWithAutocrash(env env, cfg *config, originalCmd *command) (exitCode int, err error) { 86*760c253cSXin Li return buildWithAutocrashImpl(env, cfg, originalCmd, buildWithAutocrashPredicates{ 87*760c253cSXin Li allowInConfigure: allowAutoCrashInConfigure, 88*760c253cSXin Li shouldAutocrash: shouldAutocrashPostExec, 89*760c253cSXin Li }) 90*760c253cSXin Li} 91*760c253cSXin Li 92*760c253cSXin Lifunc buildWithAutocrashImpl(env env, cfg *config, originalCmd *command, preds buildWithAutocrashPredicates) (exitCode int, err error) { 93*760c253cSXin Li stdinBuffer := (*bytes.Buffer)(nil) 94*760c253cSXin Li subprocStdin := io.Reader(nil) 95*760c253cSXin Li invocationUsesStdinAsAFile := needStdinTee(originalCmd) 96*760c253cSXin Li if invocationUsesStdinAsAFile { 97*760c253cSXin Li stdinBuffer = &bytes.Buffer{} 98*760c253cSXin Li if _, err := stdinBuffer.ReadFrom(env.stdin()); err != nil { 99*760c253cSXin Li return 0, wrapErrorwithSourceLocf(err, "prebuffering stdin") 100*760c253cSXin Li } 101*760c253cSXin Li subprocStdin = stdinBuffer 102*760c253cSXin Li } else { 103*760c253cSXin Li subprocStdin = env.stdin() 104*760c253cSXin Li } 105*760c253cSXin Li 106*760c253cSXin Li stdoutBuffer := &bytes.Buffer{} 107*760c253cSXin Li stderrBuffer := &bytes.Buffer{} 108*760c253cSXin Li exitCode, err = wrapSubprocessErrorWithSourceLoc(originalCmd, 109*760c253cSXin Li env.run(originalCmd, subprocStdin, stdoutBuffer, stderrBuffer)) 110*760c253cSXin Li if err != nil { 111*760c253cSXin Li return 0, err 112*760c253cSXin Li } 113*760c253cSXin Li 114*760c253cSXin Li autocrashAllowed := preds.allowInConfigure || !isInConfigureStage(env) 115*760c253cSXin Li crash := autocrashAllowed && preds.shouldAutocrash(env, cfg, originalCmd, compilerExecInfo{ 116*760c253cSXin Li exitCode: exitCode, 117*760c253cSXin Li stdout: stdoutBuffer.Bytes(), 118*760c253cSXin Li stderr: stderrBuffer.Bytes(), 119*760c253cSXin Li }) 120*760c253cSXin Li if !crash { 121*760c253cSXin Li stdoutBuffer.WriteTo(env.stdout()) 122*760c253cSXin Li stderrBuffer.WriteTo(env.stderr()) 123*760c253cSXin Li return exitCode, nil 124*760c253cSXin Li } 125*760c253cSXin Li 126*760c253cSXin Li fmt.Fprintln(env.stderr(), "** Autocrash requested; crashing the compiler...**") 127*760c253cSXin Li 128*760c253cSXin Li // `stdinBuffer == nil` implies that `-` wasn't used as a flag. If `-` isn't used as a 129*760c253cSXin Li // flag, clang will ignore stdin. We want to write our #pragma to stdin, since we can't 130*760c253cSXin Li // reasonably modify the files we're currently compiling. 131*760c253cSXin Li if stdinBuffer == nil { 132*760c253cSXin Li newArgs := []string{} 133*760c253cSXin Li // Clang can't handle `-o ${target}` when handed multiple input files. Since 134*760c253cSXin Li // we expect to crash before emitting anything, remove `-o ${file}` entirely. 135*760c253cSXin Li for i, e := 0, len(originalCmd.Args); i < e; i++ { 136*760c253cSXin Li a := originalCmd.Args[i] 137*760c253cSXin Li if a == "-o" { 138*760c253cSXin Li // Skip the -o here, then skip the following arg in the loop header. 139*760c253cSXin Li i++ 140*760c253cSXin Li } else { 141*760c253cSXin Li newArgs = append(newArgs, a) 142*760c253cSXin Li } 143*760c253cSXin Li } 144*760c253cSXin Li // And now add args that instruct clang to read from stdin. In this case, we also 145*760c253cSXin Li // need to tell Clang what language the file is written in; C is as good as anything 146*760c253cSXin Li // for this. 147*760c253cSXin Li originalCmd.Args = append(newArgs, "-x", "c", "-") 148*760c253cSXin Li stdinBuffer = &bytes.Buffer{} 149*760c253cSXin Li } 150*760c253cSXin Li 151*760c253cSXin Li stdinBuffer.WriteString(autocrashProgramLine) 152*760c253cSXin Li return wrapSubprocessErrorWithSourceLoc(originalCmd, 153*760c253cSXin Li env.run(originalCmd, stdinBuffer, env.stdout(), env.stderr())) 154*760c253cSXin Li} 155