OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 | |
6 library js.safety; | |
7 | |
8 import "js.dart" as js; | |
9 | |
10 typedef bool JavaScriptExpressionPredicate(js.Expression expression); | |
11 | |
12 /// PlaceholderSafetyAnalysis determines which placeholders in a JavaScript | |
13 /// template may be replaced with an arbitrary expression. Placeholders may be | |
14 /// replaced with an arbitrary expression providied the template ensures the | |
15 /// placeholders are evaluated in the same left-to-right order with no | |
16 /// additional effects interleaved. | |
17 /// | |
18 /// The result is semi-conservative, giving reasonable results for many simple | |
19 /// JS fragments. The non-conservative part is the assumption that arithmetic | |
20 /// operators are used on 'good' operands that do not force arbitrary code to be | |
21 /// executed via conversions (valueOf() and toString() methods). | |
22 class PlaceholderSafetyAnalysis extends js.BaseVisitor<NativeThrowBehavior> { | |
asgerf
2015/12/08 11:40:37
Extends BaseVisitor<NativeThrowBehavior> but all t
sra1
2015/12/08 20:54:04
Done.
| |
23 final JavaScriptExpressionPredicate isNullableInput; | |
24 int nextPosition = 0; | |
25 int maxSafePosition = -1; | |
26 bool safe = true; | |
27 | |
28 // We do a crude abstract interpretation to find operations that might throw | |
29 // exceptions. The possible values of expressions are represented by | |
30 // integers. Small non-negative integers 0, 1, 2, ... represent the values of | |
31 // the placeholders. Other values are: | |
32 static const int NONNULL_VALUE = -1; // Unknown but not null. | |
33 static const int UNKNOWN_VALUE = -2; // Unknown and might be null. | |
34 | |
35 PlaceholderSafetyAnalysis._(this.isNullableInput); | |
36 | |
37 /// Returns the number of placeholders that can be substituted into the | |
38 /// template AST [node] without changing the order of observable effects. | |
39 /// [isNullableInput] is a function that takes the 0-based index of a | |
40 /// placeholder and returns `true` if expression at run time may be null, and | |
41 /// `false` if the value is never null. | |
42 static int analyze(js.Node node, | |
43 JavaScriptExpressionPredicate isNullableInput) { | |
44 PlaceholderSafetyAnalysis analysis = | |
45 new PlaceholderSafetyAnalysis._(isNullableInput); | |
46 analysis.visit(node); | |
47 return analysis.maxSafePosition + 1; | |
48 } | |
49 | |
50 bool canBeNull(int value) { | |
51 if (value == NONNULL_VALUE) return false; | |
52 if (value == UNKNOWN_VALUE) return true; | |
53 return isNullableInput(value); | |
54 } | |
55 | |
56 int unsafe(int value) { | |
57 safe = false; | |
58 return value; | |
59 } | |
60 | |
61 int visit(js.Node node) { | |
62 return node.accept(this); | |
63 } | |
64 | |
65 int visitNode(js.Node node) { | |
66 safe = false; | |
67 print(node); | |
asgerf
2015/12/08 11:40:37
Renegade print statement
sra1
2015/12/08 20:54:04
Done.
| |
68 super.visitNode(node); | |
69 return UNKNOWN_VALUE; | |
70 } | |
71 | |
72 int visitLiteralNull(js.LiteralNull node) { | |
73 return UNKNOWN_VALUE; | |
74 } | |
75 | |
76 int visitLiteral(js.Literal node) { | |
77 return NONNULL_VALUE; | |
78 } | |
79 | |
80 int handleInterpolatedNode(js.InterpolatedNode node) { | |
81 assert(node.isPositional); | |
82 int position = nextPosition++; | |
83 if (safe) maxSafePosition = position; | |
84 return position; | |
85 } | |
86 | |
87 int visitInterpolatedExpression(js.InterpolatedExpression node) { | |
88 return handleInterpolatedNode(node); | |
89 } | |
90 | |
91 int visitInterpolatedLiteral(js.InterpolatedLiteral node) { | |
92 return handleInterpolatedNode(node); | |
93 } | |
94 | |
95 int visitInterpolatedSelector(js.InterpolatedSelector node) { | |
96 return handleInterpolatedNode(node); | |
97 } | |
98 | |
99 int visitInterpolatedStatement(js.InterpolatedStatement node) { | |
100 safe = false; | |
asgerf
2015/12/08 11:40:37
Why safe = false here?
sra1
2015/12/08 20:54:04
Not sure why. It would occur only in the body of a
| |
101 return handleInterpolatedNode(node); | |
102 } | |
103 | |
104 int visitInterpolatedDeclaration(js.InterpolatedDeclaration node) { | |
105 safe = false; | |
106 return handleInterpolatedNode(node); | |
107 } | |
108 | |
109 int visitObjectInitializer(js.ObjectInitializer node) { | |
110 for (js.Property property in node.properties) { | |
111 visit(property); | |
112 } | |
113 return NONNULL_VALUE; | |
114 } | |
115 | |
116 int visitProperty(js.Property node) { | |
117 visit(node.name); | |
118 visit(node.value); | |
119 return UNKNOWN_VALUE; | |
120 } | |
121 | |
122 int visitAccess(js.PropertyAccess node) { | |
123 int first = visit(node.receiver); | |
124 int second = visit(node.selector); | |
125 if (canBeNull(first)) safe = false; | |
asgerf
2015/12/08 11:40:37
If the JS fragment is annotated with throws:never
sra1
2015/12/08 20:54:04
Acknowledged.
| |
126 return UNKNOWN_VALUE; | |
127 } | |
128 | |
129 int visitAssignment(js.Assignment node) { | |
130 js.Expression target = node.leftHandSide; | |
131 | |
132 int leftToRight() { | |
133 visit(target); | |
134 visit(node.value); | |
135 return UNKNOWN_VALUE; | |
136 } | |
137 | |
138 if (target is js.InterpolatedNode) { | |
139 // A bare interpolated expression should not be the LHS of an assignment. | |
140 safe = false; | |
141 return leftToRight(); | |
142 } | |
143 | |
144 if (node.op == null) { | |
145 if (target is js.VariableReference) { | |
146 int value = visit(node.value); | |
147 // Assignment could change a global or cause a ReferenceError. | |
148 safe = false; | |
149 return value; | |
150 } else if (target is js.PropertyAccess) { | |
151 // "a.x = b.y" detects a null `b` before a null `a`. | |
asgerf
2015/12/08 11:40:37
It seems the relevant information is that the RHS
sra1
2015/12/08 20:54:04
I have clarified the comment (I hope).
| |
152 int receiver = visit(target.receiver); | |
153 int selector = visit(target.selector); | |
154 int value = visit(node.value); | |
155 if (canBeNull(receiver)) safe = false; | |
156 return value; | |
157 } else { | |
158 // | |
asgerf
2015/12/08 11:40:37
Blank comment
sra1
2015/12/08 20:54:04
Done.
| |
159 safe = false; | |
160 return leftToRight(); | |
161 } | |
162 } | |
163 return leftToRight(); | |
164 } | |
165 | |
166 int visitCall(js.Call node) { | |
167 visit(node.target); | |
168 node.arguments.forEach(visit); | |
169 return unsafe(UNKNOWN_VALUE); | |
170 } | |
171 | |
172 int visitNew(js.New node) { | |
173 // TODO(sra): `new Array(x)` where `x` is a small number. | |
174 visitCall(node); | |
175 return unsafe(NONNULL_VALUE); | |
176 } | |
177 | |
178 int visitBinary(js.Binary node) { | |
179 switch (node.op) { | |
180 // We make the non-conservative assumption that these operations are not | |
181 // used in ways that force calling arbitrary code via valueOf() or | |
182 // toString(). | |
183 case "*": | |
184 case "/": | |
185 case "%": | |
186 case "+": | |
187 case "-": | |
188 case "<<": | |
189 case ">>": | |
190 case ">>>": | |
191 case "<": | |
192 case ">": | |
193 case "<=": | |
194 case ">=": | |
195 case "==": | |
196 case "===": | |
197 case "!=": | |
198 case "!==": | |
199 case "&": | |
200 case "^": | |
201 case "|": | |
202 int left = visit(node.left); | |
203 int right = visit(node.right); | |
204 return UNKNOWN_VALUE; | |
205 | |
206 case ',': | |
207 int left = visit(node.left); | |
208 int right = visit(node.right); | |
209 return right; | |
210 | |
211 case "&&": | |
212 case "||": | |
213 int left = visit(node.left); | |
214 safe = false; | |
asgerf
2015/12/08 11:40:37
Just an observation.
If there are no placeholders
sra1
2015/12/08 20:54:04
Acknowledged.
| |
215 int right = visit(node.right); | |
216 return UNKNOWN_VALUE; | |
217 | |
218 case "instanceof": | |
219 case "in": | |
220 int left = visit(node.left); | |
221 int right = visit(node.right); | |
222 return UNKNOWN_VALUE; | |
223 | |
224 default: | |
225 return unsafe(UNKNOWN_VALUE); | |
226 } | |
227 } | |
228 | |
229 int visitThrow(js.Throw node) { | |
230 visit(node.expression); | |
231 return unsafe(UNKNOWN_VALUE); | |
232 } | |
233 | |
234 int visitPrefix(js.Prefix node) { | |
235 if (node.op == 'typeof') { | |
236 // "typeof a" does not dereference "a". | |
237 if (node.argument is js.VariableUse) return NONNULL_VALUE; | |
238 } | |
239 | |
240 visit(node.argument); | |
241 | |
242 switch (node.op) { | |
243 case '+': | |
244 case '-': | |
245 case '!': | |
246 case '~': | |
247 // Non-conservative assumption that these operators are used on values | |
248 // that do not call arbitrary code via valueOf() or toString(). | |
249 return NONNULL_VALUE; | |
asgerf
2015/12/08 11:40:37
The binary operators return UNKNOWN_VALUE. Is ther
sra1
2015/12/08 20:54:04
No reason. They all return numbers, strings, bool
| |
250 | |
251 case 'typeof': | |
252 return NONNULL_VALUE; // Always a string. | |
253 | |
254 case 'void': | |
255 return UNKNOWN_VALUE; | |
256 | |
257 default: | |
asgerf
2015/12/08 11:40:37
What about prefix ++ and --?
sra1
2015/12/08 20:54:04
Done.
| |
258 safe = false; | |
259 return UNKNOWN_VALUE; | |
260 } | |
261 } | |
262 | |
263 int visitPostfix(js.Postfix node) { | |
264 assert(node.op == '--' || node.op == '++'); | |
265 visit(node.argument); | |
266 return UNKNOWN_VALUE; | |
267 } | |
268 | |
269 int visitVariableUse(js.VariableUse node) { | |
270 // We could get a ReferenceError unless the variable is in scope. For JS | |
271 // fragments, the only use of VariableUse outside a `function(){...}` should | |
272 // be for global references. Certain global names are almost certainly not | |
273 // reference errors, e.g 'Array'. | |
asgerf
2015/12/08 11:40:37
I think we could justify adding window and self to
sra1
2015/12/08 20:54:04
Done.
| |
274 switch (node.name) { | |
275 case 'Array': | |
276 case 'Date': | |
277 case 'Function': | |
278 case 'Object': | |
279 return NONNULL_VALUE; | |
280 default: | |
281 return unsafe(UNKNOWN_VALUE); | |
282 } | |
283 } | |
284 | |
285 int visitFun(js.Fun node) { | |
286 bool oldSafe = safe; | |
287 int oldNextPosition = nextPosition; | |
288 visit(node.body); | |
289 // Creating a function has no effect on order unless there are embedded | |
290 // placeholders. | |
291 safe = (nextPosition == oldNextPosition) && oldSafe; | |
292 } | |
293 } | |
OLD | NEW |