blob: 7dc7d70f32e36a24edcc31607e8989fbf5f27e33 [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of native;
class SideEffectsVisitor extends js.BaseVisitor {
final SideEffects sideEffects;
SideEffectsVisitor(this.sideEffects);
void visit(js.Node node) {
node.accept(this);
}
void visitLiteralExpression(js.LiteralExpression node) {
sideEffects.setAllSideEffects();
sideEffects.setDependsOnSomething();
node.visitChildren(this);
}
void visitLiteralStatement(js.LiteralStatement node) {
sideEffects.setAllSideEffects();
sideEffects.setDependsOnSomething();
node.visitChildren(this);
}
void visitAssignment(js.Assignment node) {
sideEffects.setChangesStaticProperty();
sideEffects.setChangesInstanceProperty();
sideEffects.setChangesIndex();
node.visitChildren(this);
}
void visitVariableInitialization(js.VariableInitialization node) {
node.visitChildren(this);
}
void visitCall(js.Call node) {
sideEffects.setAllSideEffects();
sideEffects.setDependsOnSomething();
node.visitChildren(this);
}
void visitBinary(js.Binary node) {
node.visitChildren(this);
}
void visitThrow(js.Throw node) {
// TODO(ngeoffray): Incorporate a mayThrow flag in the
// [SideEffects] class.
sideEffects.setAllSideEffects();
}
void visitNew(js.New node) {
sideEffects.setAllSideEffects();
sideEffects.setDependsOnSomething();
node.visitChildren(this);
}
void visitPrefix(js.Prefix node) {
if (node.op == 'delete') {
sideEffects.setChangesStaticProperty();
sideEffects.setChangesInstanceProperty();
sideEffects.setChangesIndex();
}
node.visitChildren(this);
}
void visitVariableUse(js.VariableUse node) {
sideEffects.setDependsOnStaticPropertyStore();
}
void visitPostfix(js.Postfix node) {
node.visitChildren(this);
}
void visitAccess(js.PropertyAccess node) {
sideEffects.setDependsOnIndexStore();
sideEffects.setDependsOnInstancePropertyStore();
sideEffects.setDependsOnStaticPropertyStore();
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 '!':
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);
}
}