OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 | |
6 library js.safety; | 5 library js.safety; |
7 | 6 |
8 import "js.dart" as js; | 7 import "js.dart" as js; |
9 | 8 |
10 typedef bool PositionPredicate(int position); | 9 typedef bool PositionPredicate(int position); |
11 | 10 |
12 /// PlaceholderSafetyAnalysis determines which placeholders in a JavaScript | 11 /// PlaceholderSafetyAnalysis determines which placeholders in a JavaScript |
13 /// template may be replaced with an arbitrary expression. Placeholders may be | 12 /// template may be replaced with an arbitrary expression. Placeholders may be |
14 /// replaced with an arbitrary expression providied the template ensures the | 13 /// replaced with an arbitrary expression providied the template ensures the |
15 /// placeholders are evaluated in the same left-to-right order with no | 14 /// placeholders are evaluated in the same left-to-right order with no |
16 /// additional effects interleaved. | 15 /// additional effects interleaved. |
17 /// | 16 /// |
18 /// The result is semi-conservative, giving reasonable results for many simple | 17 /// The result is semi-conservative, giving reasonable results for many simple |
19 /// JS fragments. The non-conservative part is the assumption that arithmetic | 18 /// 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 | 19 /// operators are used on 'good' operands that do not force arbitrary code to be |
21 /// executed via conversions (valueOf() and toString() methods). | 20 /// executed via conversions (valueOf() and toString() methods). |
22 class PlaceholderSafetyAnalysis extends js.BaseVisitor<int> { | 21 class PlaceholderSafetyAnalysis extends js.BaseVisitor<int> { |
23 final PositionPredicate isNullableInput; | 22 final PositionPredicate isNullableInput; |
24 int nextPosition = 0; | 23 int nextPosition = 0; |
25 int maxSafePosition = -1; | 24 int maxSafePosition = -1; |
26 bool safe = true; | 25 bool safe = true; |
27 | 26 |
28 // We do a crude abstract interpretation to find operations that might throw | 27 // We do a crude abstract interpretation to find operations that might throw |
29 // exceptions. The possible values of expressions are represented by | 28 // exceptions. The possible values of expressions are represented by |
30 // integers. Small non-negative integers 0, 1, 2, ... represent the values of | 29 // integers. Small non-negative integers 0, 1, 2, ... represent the values of |
31 // the placeholders. Other values are: | 30 // the placeholders. Other values are: |
32 static const int NONNULL_VALUE = -1; // Unknown but not null. | 31 static const int NONNULL_VALUE = -1; // Unknown but not null. |
33 static const int UNKNOWN_VALUE = -2; // Unknown and might be null. | 32 static const int UNKNOWN_VALUE = -2; // Unknown and might be null. |
34 | 33 |
35 PlaceholderSafetyAnalysis._(this.isNullableInput); | 34 PlaceholderSafetyAnalysis._(this.isNullableInput); |
36 | 35 |
37 /// Returns the number of placeholders that can be substituted into the | 36 /// Returns the number of placeholders that can be substituted into the |
38 /// template AST [node] without changing the order of observable effects. | 37 /// template AST [node] without changing the order of observable effects. |
39 /// [isNullableInput] is a function that takes the 0-based index of a | 38 /// [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 | 39 /// placeholder and returns `true` if expression at run time may be null, and |
41 /// `false` if the value is never null. | 40 /// `false` if the value is never null. |
42 static int analyze(js.Node node, PositionPredicate isNullableInput) { | 41 static int analyze(js.Node node, PositionPredicate isNullableInput) { |
43 PlaceholderSafetyAnalysis analysis = | 42 PlaceholderSafetyAnalysis analysis = |
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
140 visit(left); | 139 visit(left); |
141 visit(right); | 140 visit(right); |
142 return UNKNOWN_VALUE; | 141 return UNKNOWN_VALUE; |
143 } | 142 } |
144 | 143 |
145 if (left is js.InterpolatedNode) { | 144 if (left is js.InterpolatedNode) { |
146 // A bare interpolated expression should not be the LHS of an assignment. | 145 // A bare interpolated expression should not be the LHS of an assignment. |
147 safe = false; | 146 safe = false; |
148 return leftToRight(); | 147 return leftToRight(); |
149 } | 148 } |
150 | 149 |
151 // Assignment operators dereference the LHS before evaluating the RHS. | 150 // Assignment operators dereference the LHS before evaluating the RHS. |
152 if (node.op != null) return leftToRight(); | 151 if (node.op != null) return leftToRight(); |
153 | 152 |
154 // Assignment (1) evaluates the LHS as a Reference `lval`, (2) evaluates the | 153 // Assignment (1) evaluates the LHS as a Reference `lval`, (2) evaluates the |
155 // RHS as a value, (3) dereferences the `lval` in PutValue. | 154 // RHS as a value, (3) dereferences the `lval` in PutValue. |
156 if (left is js.VariableReference) { | 155 if (left is js.VariableReference) { |
157 int value = visit(right); | 156 int value = visit(right); |
158 // Assignment could change an observed global or cause a ReferenceError. | 157 // Assignment could change an observed global or cause a ReferenceError. |
159 safe = false; | 158 safe = false; |
160 return value; | 159 return value; |
161 } | 160 } |
162 if (left is js.PropertyAccess) { | 161 if (left is js.PropertyAccess) { |
163 // "a.b.x = c.y" gives a TypeError for null values in this order: `a`, | 162 // "a.b.x = c.y" gives a TypeError for null values in this order: `a`, |
164 // `c`, `a.b`. | 163 // `c`, `a.b`. |
165 int receiver = visit(left.receiver); | 164 int receiver = visit(left.receiver); |
166 int selector = visit(left.selector); | 165 int selector = visit(left.selector); |
167 int value = visit(right); | 166 int value = visit(right); |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
206 case ">=": | 205 case ">=": |
207 case "==": | 206 case "==": |
208 case "===": | 207 case "===": |
209 case "!=": | 208 case "!=": |
210 case "!==": | 209 case "!==": |
211 case "&": | 210 case "&": |
212 case "^": | 211 case "^": |
213 case "|": | 212 case "|": |
214 int left = visit(node.left); | 213 int left = visit(node.left); |
215 int right = visit(node.right); | 214 int right = visit(node.right); |
216 return NONNULL_VALUE; // Number, String, Boolean. | 215 return NONNULL_VALUE; // Number, String, Boolean. |
217 | 216 |
218 case ',': | 217 case ',': |
219 int left = visit(node.left); | 218 int left = visit(node.left); |
220 int right = visit(node.right); | 219 int right = visit(node.right); |
221 return right; | 220 return right; |
222 | 221 |
223 case "&&": | 222 case "&&": |
224 case "||": | 223 case "||": |
225 int left = visit(node.left); | 224 int left = visit(node.left); |
226 // TODO(sra): Might be safe, e.g. "x || 0". | 225 // TODO(sra): Might be safe, e.g. "x || 0". |
(...skipping 24 matching lines...) Expand all Loading... |
251 int visitThrow(js.Throw node) { | 250 int visitThrow(js.Throw node) { |
252 visit(node.expression); | 251 visit(node.expression); |
253 return unsafe(UNKNOWN_VALUE); | 252 return unsafe(UNKNOWN_VALUE); |
254 } | 253 } |
255 | 254 |
256 int visitPrefix(js.Prefix node) { | 255 int visitPrefix(js.Prefix node) { |
257 if (node.op == 'typeof') { | 256 if (node.op == 'typeof') { |
258 // "typeof a" first evaluates to a Reference. If the Reference is to a | 257 // "typeof a" first evaluates to a Reference. If the Reference is to a |
259 // variable that is not present, "undefined" is returned without | 258 // variable that is not present, "undefined" is returned without |
260 // dereferencing. | 259 // dereferencing. |
261 if (node.argument is js.VariableUse) return NONNULL_VALUE; // A string. | 260 if (node.argument is js.VariableUse) return NONNULL_VALUE; // A string. |
262 } | 261 } |
263 | 262 |
264 visit(node.argument); | 263 visit(node.argument); |
265 | 264 |
266 switch (node.op) { | 265 switch (node.op) { |
267 case '+': | 266 case '+': |
268 case '-': | 267 case '-': |
269 case '!': | 268 case '!': |
270 case '~': | 269 case '~': |
271 // Non-conservative assumption that these operators are used on values | 270 // Non-conservative assumption that these operators are used on values |
272 // that do not call arbitrary code via valueOf() or toString(). | 271 // that do not call arbitrary code via valueOf() or toString(). |
273 return NONNULL_VALUE; | 272 return NONNULL_VALUE; |
274 | 273 |
275 case 'typeof': | 274 case 'typeof': |
276 return NONNULL_VALUE; // Always a string. | 275 return NONNULL_VALUE; // Always a string. |
277 | 276 |
278 case 'void': | 277 case 'void': |
279 return UNKNOWN_VALUE; | 278 return UNKNOWN_VALUE; |
280 | 279 |
281 case '--': | 280 case '--': |
282 case '++': | 281 case '++': |
283 return NONNULL_VALUE; // Always a number. | 282 return NONNULL_VALUE; // Always a number. |
284 | 283 |
285 default: | 284 default: |
286 safe = false; | 285 safe = false; |
287 return UNKNOWN_VALUE; | 286 return UNKNOWN_VALUE; |
288 } | 287 } |
289 } | 288 } |
290 | 289 |
291 int visitPostfix(js.Postfix node) { | 290 int visitPostfix(js.Postfix node) { |
292 assert(node.op == '--' || node.op == '++'); | 291 assert(node.op == '--' || node.op == '++'); |
293 visit(node.argument); | 292 visit(node.argument); |
294 return NONNULL_VALUE; // Always a number, even for "(a=null, a++)". | 293 return NONNULL_VALUE; // Always a number, even for "(a=null, a++)". |
295 } | 294 } |
296 | 295 |
297 int visitVariableUse(js.VariableUse node) { | 296 int visitVariableUse(js.VariableUse node) { |
298 // We could get a ReferenceError unless the variable is in scope. For JS | 297 // We could get a ReferenceError unless the variable is in scope. For JS |
299 // fragments, the only use of VariableUse outside a `function(){...}` should | 298 // fragments, the only use of VariableUse outside a `function(){...}` should |
300 // be for global references. Certain global names are almost certainly not | 299 // be for global references. Certain global names are almost certainly not |
301 // reference errors, e.g 'Array'. | 300 // reference errors, e.g 'Array'. |
302 switch (node.name) { | 301 switch (node.name) { |
303 case 'Array': | 302 case 'Array': |
304 case 'Date': | 303 case 'Date': |
(...skipping 14 matching lines...) Expand all Loading... |
319 int visitFun(js.Fun node) { | 318 int visitFun(js.Fun node) { |
320 bool oldSafe = safe; | 319 bool oldSafe = safe; |
321 int oldNextPosition = nextPosition; | 320 int oldNextPosition = nextPosition; |
322 visit(node.body); | 321 visit(node.body); |
323 // Creating a function has no effect on order unless there are embedded | 322 // Creating a function has no effect on order unless there are embedded |
324 // placeholders. | 323 // placeholders. |
325 safe = (nextPosition == oldNextPosition) && oldSafe; | 324 safe = (nextPosition == oldNextPosition) && oldSafe; |
326 return NONNULL_VALUE; | 325 return NONNULL_VALUE; |
327 } | 326 } |
328 } | 327 } |
OLD | NEW |