From 8f4b3333f23658721b4ed95f4ce2db19b3207aca Mon Sep 17 00:00:00 2001 From: adawang Date: Tue, 16 Jun 2026 17:42:54 +0800 Subject: [PATCH] perf(sandbox): compile command-substitution regexps once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hasCommandSubstitution recompiled its three regexps with regexp.MustCompile on every call, and ValidateArgs calls it once per argument — so validating an N-arg command compiled 3N patterns. Hoist them to a package-level slice compiled once at init. Behavior is unchanged (same patterns); covered by the existing TestScriptValidator_ValidateArgs / ValidateAll tests. Benchmark, ValidateArgs with 8 args (-benchmem): before: 16374 ns/op, 40142 B/op, 505 allocs/op after: 2092 ns/op, 32 B/op, 1 allocs/op Co-Authored-By: Claude Opus 4.8 --- internal/sandbox/validator.go | 17 ++++++++++------- internal/sandbox/validator_test.go | 9 +++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/internal/sandbox/validator.go b/internal/sandbox/validator.go index 28eb9cf3..0327dcf7 100644 --- a/internal/sandbox/validator.go +++ b/internal/sandbox/validator.go @@ -217,15 +217,18 @@ func (v *ScriptValidator) hasShellOperators(s string) bool { return false } +// Command-substitution patterns, compiled once. hasCommandSubstitution runs +// per argument in ValidateArgs, so recompiling these on every call wasted work +// proportional to the argument count. +var commandSubstitutionPatterns = []*regexp.Regexp{ + regexp.MustCompile(`\$\([^)]+\)`), // $(command) + regexp.MustCompile("`[^`]+`"), // `command` + regexp.MustCompile(`\$\{[^}]*\$\(`), // ${...$(command) +} + // hasCommandSubstitution checks for command substitution patterns func (v *ScriptValidator) hasCommandSubstitution(s string) bool { - patterns := []*regexp.Regexp{ - regexp.MustCompile(`\$\([^)]+\)`), // $(command) - regexp.MustCompile("`[^`]+`"), // `command` - regexp.MustCompile(`\$\{[^}]*\$\(`), // ${...$(command) - } - - for _, p := range patterns { + for _, p := range commandSubstitutionPatterns { if p.MatchString(s) { return true } diff --git a/internal/sandbox/validator_test.go b/internal/sandbox/validator_test.go index 67e18174..a8fd3a94 100644 --- a/internal/sandbox/validator_test.go +++ b/internal/sandbox/validator_test.go @@ -359,3 +359,12 @@ func containsHelper(s, substr string) bool { } return false } + +func BenchmarkValidateArgs(b *testing.B) { + v := NewScriptValidator() + args := []string{"--input", "file.txt", "--name", "report 2024", "--out", "/tmp/x", "--verbose", "--limit=50"} + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = v.ValidateArgs(args) + } +}