| 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);
|
| + }
|
| +}
|
|
|