Chromium Code Reviews| 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 |