Index: pkg/compiler/lib/src/native/js.dart |
diff --git a/pkg/compiler/lib/src/native/js.dart b/pkg/compiler/lib/src/native/js.dart |
index 1cbe8da5c8fb978d81a05f35d7e040febc8b2e19..2203b55f19394d1e10bb928db11461a0dee10283 100644 |
--- a/pkg/compiler/lib/src/native/js.dart |
+++ b/pkg/compiler/lib/src/native/js.dart |
@@ -81,3 +81,187 @@ class SideEffectsVisitor extends js.BaseVisitor { |
node.visitChildren(this); |
} |
} |
+ |
+ |
+/// ThrowBehaviorVisitor generates a NativeThrowBehavior describing the |
+/// exception behavior of a JavaScript expression. |
+/// |
+/// The result is semi-conservative, giving reasonable results for many simple |
+/// JS fragments. The non-conservative part is the assumption that binary |
+/// operators are used on 'good' operands that do not force arbirary code to be |
+/// executed via conversions (valueOf() and toString() methods). |
+/// |
+/// In many cases a JS fragment has more precise behavior. In these cases the |
+/// behavior should be described as a property of the JS fragment. For example, |
+/// Object.keys(#) has a TypeError on null / undefined, which can only be known |
+/// in the calling context. |
+/// |
+class ThrowBehaviorVisitor extends js.BaseVisitor<NativeThrowBehavior> { |
+ |
+ ThrowBehaviorVisitor(); |
+ |
+ NativeThrowBehavior analyze(js.Node node) { |
+ return visit(node); |
+ } |
+ |
+ // TODO(sra): Add [sequence] functionality to NativeThrowBehavior. |
+ /// Returns the combined behavior of sequential execution of code having |
+ /// behavior [first] followed by code having behavior [second]. |
+ static NativeThrowBehavior sequence(NativeThrowBehavior first, |
+ NativeThrowBehavior second) { |
+ if (first == NativeThrowBehavior.MUST) return first; |
+ if (second == NativeThrowBehavior.MUST) return second; |
+ if (second == NativeThrowBehavior.NEVER) return first; |
+ if (first == NativeThrowBehavior.NEVER) return second; |
+ // Both are one of MAY or MAY_THROW_ONLY_ON_FIRST_ARGUMENT_ACCESS. |
+ return NativeThrowBehavior.MAY; |
+ } |
+ |
+ // TODO(sra): Add [choice] functionality to NativeThrowBehavior. |
+ /// Returns the combined behavior of a choice between two paths with behaviors |
+ /// [first] and [second]. |
+ static NativeThrowBehavior choice(NativeThrowBehavior first, |
+ NativeThrowBehavior second) { |
+ if (first == second) return first; // Both paths have same behaviour. |
+ return NativeThrowBehavior.MAY; |
+ } |
+ |
+ NativeThrowBehavior visit(js.Node node) { |
+ return node.accept(this); |
+ } |
+ |
+ NativeThrowBehavior visitNode(js.Node node) { |
+ return NativeThrowBehavior.MAY; |
+ } |
+ |
+ NativeThrowBehavior visitLiteral(js.Literal node) { |
+ return NativeThrowBehavior.NEVER; |
+ } |
+ |
+ NativeThrowBehavior visitInterpolatedExpression(js.InterpolatedNode node) { |
+ return NativeThrowBehavior.NEVER; |
+ } |
+ |
+ NativeThrowBehavior visitInterpolatedSelector(js.InterpolatedNode node) { |
+ return NativeThrowBehavior.NEVER; |
+ } |
+ |
+ NativeThrowBehavior visitObjectInitializer(js.ObjectInitializer node) { |
+ NativeThrowBehavior result = NativeThrowBehavior.NEVER; |
+ for (js.Property property in node.properties) { |
+ result = sequence(result, visit(property)); |
+ } |
+ return result; |
+ } |
+ |
+ NativeThrowBehavior visitProperty(js.Property node) { |
+ return sequence(visit(node.name), visit(node.value)); |
+ } |
+ |
+ NativeThrowBehavior visitAssignment(js.Assignment node) { |
+ // TODO(sra): Can we make "#.p = #" be null(1)? |
+ return NativeThrowBehavior.MAY; |
+ } |
+ |
+ NativeThrowBehavior visitCall(js.Call node) { |
+ return NativeThrowBehavior.MAY; |
+ } |
+ |
+ NativeThrowBehavior visitNew(js.New node) { |
+ // TODO(sra): `new Array(x)` where `x` is a small number. |
+ return NativeThrowBehavior.MAY; |
+ } |
+ |
+ NativeThrowBehavior visitBinary(js.Binary node) { |
+ NativeThrowBehavior left = visit(node.left); |
+ NativeThrowBehavior right = visit(node.right); |
+ switch (node.op) { |
+ // We make the non-conservative assumption that these operations are not |
+ // used in ways that force calling arbitrary code via valueOf or |
+ // toString(). |
+ case "*": |
+ case "/": |
+ case "%": |
+ case "+": |
+ case "-": |
+ case "<<": |
+ case ">>": |
+ case ">>>": |
+ case "<": |
+ case ">": |
+ case "<=": |
+ case ">=": |
+ case "==": |
+ case "===": |
+ case "!=": |
+ case "!==": |
+ case "&": |
+ case "^": |
+ case "|": |
+ return sequence(left, right); |
+ |
+ case ',': |
+ return sequence(left, right); |
+ |
+ case "&&": |
+ case "||": |
+ return choice(left, sequence(left, right)); |
+ |
+ case "instanceof": |
+ case "in": |
+ default: |
+ return NativeThrowBehavior.MAY; |
+ } |
+ } |
+ |
+ NativeThrowBehavior visitThrow(js.Throw node) { |
+ return NativeThrowBehavior.MUST; |
+ } |
+ |
+ NativeThrowBehavior visitPrefix(js.Prefix node) { |
+ if (node.op == 'typeof' && node.argument is js.VariableUse) |
+ return NativeThrowBehavior.NEVER; |
+ NativeThrowBehavior result = visit(node.argument); |
+ switch (node.op) { |
+ case '!': |
+ case '~': |
+ case 'void': |
+ case 'typeof': |
+ return result; |
+ default: |
+ return NativeThrowBehavior.MAY; |
+ } |
+ } |
+ |
+ NativeThrowBehavior visitVariableUse(js.VariableUse node) { |
+ // We could get a ReferenceError unless the variable is in scope. The AST |
+ // could distinguish in-scope and out-of scope references. For JS fragments, |
+ // the only use of VariableUse should be for global references. Certain |
+ // global names are almost certainly not reference errors, e.g 'Array'. |
+ switch (node.name) { |
+ case 'Array': |
+ case 'Object': |
+ return NativeThrowBehavior.NEVER; |
+ default: |
+ return NativeThrowBehavior.MAY; |
+ } |
+ } |
+ |
+ NativeThrowBehavior visitAccess(js.PropertyAccess node) { |
+ // TODO(sra): We need a representation where the nsm guard behaviour is |
+ // maintained when combined with other throwing behaviour. |
+ js.Node receiver = node.receiver; |
+ NativeThrowBehavior first = visit(receiver); |
+ NativeThrowBehavior second = visit(node.selector); |
+ |
+ if (receiver is js.InterpolatedExpression && |
+ receiver.isPositional && |
+ receiver.nameOrPosition == 0) { |
+ first = NativeThrowBehavior.MAY_THROW_ONLY_ON_FIRST_ARGUMENT_ACCESS; |
+ } else { |
+ first = NativeThrowBehavior.MAY; |
+ } |
+ |
+ return sequence(first, second); |
+ } |
+} |