aboutsummaryrefslogtreecommitdiff
path: root/compiler_wrapper/crash_builds.go
blob: 76a5412adfc8eeba56d3d847d44afd06eb4c1e22 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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()))
}