Skip to content

Commit aa8bf70

Browse files
committed
feat: check ARG_MAX on Unix platforms
On Unix the limits can be gargantuan anyway so we're pretty unlikely to hit them, but might still exceed it. We consult ARG_MAX here to get an estimate.
1 parent 249cb84 commit aa8bf70

File tree

1 file changed

+56
-31
lines changed

1 file changed

+56
-31
lines changed

compiler/rustc_codegen_ssa/src/back/command.rs

+56-31
Original file line numberDiff line numberDiff line change
@@ -137,44 +137,69 @@ impl Command {
137137
/// Returns a `true` if we're pretty sure that this'll blow OS spawn limits,
138138
/// or `false` if we should attempt to spawn and see what the OS says.
139139
pub(crate) fn very_likely_to_exceed_some_spawn_limit(&self) -> bool {
140-
// We mostly only care about Windows in this method, on Unix the limits
141-
// can be gargantuan anyway so we're pretty unlikely to hit them
142-
if cfg!(unix) {
143-
return false;
144-
}
145-
146140
// Right now LLD doesn't support the `@` syntax of passing an argument
147141
// through files, so regardless of the platform we try to go to the OS
148142
// on this one.
149143
if let Program::Lld(..) = self.program {
150144
return false;
151145
}
152146

153-
// Ok so on Windows to spawn a process is 32,768 characters in its
154-
// command line [1]. Unfortunately we don't actually have access to that
155-
// as it's calculated just before spawning. Instead we perform a
156-
// poor-man's guess as to how long our command line will be. We're
157-
// assuming here that we don't have to escape every character...
158-
//
159-
// Turns out though that `cmd.exe` has even smaller limits, 8192
160-
// characters [2]. Linkers can often be batch scripts (for example
161-
// Emscripten, Gecko's current build system) which means that we're
162-
// running through batch scripts. These linkers often just forward
163-
// arguments elsewhere (and maybe tack on more), so if we blow 8192
164-
// bytes we'll typically cause them to blow as well.
165-
//
166-
// Basically as a result just perform an inflated estimate of what our
167-
// command line will look like and test if it's > 8192 (we actually
168-
// test against 6k to artificially inflate our estimate). If all else
169-
// fails we'll fall back to the normal unix logic of testing the OS
170-
// error code if we fail to spawn and automatically re-spawning the
171-
// linker with smaller arguments.
172-
//
173-
// [1]: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
174-
// [2]: https://devblogs.microsoft.com/oldnewthing/?p=41553
175-
176-
let estimated_command_line_len = self.args.iter().map(|a| a.len()).sum::<usize>();
177-
estimated_command_line_len > 1024 * 6
147+
if cfg!(windows) {
148+
// Ok so on Windows to spawn a process is 32,768 characters in its
149+
// command line [1]. Unfortunately we don't actually have access to that
150+
// as it's calculated just before spawning. Instead we perform a
151+
// poor-man's guess as to how long our command line will be. We're
152+
// assuming here that we don't have to escape every character...
153+
//
154+
// Turns out though that `cmd.exe` has even smaller limits, 8192
155+
// characters [2]. Linkers can often be batch scripts (for example
156+
// Emscripten, Gecko's current build system) which means that we're
157+
// running through batch scripts. These linkers often just forward
158+
// arguments elsewhere (and maybe tack on more), so if we blow 8192
159+
// bytes we'll typically cause them to blow as well.
160+
//
161+
// Basically as a result just perform an inflated estimate of what our
162+
// command line will look like and test if it's > 8192 (we actually
163+
// test against 6k to artificially inflate our estimate). If all else
164+
// fails we'll fall back to the normal unix logic of testing the OS
165+
// error code if we fail to spawn and automatically re-spawning the
166+
// linker with smaller arguments.
167+
//
168+
// [1]: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
169+
// [2]: https://devblogs.microsoft.com/oldnewthing/?p=41553
170+
let estimated_command_line_len = self.args.iter().map(|a| a.len()).sum::<usize>();
171+
estimated_command_line_len > 1024 * 6
172+
} else if cfg!(unix) {
173+
// On Unix the limits can be gargantuan anyway so we're pretty
174+
// unlikely to hit them, but might still exceed it.
175+
// We consult ARG_MAX here to get an estimate.
176+
let ptr_size = mem::size_of::<usize>();
177+
// arg + \0 + pointer
178+
let args_size = self.args.iter().fold(0, |acc, a| {
179+
let arg = a.as_encoded_bytes().len();
180+
let nul = 1;
181+
acc.saturating_add(arg).saturating_add(nul).saturating_add(ptr_size)
182+
});
183+
// key + `=` + value + \0 + pointer
184+
let envs_size = self.env.iter().fold(0, |acc, (k, v)| {
185+
let k = k.as_encoded_bytes().len();
186+
let eq = 1;
187+
let v = v.as_encoded_bytes().len();
188+
let nul = 1;
189+
acc.saturating_add(k)
190+
.saturating_add(eq)
191+
.saturating_add(v)
192+
.saturating_add(nul)
193+
.saturating_add(ptr_size)
194+
});
195+
let arg_max = match unsafe { libc::sysconf(libc::_SC_ARG_MAX) } {
196+
-1 => return false, // Go to OS anyway.
197+
max => max as usize,
198+
};
199+
args_size + envs_size > arg_max
200+
} else {
201+
false
202+
}
178203
}
179204
}
180205

0 commit comments

Comments
 (0)