| 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 |