diff options
Diffstat (limited to 'meta/recipes-devtools/go/go-1.14/CVE-2023-24538_5.patch')
-rw-r--r-- | meta/recipes-devtools/go/go-1.14/CVE-2023-24538_5.patch | 585 |
1 files changed, 585 insertions, 0 deletions
diff --git a/meta/recipes-devtools/go/go-1.14/CVE-2023-24538_5.patch b/meta/recipes-devtools/go/go-1.14/CVE-2023-24538_5.patch new file mode 100644 index 0000000000..fc38929648 --- /dev/null +++ b/meta/recipes-devtools/go/go-1.14/CVE-2023-24538_5.patch | |||
@@ -0,0 +1,585 @@ | |||
1 | From e0e6bca6ddc0e6d9fa3a5b644af9b446924fbf83 Mon Sep 17 00:00:00 2001 | ||
2 | From: Russ Cox <rsc@golang.org> | ||
3 | Date: Thu, 20 May 2021 12:46:33 -0400 | ||
4 | Subject: [PATCH 5/6] html/template, text/template: implement break and | ||
5 | continue for range loops | ||
6 | |||
7 | Break and continue for range loops was accepted as a proposal in June 2017. | ||
8 | It was implemented in CL 66410 (Oct 2017) | ||
9 | but then rolled back in CL 92155 (Feb 2018) | ||
10 | because html/template changes had not been implemented. | ||
11 | |||
12 | This CL reimplements break and continue in text/template | ||
13 | and then adds support for them in html/template as well. | ||
14 | |||
15 | Fixes #20531. | ||
16 | |||
17 | Change-Id: I05330482a976f1c078b4b49c2287bd9031bb7616 | ||
18 | Reviewed-on: https://go-review.googlesource.com/c/go/+/321491 | ||
19 | Trust: Russ Cox <rsc@golang.org> | ||
20 | Run-TryBot: Russ Cox <rsc@golang.org> | ||
21 | TryBot-Result: Go Bot <gobot@golang.org> | ||
22 | Reviewed-by: Rob Pike <r@golang.org> | ||
23 | |||
24 | Dependency Patch #5 | ||
25 | |||
26 | Upstream-Status: Backport from https://github.com/golang/go/commit/d0dd26a88c019d54f22463daae81e785f5867565 | ||
27 | CVE: CVE-2023-24538 | ||
28 | Signed-off-by: Shubham Kulkarni <skulkarni@mvista.com> | ||
29 | --- | ||
30 | src/html/template/context.go | 4 ++ | ||
31 | src/html/template/escape.go | 71 ++++++++++++++++++++++++++++++++++- | ||
32 | src/html/template/escape_test.go | 24 ++++++++++++ | ||
33 | src/text/template/doc.go | 8 ++++ | ||
34 | src/text/template/exec.go | 24 +++++++++++- | ||
35 | src/text/template/exec_test.go | 2 + | ||
36 | src/text/template/parse/lex.go | 13 ++++++- | ||
37 | src/text/template/parse/lex_test.go | 2 + | ||
38 | src/text/template/parse/node.go | 36 ++++++++++++++++++ | ||
39 | src/text/template/parse/parse.go | 42 ++++++++++++++++++++- | ||
40 | src/text/template/parse/parse_test.go | 8 ++++ | ||
41 | 11 files changed, 230 insertions(+), 4 deletions(-) | ||
42 | |||
43 | diff --git a/src/html/template/context.go b/src/html/template/context.go | ||
44 | index f7d4849..aaa7d08 100644 | ||
45 | --- a/src/html/template/context.go | ||
46 | +++ b/src/html/template/context.go | ||
47 | @@ -6,6 +6,7 @@ package template | ||
48 | |||
49 | import ( | ||
50 | "fmt" | ||
51 | + "text/template/parse" | ||
52 | ) | ||
53 | |||
54 | // context describes the state an HTML parser must be in when it reaches the | ||
55 | @@ -22,6 +23,7 @@ type context struct { | ||
56 | jsCtx jsCtx | ||
57 | attr attr | ||
58 | element element | ||
59 | + n parse.Node // for range break/continue | ||
60 | err *Error | ||
61 | } | ||
62 | |||
63 | @@ -141,6 +143,8 @@ const ( | ||
64 | // stateError is an infectious error state outside any valid | ||
65 | // HTML/CSS/JS construct. | ||
66 | stateError | ||
67 | + // stateDead marks unreachable code after a {{break}} or {{continue}}. | ||
68 | + stateDead | ||
69 | ) | ||
70 | |||
71 | // isComment is true for any state that contains content meant for template | ||
72 | diff --git a/src/html/template/escape.go b/src/html/template/escape.go | ||
73 | index 8739735..6dea79c 100644 | ||
74 | --- a/src/html/template/escape.go | ||
75 | +++ b/src/html/template/escape.go | ||
76 | @@ -97,6 +97,15 @@ type escaper struct { | ||
77 | actionNodeEdits map[*parse.ActionNode][]string | ||
78 | templateNodeEdits map[*parse.TemplateNode]string | ||
79 | textNodeEdits map[*parse.TextNode][]byte | ||
80 | + // rangeContext holds context about the current range loop. | ||
81 | + rangeContext *rangeContext | ||
82 | +} | ||
83 | + | ||
84 | +// rangeContext holds information about the current range loop. | ||
85 | +type rangeContext struct { | ||
86 | + outer *rangeContext // outer loop | ||
87 | + breaks []context // context at each break action | ||
88 | + continues []context // context at each continue action | ||
89 | } | ||
90 | |||
91 | // makeEscaper creates a blank escaper for the given set. | ||
92 | @@ -109,6 +118,7 @@ func makeEscaper(n *nameSpace) escaper { | ||
93 | map[*parse.ActionNode][]string{}, | ||
94 | map[*parse.TemplateNode]string{}, | ||
95 | map[*parse.TextNode][]byte{}, | ||
96 | + nil, | ||
97 | } | ||
98 | } | ||
99 | |||
100 | @@ -124,8 +134,16 @@ func (e *escaper) escape(c context, n parse.Node) context { | ||
101 | switch n := n.(type) { | ||
102 | case *parse.ActionNode: | ||
103 | return e.escapeAction(c, n) | ||
104 | + case *parse.BreakNode: | ||
105 | + c.n = n | ||
106 | + e.rangeContext.breaks = append(e.rangeContext.breaks, c) | ||
107 | + return context{state: stateDead} | ||
108 | case *parse.CommentNode: | ||
109 | return c | ||
110 | + case *parse.ContinueNode: | ||
111 | + c.n = n | ||
112 | + e.rangeContext.continues = append(e.rangeContext.breaks, c) | ||
113 | + return context{state: stateDead} | ||
114 | case *parse.IfNode: | ||
115 | return e.escapeBranch(c, &n.BranchNode, "if") | ||
116 | case *parse.ListNode: | ||
117 | @@ -427,6 +445,12 @@ func join(a, b context, node parse.Node, nodeName string) context { | ||
118 | if b.state == stateError { | ||
119 | return b | ||
120 | } | ||
121 | + if a.state == stateDead { | ||
122 | + return b | ||
123 | + } | ||
124 | + if b.state == stateDead { | ||
125 | + return a | ||
126 | + } | ||
127 | if a.eq(b) { | ||
128 | return a | ||
129 | } | ||
130 | @@ -466,14 +490,27 @@ func join(a, b context, node parse.Node, nodeName string) context { | ||
131 | |||
132 | // escapeBranch escapes a branch template node: "if", "range" and "with". | ||
133 | func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) context { | ||
134 | + if nodeName == "range" { | ||
135 | + e.rangeContext = &rangeContext{outer: e.rangeContext} | ||
136 | + } | ||
137 | c0 := e.escapeList(c, n.List) | ||
138 | - if nodeName == "range" && c0.state != stateError { | ||
139 | + if nodeName == "range" { | ||
140 | + if c0.state != stateError { | ||
141 | + c0 = joinRange(c0, e.rangeContext) | ||
142 | + } | ||
143 | + e.rangeContext = e.rangeContext.outer | ||
144 | + if c0.state == stateError { | ||
145 | + return c0 | ||
146 | + } | ||
147 | + | ||
148 | // The "true" branch of a "range" node can execute multiple times. | ||
149 | // We check that executing n.List once results in the same context | ||
150 | // as executing n.List twice. | ||
151 | + e.rangeContext = &rangeContext{outer: e.rangeContext} | ||
152 | c1, _ := e.escapeListConditionally(c0, n.List, nil) | ||
153 | c0 = join(c0, c1, n, nodeName) | ||
154 | if c0.state == stateError { | ||
155 | + e.rangeContext = e.rangeContext.outer | ||
156 | // Make clear that this is a problem on loop re-entry | ||
157 | // since developers tend to overlook that branch when | ||
158 | // debugging templates. | ||
159 | @@ -481,11 +518,39 @@ func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) | ||
160 | c0.err.Description = "on range loop re-entry: " + c0.err.Description | ||
161 | return c0 | ||
162 | } | ||
163 | + c0 = joinRange(c0, e.rangeContext) | ||
164 | + e.rangeContext = e.rangeContext.outer | ||
165 | + if c0.state == stateError { | ||
166 | + return c0 | ||
167 | + } | ||
168 | } | ||
169 | c1 := e.escapeList(c, n.ElseList) | ||
170 | return join(c0, c1, n, nodeName) | ||
171 | } | ||
172 | |||
173 | +func joinRange(c0 context, rc *rangeContext) context { | ||
174 | + // Merge contexts at break and continue statements into overall body context. | ||
175 | + // In theory we could treat breaks differently from continues, but for now it is | ||
176 | + // enough to treat them both as going back to the start of the loop (which may then stop). | ||
177 | + for _, c := range rc.breaks { | ||
178 | + c0 = join(c0, c, c.n, "range") | ||
179 | + if c0.state == stateError { | ||
180 | + c0.err.Line = c.n.(*parse.BreakNode).Line | ||
181 | + c0.err.Description = "at range loop break: " + c0.err.Description | ||
182 | + return c0 | ||
183 | + } | ||
184 | + } | ||
185 | + for _, c := range rc.continues { | ||
186 | + c0 = join(c0, c, c.n, "range") | ||
187 | + if c0.state == stateError { | ||
188 | + c0.err.Line = c.n.(*parse.ContinueNode).Line | ||
189 | + c0.err.Description = "at range loop continue: " + c0.err.Description | ||
190 | + return c0 | ||
191 | + } | ||
192 | + } | ||
193 | + return c0 | ||
194 | +} | ||
195 | + | ||
196 | // escapeList escapes a list template node. | ||
197 | func (e *escaper) escapeList(c context, n *parse.ListNode) context { | ||
198 | if n == nil { | ||
199 | @@ -493,6 +558,9 @@ func (e *escaper) escapeList(c context, n *parse.ListNode) context { | ||
200 | } | ||
201 | for _, m := range n.Nodes { | ||
202 | c = e.escape(c, m) | ||
203 | + if c.state == stateDead { | ||
204 | + break | ||
205 | + } | ||
206 | } | ||
207 | return c | ||
208 | } | ||
209 | @@ -503,6 +571,7 @@ func (e *escaper) escapeList(c context, n *parse.ListNode) context { | ||
210 | // which is the same as whether e was updated. | ||
211 | func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) { | ||
212 | e1 := makeEscaper(e.ns) | ||
213 | + e1.rangeContext = e.rangeContext | ||
214 | // Make type inferences available to f. | ||
215 | for k, v := range e.output { | ||
216 | e1.output[k] = v | ||
217 | diff --git a/src/html/template/escape_test.go b/src/html/template/escape_test.go | ||
218 | index c709660..fa2b84a 100644 | ||
219 | --- a/src/html/template/escape_test.go | ||
220 | +++ b/src/html/template/escape_test.go | ||
221 | @@ -920,6 +920,22 @@ func TestErrors(t *testing.T) { | ||
222 | "<a href='/foo?{{range .Items}}&{{.K}}={{.V}}{{end}}'>", | ||
223 | "", | ||
224 | }, | ||
225 | + { | ||
226 | + "{{range .Items}}<a{{if .X}}{{end}}>{{end}}", | ||
227 | + "", | ||
228 | + }, | ||
229 | + { | ||
230 | + "{{range .Items}}<a{{if .X}}{{end}}>{{continue}}{{end}}", | ||
231 | + "", | ||
232 | + }, | ||
233 | + { | ||
234 | + "{{range .Items}}<a{{if .X}}{{end}}>{{break}}{{end}}", | ||
235 | + "", | ||
236 | + }, | ||
237 | + { | ||
238 | + "{{range .Items}}<a{{if .X}}{{end}}>{{if .X}}{{break}}{{end}}{{end}}", | ||
239 | + "", | ||
240 | + }, | ||
241 | // Error cases. | ||
242 | { | ||
243 | "{{if .Cond}}<a{{end}}", | ||
244 | @@ -956,6 +972,14 @@ func TestErrors(t *testing.T) { | ||
245 | "z:2:8: on range loop re-entry: {{range}} branches", | ||
246 | }, | ||
247 | { | ||
248 | + "{{range .Items}}<a{{if .X}}{{break}}{{end}}>{{end}}", | ||
249 | + "z:1:29: at range loop break: {{range}} branches end in different contexts", | ||
250 | + }, | ||
251 | + { | ||
252 | + "{{range .Items}}<a{{if .X}}{{continue}}{{end}}>{{end}}", | ||
253 | + "z:1:29: at range loop continue: {{range}} branches end in different contexts", | ||
254 | + }, | ||
255 | + { | ||
256 | "<a b=1 c={{.H}}", | ||
257 | "z: ends in a non-text context: {stateAttr delimSpaceOrTagEnd", | ||
258 | }, | ||
259 | diff --git a/src/text/template/doc.go b/src/text/template/doc.go | ||
260 | index 7b30294..0228b15 100644 | ||
261 | --- a/src/text/template/doc.go | ||
262 | +++ b/src/text/template/doc.go | ||
263 | @@ -112,6 +112,14 @@ data, defined in detail in the corresponding sections that follow. | ||
264 | T0 is executed; otherwise, dot is set to the successive elements | ||
265 | of the array, slice, or map and T1 is executed. | ||
266 | |||
267 | + {{break}} | ||
268 | + The innermost {{range pipeline}} loop is ended early, stopping the | ||
269 | + current iteration and bypassing all remaining iterations. | ||
270 | + | ||
271 | + {{continue}} | ||
272 | + The current iteration of the innermost {{range pipeline}} loop is | ||
273 | + stopped, and the loop starts the next iteration. | ||
274 | + | ||
275 | {{template "name"}} | ||
276 | The template with the specified name is executed with nil data. | ||
277 | |||
278 | diff --git a/src/text/template/exec.go b/src/text/template/exec.go | ||
279 | index 7ac5175..6cb140a 100644 | ||
280 | --- a/src/text/template/exec.go | ||
281 | +++ b/src/text/template/exec.go | ||
282 | @@ -5,6 +5,7 @@ | ||
283 | package template | ||
284 | |||
285 | import ( | ||
286 | + "errors" | ||
287 | "fmt" | ||
288 | "internal/fmtsort" | ||
289 | "io" | ||
290 | @@ -244,6 +245,12 @@ func (t *Template) DefinedTemplates() string { | ||
291 | return b.String() | ||
292 | } | ||
293 | |||
294 | +// Sentinel errors for use with panic to signal early exits from range loops. | ||
295 | +var ( | ||
296 | + walkBreak = errors.New("break") | ||
297 | + walkContinue = errors.New("continue") | ||
298 | +) | ||
299 | + | ||
300 | // Walk functions step through the major pieces of the template structure, | ||
301 | // generating output as they go. | ||
302 | func (s *state) walk(dot reflect.Value, node parse.Node) { | ||
303 | @@ -256,7 +263,11 @@ func (s *state) walk(dot reflect.Value, node parse.Node) { | ||
304 | if len(node.Pipe.Decl) == 0 { | ||
305 | s.printValue(node, val) | ||
306 | } | ||
307 | + case *parse.BreakNode: | ||
308 | + panic(walkBreak) | ||
309 | case *parse.CommentNode: | ||
310 | + case *parse.ContinueNode: | ||
311 | + panic(walkContinue) | ||
312 | case *parse.IfNode: | ||
313 | s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList) | ||
314 | case *parse.ListNode: | ||
315 | @@ -335,6 +346,11 @@ func isTrue(val reflect.Value) (truth, ok bool) { | ||
316 | |||
317 | func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { | ||
318 | s.at(r) | ||
319 | + defer func() { | ||
320 | + if r := recover(); r != nil && r != walkBreak { | ||
321 | + panic(r) | ||
322 | + } | ||
323 | + }() | ||
324 | defer s.pop(s.mark()) | ||
325 | val, _ := indirect(s.evalPipeline(dot, r.Pipe)) | ||
326 | // mark top of stack before any variables in the body are pushed. | ||
327 | @@ -348,8 +364,14 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { | ||
328 | if len(r.Pipe.Decl) > 1 { | ||
329 | s.setTopVar(2, index) | ||
330 | } | ||
331 | + defer s.pop(mark) | ||
332 | + defer func() { | ||
333 | + // Consume panic(walkContinue) | ||
334 | + if r := recover(); r != nil && r != walkContinue { | ||
335 | + panic(r) | ||
336 | + } | ||
337 | + }() | ||
338 | s.walk(elem, r.List) | ||
339 | - s.pop(mark) | ||
340 | } | ||
341 | switch val.Kind() { | ||
342 | case reflect.Array, reflect.Slice: | ||
343 | diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go | ||
344 | index 3309b33..a639f44 100644 | ||
345 | --- a/src/text/template/exec_test.go | ||
346 | +++ b/src/text/template/exec_test.go | ||
347 | @@ -563,6 +563,8 @@ var execTests = []execTest{ | ||
348 | {"range empty no else", "{{range .SIEmpty}}-{{.}}-{{end}}", "", tVal, true}, | ||
349 | {"range []int else", "{{range .SI}}-{{.}}-{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true}, | ||
350 | {"range empty else", "{{range .SIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, | ||
351 | + {"range []int break else", "{{range .SI}}-{{.}}-{{break}}NOTREACHED{{else}}EMPTY{{end}}", "-3-", tVal, true}, | ||
352 | + {"range []int continue else", "{{range .SI}}-{{.}}-{{continue}}NOTREACHED{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true}, | ||
353 | {"range []bool", "{{range .SB}}-{{.}}-{{end}}", "-true--false-", tVal, true}, | ||
354 | {"range []int method", "{{range .SI | .MAdd .I}}-{{.}}-{{end}}", "-20--21--22-", tVal, true}, | ||
355 | {"range map", "{{range .MSI}}-{{.}}-{{end}}", "-1--3--2-", tVal, true}, | ||
356 | diff --git a/src/text/template/parse/lex.go b/src/text/template/parse/lex.go | ||
357 | index 6784071..95e3377 100644 | ||
358 | --- a/src/text/template/parse/lex.go | ||
359 | +++ b/src/text/template/parse/lex.go | ||
360 | @@ -62,6 +62,8 @@ const ( | ||
361 | // Keywords appear after all the rest. | ||
362 | itemKeyword // used only to delimit the keywords | ||
363 | itemBlock // block keyword | ||
364 | + itemBreak // break keyword | ||
365 | + itemContinue // continue keyword | ||
366 | itemDot // the cursor, spelled '.' | ||
367 | itemDefine // define keyword | ||
368 | itemElse // else keyword | ||
369 | @@ -76,6 +78,8 @@ const ( | ||
370 | var key = map[string]itemType{ | ||
371 | ".": itemDot, | ||
372 | "block": itemBlock, | ||
373 | + "break": itemBreak, | ||
374 | + "continue": itemContinue, | ||
375 | "define": itemDefine, | ||
376 | "else": itemElse, | ||
377 | "end": itemEnd, | ||
378 | @@ -119,6 +123,8 @@ type lexer struct { | ||
379 | parenDepth int // nesting depth of ( ) exprs | ||
380 | line int // 1+number of newlines seen | ||
381 | startLine int // start line of this item | ||
382 | + breakOK bool // break keyword allowed | ||
383 | + continueOK bool // continue keyword allowed | ||
384 | } | ||
385 | |||
386 | // next returns the next rune in the input. | ||
387 | @@ -461,7 +467,12 @@ Loop: | ||
388 | } | ||
389 | switch { | ||
390 | case key[word] > itemKeyword: | ||
391 | - l.emit(key[word]) | ||
392 | + item := key[word] | ||
393 | + if item == itemBreak && !l.breakOK || item == itemContinue && !l.continueOK { | ||
394 | + l.emit(itemIdentifier) | ||
395 | + } else { | ||
396 | + l.emit(item) | ||
397 | + } | ||
398 | case word[0] == '.': | ||
399 | l.emit(itemField) | ||
400 | case word == "true", word == "false": | ||
401 | diff --git a/src/text/template/parse/lex_test.go b/src/text/template/parse/lex_test.go | ||
402 | index 6510eed..df6aabf 100644 | ||
403 | --- a/src/text/template/parse/lex_test.go | ||
404 | +++ b/src/text/template/parse/lex_test.go | ||
405 | @@ -35,6 +35,8 @@ var itemName = map[itemType]string{ | ||
406 | // keywords | ||
407 | itemDot: ".", | ||
408 | itemBlock: "block", | ||
409 | + itemBreak: "break", | ||
410 | + itemContinue: "continue", | ||
411 | itemDefine: "define", | ||
412 | itemElse: "else", | ||
413 | itemIf: "if", | ||
414 | diff --git a/src/text/template/parse/node.go b/src/text/template/parse/node.go | ||
415 | index a9dad5e..c398da0 100644 | ||
416 | --- a/src/text/template/parse/node.go | ||
417 | +++ b/src/text/template/parse/node.go | ||
418 | @@ -71,6 +71,8 @@ const ( | ||
419 | NodeVariable // A $ variable. | ||
420 | NodeWith // A with action. | ||
421 | NodeComment // A comment. | ||
422 | + NodeBreak // A break action. | ||
423 | + NodeContinue // A continue action. | ||
424 | ) | ||
425 | |||
426 | // Nodes. | ||
427 | @@ -907,6 +909,40 @@ func (i *IfNode) Copy() Node { | ||
428 | return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList()) | ||
429 | } | ||
430 | |||
431 | +// BreakNode represents a {{break}} action. | ||
432 | +type BreakNode struct { | ||
433 | + tr *Tree | ||
434 | + NodeType | ||
435 | + Pos | ||
436 | + Line int | ||
437 | +} | ||
438 | + | ||
439 | +func (t *Tree) newBreak(pos Pos, line int) *BreakNode { | ||
440 | + return &BreakNode{tr: t, NodeType: NodeBreak, Pos: pos, Line: line} | ||
441 | +} | ||
442 | + | ||
443 | +func (b *BreakNode) Copy() Node { return b.tr.newBreak(b.Pos, b.Line) } | ||
444 | +func (b *BreakNode) String() string { return "{{break}}" } | ||
445 | +func (b *BreakNode) tree() *Tree { return b.tr } | ||
446 | +func (b *BreakNode) writeTo(sb *strings.Builder) { sb.WriteString("{{break}}") } | ||
447 | + | ||
448 | +// ContinueNode represents a {{continue}} action. | ||
449 | +type ContinueNode struct { | ||
450 | + tr *Tree | ||
451 | + NodeType | ||
452 | + Pos | ||
453 | + Line int | ||
454 | +} | ||
455 | + | ||
456 | +func (t *Tree) newContinue(pos Pos, line int) *ContinueNode { | ||
457 | + return &ContinueNode{tr: t, NodeType: NodeContinue, Pos: pos, Line: line} | ||
458 | +} | ||
459 | + | ||
460 | +func (c *ContinueNode) Copy() Node { return c.tr.newContinue(c.Pos, c.Line) } | ||
461 | +func (c *ContinueNode) String() string { return "{{continue}}" } | ||
462 | +func (c *ContinueNode) tree() *Tree { return c.tr } | ||
463 | +func (c *ContinueNode) writeTo(sb *strings.Builder) { sb.WriteString("{{continue}}") } | ||
464 | + | ||
465 | // RangeNode represents a {{range}} action and its commands. | ||
466 | type RangeNode struct { | ||
467 | BranchNode | ||
468 | diff --git a/src/text/template/parse/parse.go b/src/text/template/parse/parse.go | ||
469 | index 5e6e512..7f78b56 100644 | ||
470 | --- a/src/text/template/parse/parse.go | ||
471 | +++ b/src/text/template/parse/parse.go | ||
472 | @@ -31,6 +31,7 @@ type Tree struct { | ||
473 | vars []string // variables defined at the moment. | ||
474 | treeSet map[string]*Tree | ||
475 | actionLine int // line of left delim starting action | ||
476 | + rangeDepth int | ||
477 | mode Mode | ||
478 | } | ||
479 | |||
480 | @@ -223,6 +224,8 @@ func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer, treeSet ma | ||
481 | t.vars = []string{"$"} | ||
482 | t.funcs = funcs | ||
483 | t.treeSet = treeSet | ||
484 | + lex.breakOK = !t.hasFunction("break") | ||
485 | + lex.continueOK = !t.hasFunction("continue") | ||
486 | } | ||
487 | |||
488 | // stopParse terminates parsing. | ||
489 | @@ -385,6 +388,10 @@ func (t *Tree) action() (n Node) { | ||
490 | switch token := t.nextNonSpace(); token.typ { | ||
491 | case itemBlock: | ||
492 | return t.blockControl() | ||
493 | + case itemBreak: | ||
494 | + return t.breakControl(token.pos, token.line) | ||
495 | + case itemContinue: | ||
496 | + return t.continueControl(token.pos, token.line) | ||
497 | case itemElse: | ||
498 | return t.elseControl() | ||
499 | case itemEnd: | ||
500 | @@ -404,6 +411,32 @@ func (t *Tree) action() (n Node) { | ||
501 | return t.newAction(token.pos, token.line, t.pipeline("command", itemRightDelim)) | ||
502 | } | ||
503 | |||
504 | +// Break: | ||
505 | +// {{break}} | ||
506 | +// Break keyword is past. | ||
507 | +func (t *Tree) breakControl(pos Pos, line int) Node { | ||
508 | + if token := t.next(); token.typ != itemRightDelim { | ||
509 | + t.unexpected(token, "in {{break}}") | ||
510 | + } | ||
511 | + if t.rangeDepth == 0 { | ||
512 | + t.errorf("{{break}} outside {{range}}") | ||
513 | + } | ||
514 | + return t.newBreak(pos, line) | ||
515 | +} | ||
516 | + | ||
517 | +// Continue: | ||
518 | +// {{continue}} | ||
519 | +// Continue keyword is past. | ||
520 | +func (t *Tree) continueControl(pos Pos, line int) Node { | ||
521 | + if token := t.next(); token.typ != itemRightDelim { | ||
522 | + t.unexpected(token, "in {{continue}}") | ||
523 | + } | ||
524 | + if t.rangeDepth == 0 { | ||
525 | + t.errorf("{{continue}} outside {{range}}") | ||
526 | + } | ||
527 | + return t.newContinue(pos, line) | ||
528 | +} | ||
529 | + | ||
530 | // Pipeline: | ||
531 | // declarations? command ('|' command)* | ||
532 | func (t *Tree) pipeline(context string, end itemType) (pipe *PipeNode) { | ||
533 | @@ -479,8 +512,14 @@ func (t *Tree) checkPipeline(pipe *PipeNode, context string) { | ||
534 | func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) { | ||
535 | defer t.popVars(len(t.vars)) | ||
536 | pipe = t.pipeline(context, itemRightDelim) | ||
537 | + if context == "range" { | ||
538 | + t.rangeDepth++ | ||
539 | + } | ||
540 | var next Node | ||
541 | list, next = t.itemList() | ||
542 | + if context == "range" { | ||
543 | + t.rangeDepth-- | ||
544 | + } | ||
545 | switch next.Type() { | ||
546 | case nodeEnd: //done | ||
547 | case nodeElse: | ||
548 | @@ -522,7 +561,8 @@ func (t *Tree) ifControl() Node { | ||
549 | // {{range pipeline}} itemList {{else}} itemList {{end}} | ||
550 | // Range keyword is past. | ||
551 | func (t *Tree) rangeControl() Node { | ||
552 | - return t.newRange(t.parseControl(false, "range")) | ||
553 | + r := t.newRange(t.parseControl(false, "range")) | ||
554 | + return r | ||
555 | } | ||
556 | |||
557 | // With: | ||
558 | diff --git a/src/text/template/parse/parse_test.go b/src/text/template/parse/parse_test.go | ||
559 | index 220f984..ba45636 100644 | ||
560 | --- a/src/text/template/parse/parse_test.go | ||
561 | +++ b/src/text/template/parse/parse_test.go | ||
562 | @@ -230,6 +230,10 @@ var parseTests = []parseTest{ | ||
563 | `{{range $x := .SI}}{{.}}{{end}}`}, | ||
564 | {"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError, | ||
565 | `{{range $x, $y := .SI}}{{.}}{{end}}`}, | ||
566 | + {"range with break", "{{range .SI}}{{.}}{{break}}{{end}}", noError, | ||
567 | + `{{range .SI}}{{.}}{{break}}{{end}}`}, | ||
568 | + {"range with continue", "{{range .SI}}{{.}}{{continue}}{{end}}", noError, | ||
569 | + `{{range .SI}}{{.}}{{continue}}{{end}}`}, | ||
570 | {"constants", "{{range .SI 1 -3.2i true false 'a' nil}}{{end}}", noError, | ||
571 | `{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`}, | ||
572 | {"template", "{{template `x`}}", noError, | ||
573 | @@ -279,6 +283,10 @@ var parseTests = []parseTest{ | ||
574 | {"adjacent args", "{{printf 3`x`}}", hasError, ""}, | ||
575 | {"adjacent args with .", "{{printf `x`.}}", hasError, ""}, | ||
576 | {"extra end after if", "{{if .X}}a{{else if .Y}}b{{end}}{{end}}", hasError, ""}, | ||
577 | + {"break outside range", "{{range .}}{{end}} {{break}}", hasError, ""}, | ||
578 | + {"continue outside range", "{{range .}}{{end}} {{continue}}", hasError, ""}, | ||
579 | + {"break in range else", "{{range .}}{{else}}{{break}}{{end}}", hasError, ""}, | ||
580 | + {"continue in range else", "{{range .}}{{else}}{{continue}}{{end}}", hasError, ""}, | ||
581 | // Other kinds of assignments and operators aren't available yet. | ||
582 | {"bug0a", "{{$x := 0}}{{$x}}", noError, "{{$x := 0}}{{$x}}"}, | ||
583 | {"bug0b", "{{$x += 1}}{{$x}}", hasError, ""}, | ||
584 | -- | ||
585 | 2.7.4 | ||