Skip to content

Commit 405726f

Browse files
authored
Clone ASTs to avoid aliasing and double-unescaping (google#210)
* Clone ASTs to avoid aliasing and double-unescaping
1 parent 5cc426e commit 405726f

File tree

2 files changed

+317
-8
lines changed

2 files changed

+317
-8
lines changed

ast/clone.go

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
/*
2+
Copyright 2018 Google Inc. All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package ast
18+
19+
import (
20+
"fmt"
21+
"reflect"
22+
)
23+
24+
// Updates fields of specPtr to point to deep clones.
25+
func cloneForSpec(specPtr *ForSpec) {
26+
clone(&specPtr.Expr)
27+
oldOuter := specPtr.Outer
28+
specPtr.Outer = new(ForSpec)
29+
*specPtr.Outer = *oldOuter
30+
cloneForSpec(specPtr.Outer)
31+
for i := range specPtr.Conditions {
32+
clone(&specPtr.Conditions[i].Expr)
33+
}
34+
}
35+
36+
// Updates fields of params to point to deep clones.
37+
func cloneParameters(params *Parameters) {
38+
if params == nil {
39+
return
40+
}
41+
params.Optional = append(make([]NamedParameter, 0), params.Optional...)
42+
for i := range params.Optional {
43+
clone(&params.Optional[i].DefaultArg)
44+
}
45+
}
46+
47+
// Updates fields of field to point to deep clones.
48+
func cloneField(field *ObjectField) {
49+
if field.Method != nil {
50+
field.Method = Clone(field.Method).(*Function)
51+
}
52+
53+
oldParams := field.Params
54+
if oldParams != nil {
55+
field.Params = new(Parameters)
56+
*field.Params = *oldParams
57+
}
58+
cloneParameters(field.Params)
59+
60+
clone(&field.Expr1)
61+
clone(&field.Expr2)
62+
clone(&field.Expr3)
63+
}
64+
65+
// Updates fields of field to point to deep clones.
66+
func cloneDesugaredField(field *DesugaredObjectField) {
67+
clone(&field.Name)
68+
clone(&field.Body)
69+
}
70+
71+
// Updates the NodeBase fields of astPtr to point to deep clones.
72+
func cloneNodeBase(astPtr Node) {
73+
if astPtr.Context() != nil {
74+
newContext := new(string)
75+
*newContext = *astPtr.Context()
76+
astPtr.SetContext(newContext)
77+
}
78+
astPtr.SetFreeVariables(append(make(Identifiers, 0), astPtr.FreeVariables()...))
79+
}
80+
81+
// Updates *astPtr to point to a deep clone of what it originally pointed at.
82+
func clone(astPtr *Node) {
83+
node := *astPtr
84+
if node == nil {
85+
return
86+
}
87+
88+
switch node := node.(type) {
89+
case *Apply:
90+
r := new(Apply)
91+
*astPtr = r
92+
*r = *node
93+
clone(&r.Target)
94+
r.Arguments.Positional = append(make(Nodes, 0), r.Arguments.Positional...)
95+
for i := range r.Arguments.Positional {
96+
clone(&r.Arguments.Positional[i])
97+
}
98+
r.Arguments.Named = append(make([]NamedArgument, 0), r.Arguments.Named...)
99+
for i := range r.Arguments.Named {
100+
clone(&r.Arguments.Named[i].Arg)
101+
}
102+
103+
case *ApplyBrace:
104+
r := new(ApplyBrace)
105+
*astPtr = r
106+
*r = *node
107+
clone(&r.Left)
108+
clone(&r.Right)
109+
110+
case *Array:
111+
r := new(Array)
112+
*astPtr = r
113+
*r = *node
114+
r.Elements = append(make(Nodes, 0), r.Elements...)
115+
for i := range r.Elements {
116+
clone(&r.Elements[i])
117+
}
118+
119+
case *ArrayComp:
120+
r := new(ArrayComp)
121+
*astPtr = r
122+
*r = *node
123+
clone(&r.Body)
124+
cloneForSpec(&r.Spec)
125+
126+
case *Assert:
127+
r := new(Assert)
128+
*astPtr = r
129+
*r = *node
130+
clone(&r.Cond)
131+
clone(&r.Message)
132+
clone(&r.Rest)
133+
134+
case *Binary:
135+
r := new(Binary)
136+
*astPtr = r
137+
*r = *node
138+
clone(&r.Left)
139+
clone(&r.Right)
140+
141+
case *Conditional:
142+
r := new(Conditional)
143+
*astPtr = r
144+
*r = *node
145+
clone(&r.Cond)
146+
clone(&r.BranchTrue)
147+
clone(&r.BranchFalse)
148+
149+
case *Dollar:
150+
r := new(Dollar)
151+
*astPtr = r
152+
*r = *node
153+
154+
case *Error:
155+
r := new(Error)
156+
*astPtr = r
157+
*r = *node
158+
clone(&r.Expr)
159+
160+
case *Function:
161+
r := new(Function)
162+
*astPtr = r
163+
*r = *node
164+
cloneParameters(&r.Parameters)
165+
clone(&r.Body)
166+
167+
case *Import:
168+
r := new(Import)
169+
*astPtr = r
170+
*r = *node
171+
r.File = new(LiteralString)
172+
*r.File = *node.File
173+
174+
case *ImportStr:
175+
r := new(ImportStr)
176+
*astPtr = r
177+
*r = *node
178+
r.File = new(LiteralString)
179+
*r.File = *node.File
180+
181+
case *Index:
182+
r := new(Index)
183+
*astPtr = r
184+
*r = *node
185+
clone(&r.Target)
186+
clone(&r.Index)
187+
188+
case *Slice:
189+
r := new(Slice)
190+
*astPtr = r
191+
*r = *node
192+
clone(&r.Target)
193+
clone(&r.BeginIndex)
194+
clone(&r.EndIndex)
195+
clone(&r.Step)
196+
197+
case *Local:
198+
r := new(Local)
199+
*astPtr = r
200+
*r = *node
201+
r.Binds = append(make(LocalBinds, 0), r.Binds...)
202+
for i := range r.Binds {
203+
if r.Binds[i].Fun != nil {
204+
r.Binds[i].Fun = Clone(r.Binds[i].Fun).(*Function)
205+
}
206+
clone(&r.Binds[i].Body)
207+
}
208+
clone(&r.Body)
209+
210+
case *LiteralBoolean:
211+
r := new(LiteralBoolean)
212+
*astPtr = r
213+
*r = *node
214+
215+
case *LiteralNull:
216+
r := new(LiteralNull)
217+
*astPtr = r
218+
*r = *node
219+
220+
case *LiteralNumber:
221+
r := new(LiteralNumber)
222+
*astPtr = r
223+
*r = *node
224+
225+
case *LiteralString:
226+
r := new(LiteralString)
227+
*astPtr = r
228+
*r = *node
229+
230+
case *Object:
231+
r := new(Object)
232+
*astPtr = r
233+
*r = *node
234+
r.Fields = append(make(ObjectFields, 0), r.Fields...)
235+
for i := range r.Fields {
236+
cloneField(&r.Fields[i])
237+
}
238+
239+
case *DesugaredObject:
240+
r := new(DesugaredObject)
241+
*astPtr = r
242+
*r = *node
243+
r.Fields = append(make(DesugaredObjectFields, 0), r.Fields...)
244+
for i := range r.Fields {
245+
cloneDesugaredField(&r.Fields[i])
246+
}
247+
248+
case *ObjectComp:
249+
r := new(ObjectComp)
250+
*astPtr = r
251+
*r = *node
252+
r.Fields = append(make(ObjectFields, 0), r.Fields...)
253+
for i := range r.Fields {
254+
cloneField(&r.Fields[i])
255+
}
256+
cloneForSpec(&r.Spec)
257+
258+
case *Parens:
259+
r := new(Parens)
260+
*astPtr = r
261+
*r = *node
262+
clone(&r.Inner)
263+
264+
case *Self:
265+
r := new(Self)
266+
*astPtr = r
267+
*r = *node
268+
269+
case *SuperIndex:
270+
r := new(SuperIndex)
271+
*astPtr = r
272+
*r = *node
273+
clone(&r.Index)
274+
275+
case *InSuper:
276+
r := new(InSuper)
277+
*astPtr = r
278+
*r = *node
279+
clone(&r.Index)
280+
281+
case *Unary:
282+
r := new(Unary)
283+
*astPtr = r
284+
*r = *node
285+
clone(&r.Expr)
286+
287+
case *Var:
288+
r := new(Var)
289+
*astPtr = r
290+
*r = *node
291+
292+
default:
293+
panic(fmt.Sprintf("ast.Clone() does not recognize ast: %s", reflect.TypeOf(node)))
294+
}
295+
296+
cloneNodeBase(*astPtr)
297+
}
298+
299+
func Clone(astPtr Node) Node {
300+
clone(&astPtr)
301+
return astPtr
302+
}

desugarer.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,17 +123,17 @@ func desugarFields(location ast.LocationRange, fields *ast.ObjectFields, objLeve
123123

124124
// Remove object-level locals
125125
newFields := []ast.ObjectField{}
126-
var binds ast.LocalBinds
127-
for _, local := range *fields {
128-
if local.Kind != ast.ObjectLocal {
129-
continue
130-
}
131-
binds = append(binds, ast.LocalBind{Variable: *local.Id, Body: local.Expr2})
132-
}
133126
for _, field := range *fields {
134127
if field.Kind == ast.ObjectLocal {
135128
continue
136129
}
130+
var binds ast.LocalBinds
131+
for _, local := range *fields {
132+
if local.Kind != ast.ObjectLocal {
133+
continue
134+
}
135+
binds = append(binds, ast.LocalBind{Variable: *local.Id, Body: ast.Clone(local.Expr2)})
136+
}
137137
if len(binds) > 0 {
138138
field.Expr2 = &ast.Local{
139139
NodeBase: ast.NewNodeBaseLoc(*field.Expr2.Loc()),
@@ -294,7 +294,10 @@ func buildDesugaredObject(nodeBase ast.NodeBase, fields ast.ObjectFields) *ast.D
294294

295295
// Desugar Jsonnet expressions to reduce the number of constructs the rest of the implementation
296296
// needs to understand.
297-
297+
//
298+
// Note that despite the name, desugar() is not idempotent. String literals have their escape
299+
// codes translated to low-level characters during desugaring.
300+
//
298301
// Desugaring should happen immediately after parsing, i.e. before static analysis and execution.
299302
// Temporary variables introduced here should be prefixed with $ to ensure they do not clash with
300303
// variables used in user code.
@@ -443,13 +446,17 @@ func desugar(astPtr *ast.Node, objLevel int) (err error) {
443446
}
444447

445448
case *ast.Import:
449+
// desugar() is allowed to update the pointer to point to something else, but will never do
450+
// this for a LiteralString. We cannot simply do &node.File because the type is
451+
// **ast.LiteralString which is not compatible with *ast.Node.
446452
var file ast.Node = node.File
447453
err = desugar(&file, objLevel)
448454
if err != nil {
449455
return
450456
}
451457

452458
case *ast.ImportStr:
459+
// See comment in ast.Import.
453460
var file ast.Node = node.File
454461
err = desugar(&file, objLevel)
455462
if err != nil {

0 commit comments

Comments
 (0)