mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 16:02:45 +00:00
* perf: count command-line escapes without regex match arrays * test(windows): pin command-line budget counting against the regex oracle Covers every BMP code unit, astral and lone-surrogate adjacency, trailing backslashes, %VAR% and carets, plus randomized quote-heavy command lines. * perf(windows): count command-line escapes by seeking, not scanning Counting every character regressed the shape this estimator actually guards: a multi-KB WSL script with almost no escapes went 25-38x slower on Windows. Seek escapes with indexOf so the cost tracks their count, and hand the rest to a plain scan once they are dense enough to pay for it. --------- Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
77 lines
2.8 KiB
TypeScript
77 lines
2.8 KiB
TypeScript
/**
|
|
* How long a command line may get before `CreateProcess` refuses it.
|
|
*
|
|
* Windows caps a command line at 32767 characters, and *everything* shares that
|
|
* one budget: the binary, `wsl.exe -d <distro> --exec`, the login-shell wrapper
|
|
* and the payload. So the number only means anything when it is measured on the
|
|
* FINISHED line. Two shipped defects came from measuring a part instead: a
|
|
* script-only threshold in the WSL runner, where a multi-KB login PATH pushed a
|
|
* legal-looking script over the real limit, and count-only chunking of bulk git
|
|
* pathspecs, where the login-shell wrapper tripled the line behind our back.
|
|
*
|
|
* The 2767-character margin absorbs what we do not model exactly (the distro
|
|
* name, libuv's requoting of the outer argv).
|
|
*/
|
|
export const MAX_COMMAND_LINE_CHARS = 30_000
|
|
|
|
/**
|
|
* What `CreateProcess` will count.
|
|
*
|
|
* libuv escapes every `"` and doubles a backslash run before a quote, so a
|
|
* quote-dense script costs more than its length. Charging one extra character
|
|
* per `"` or `\\` keeps the estimate on the safe side of the cap; an earlier
|
|
* version claimed to over-count and in fact under-counted, which put a
|
|
* quote-heavy ~26KB script on argv and over the real limit.
|
|
*/
|
|
export function commandLineLength(args: readonly string[]): number {
|
|
let total = 0
|
|
for (const arg of args) {
|
|
total += arg.length + 3 + countEscapes(arg)
|
|
}
|
|
return total
|
|
}
|
|
|
|
/** One escape per this many characters is where seeking stops paying off. */
|
|
const DENSE_ESCAPE_RATIO = 4
|
|
/** Leading escapes alone must not condemn a long plain tail to a full scan. */
|
|
const DENSE_ESCAPE_GRACE = 256
|
|
|
|
/**
|
|
* Count `"` and `\\` in an argument.
|
|
*
|
|
* `indexOf` skips plain runs with a native search, so the cost tracks the number
|
|
* of escapes rather than the length of the argument — which is what the WSL
|
|
* runner hands us: one multi-KB script with almost no escapes in it. Past the
|
|
* density where seeking each escape costs more than reading every character,
|
|
* a plain scan finishes the rest; that is the quote-dense script's shape.
|
|
*/
|
|
function countEscapes(arg: string): number {
|
|
let count = 0
|
|
let scanFrom = 0
|
|
let quote = arg.indexOf('"')
|
|
let slash = arg.indexOf('\\')
|
|
while (quote !== -1 || slash !== -1) {
|
|
const at = slash === -1 || (quote !== -1 && quote < slash) ? quote : slash
|
|
if (at === quote) {
|
|
quote = arg.indexOf('"', at + 1)
|
|
} else {
|
|
slash = arg.indexOf('\\', at + 1)
|
|
}
|
|
count += 1
|
|
scanFrom = at + 1
|
|
if (count * DENSE_ESCAPE_RATIO > at + DENSE_ESCAPE_GRACE) {
|
|
break
|
|
}
|
|
}
|
|
if (quote === -1 && slash === -1) {
|
|
return count
|
|
}
|
|
for (let index = scanFrom; index < arg.length; index += 1) {
|
|
const code = arg.charCodeAt(index)
|
|
if (code === 34 || code === 92) {
|
|
count += 1
|
|
}
|
|
}
|
|
return count
|
|
}
|