Skip to content

Commit 0cdb0e3

Browse files
committed
shell: avoid allocating scanner for each shellword
Allocating Scanner is expensive as it embeds a 1KB byte array into the struct. Signed-off-by: Tonis Tiigi <[email protected]>
1 parent 5e300b1 commit 0cdb0e3

File tree

1 file changed

+29
-40
lines changed
  • frontend/dockerfile/shell

1 file changed

+29
-40
lines changed

frontend/dockerfile/shell/lex.go

Lines changed: 29 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@ import (
1919
// tokens. Tries to mimic bash shell process.
2020
// It doesn't support all flavors of ${xx:...} formats but new ones can
2121
// be added by adding code to the "special ${} format processing" section
22+
//
23+
// It is not safe to call methods on a Lex instance concurrently.
2224
type Lex struct {
2325
escapeToken rune
2426
RawQuotes bool
2527
RawEscapes bool
2628
SkipProcessQuotes bool
2729
SkipUnsetEnv bool
30+
shellWord shellWord
2831
}
2932

3033
// NewLex creates a new Lex which uses escapeToken to escape quotes.
@@ -70,40 +73,29 @@ type ProcessWordResult struct {
7073
// ProcessWordWithMatches will use the 'env' list of environment variables,
7174
// replace any env var references in 'word' and return the env that were used.
7275
func (s *Lex) ProcessWordWithMatches(word string, env map[string]string) (ProcessWordResult, error) {
73-
sw := s.init(word, env)
74-
word, words, err := sw.process(word)
75-
return ProcessWordResult{
76-
Result: word,
77-
Words: words,
78-
Matched: sw.matches,
79-
Unmatched: sw.nonmatches,
80-
}, err
76+
return s.process(word, env)
8177
}
8278

8379
func (s *Lex) ProcessWordsWithMap(word string, env map[string]string) ([]string, error) {
8480
result, err := s.process(word, env)
8581
return result.Words, err
8682
}
8783

88-
func (s *Lex) init(word string, env map[string]string) *shellWord {
89-
sw := &shellWord{
90-
envs: env,
91-
escapeToken: s.escapeToken,
92-
skipUnsetEnv: s.SkipUnsetEnv,
93-
skipProcessQuotes: s.SkipProcessQuotes,
94-
rawQuotes: s.RawQuotes,
95-
rawEscapes: s.RawEscapes,
96-
matches: make(map[string]struct{}),
97-
nonmatches: make(map[string]struct{}),
98-
}
84+
func (s *Lex) initWord(word string, env map[string]string) *shellWord {
85+
sw := &s.shellWord
86+
sw.Lex = s
87+
sw.envs = env
88+
sw.rawEscapes = s.RawEscapes
89+
sw.matches = make(map[string]struct{}) // TODO: create these maps lazily
90+
sw.nonmatches = make(map[string]struct{})
9991
sw.scanner.Init(strings.NewReader(word))
10092
return sw
10193
}
10294

103-
func (s *Lex) process(word string, env map[string]string) (*ProcessWordResult, error) {
104-
sw := s.init(word, env)
95+
func (s *Lex) process(word string, env map[string]string) (ProcessWordResult, error) {
96+
sw := s.initWord(word, env)
10597
word, words, err := sw.process(word)
106-
return &ProcessWordResult{
98+
return ProcessWordResult{
10799
Result: word,
108100
Words: words,
109101
Matched: sw.matches,
@@ -112,15 +104,12 @@ func (s *Lex) process(word string, env map[string]string) (*ProcessWordResult, e
112104
}
113105

114106
type shellWord struct {
115-
scanner scanner.Scanner
116-
envs map[string]string
117-
escapeToken rune
118-
rawQuotes bool
119-
rawEscapes bool
120-
skipUnsetEnv bool
121-
skipProcessQuotes bool
122-
matches map[string]struct{}
123-
nonmatches map[string]struct{}
107+
*Lex
108+
scanner scanner.Scanner
109+
envs map[string]string
110+
rawEscapes bool
111+
matches map[string]struct{}
112+
nonmatches map[string]struct{}
124113
}
125114

126115
func (sw *shellWord) process(source string) (string, []string, error) {
@@ -185,7 +174,7 @@ func (sw *shellWord) processStopOn(stopChar rune, rawEscapes bool) (string, []st
185174
var charFuncMapping = map[rune]func() (string, error){
186175
'$': sw.processDollar,
187176
}
188-
if !sw.skipProcessQuotes {
177+
if !sw.SkipProcessQuotes {
189178
charFuncMapping['\''] = sw.processSingleQuote
190179
charFuncMapping['"'] = sw.processDoubleQuote
191180
}
@@ -262,7 +251,7 @@ func (sw *shellWord) processSingleQuote() (string, error) {
262251
var result bytes.Buffer
263252

264253
ch := sw.scanner.Next()
265-
if sw.rawQuotes {
254+
if sw.RawQuotes {
266255
result.WriteRune(ch)
267256
}
268257

@@ -272,7 +261,7 @@ func (sw *shellWord) processSingleQuote() (string, error) {
272261
case scanner.EOF:
273262
return "", errors.New("unexpected end of statement while looking for matching single-quote")
274263
case '\'':
275-
if sw.rawQuotes {
264+
if sw.RawQuotes {
276265
result.WriteRune(ch)
277266
}
278267
return result.String(), nil
@@ -297,7 +286,7 @@ func (sw *shellWord) processDoubleQuote() (string, error) {
297286
var result bytes.Buffer
298287

299288
ch := sw.scanner.Next()
300-
if sw.rawQuotes {
289+
if sw.RawQuotes {
301290
result.WriteRune(ch)
302291
}
303292

@@ -307,7 +296,7 @@ func (sw *shellWord) processDoubleQuote() (string, error) {
307296
return "", errors.New("unexpected end of statement while looking for matching double-quote")
308297
case '"':
309298
ch := sw.scanner.Next()
310-
if sw.rawQuotes {
299+
if sw.RawQuotes {
311300
result.WriteRune(ch)
312301
}
313302
return result.String(), nil
@@ -351,7 +340,7 @@ func (sw *shellWord) processDollar() (string, error) {
351340
return "$", nil
352341
}
353342
value, found := sw.getEnv(name)
354-
if !found && sw.skipUnsetEnv {
343+
if !found && sw.SkipUnsetEnv {
355344
return "$" + name, nil
356345
}
357346
return value, nil
@@ -374,7 +363,7 @@ func (sw *shellWord) processDollar() (string, error) {
374363
case '}':
375364
// Normal ${xx} case
376365
value, set := sw.getEnv(name)
377-
if !set && sw.skipUnsetEnv {
366+
if !set && sw.SkipUnsetEnv {
378367
return fmt.Sprintf("${%s}", name), nil
379368
}
380369
return value, nil
@@ -396,7 +385,7 @@ func (sw *shellWord) processDollar() (string, error) {
396385
// Grab the current value of the variable in question so we
397386
// can use it to determine what to do based on the modifier
398387
value, set := sw.getEnv(name)
399-
if sw.skipUnsetEnv && !set {
388+
if sw.SkipUnsetEnv && !set {
400389
return fmt.Sprintf("${%s%s%s}", name, chs, word), nil
401390
}
402391

@@ -466,7 +455,7 @@ func (sw *shellWord) processDollar() (string, error) {
466455
}
467456

468457
value, set := sw.getEnv(name)
469-
if sw.skipUnsetEnv && !set {
458+
if sw.SkipUnsetEnv && !set {
470459
return fmt.Sprintf("${%s/%s/%s}", name, pattern, replacement), nil
471460
}
472461

0 commit comments

Comments
 (0)