|  | // 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. | 
|  |  | 
|  | import '../js/js.dart' as js; | 
|  | import '../universe/side_effects.dart' show SideEffects; | 
|  | import 'native_throw_behavior.dart' show NativeThrowBehavior; | 
|  |  | 
|  | class HasCapturedPlaceholders extends js.BaseVisitorVoid { | 
|  | HasCapturedPlaceholders._(); | 
|  |  | 
|  | static bool check(js.Node node) { | 
|  | HasCapturedPlaceholders visitor = HasCapturedPlaceholders._(); | 
|  | node.accept(visitor); | 
|  | return visitor.found; | 
|  | } | 
|  |  | 
|  | int enclosingFunctions = 0; | 
|  | bool found = false; | 
|  |  | 
|  | @override | 
|  | void visitFunctionExpression(js.FunctionExpression node) { | 
|  | ++enclosingFunctions; | 
|  | node.visitChildren(this); | 
|  | --enclosingFunctions; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitInterpolatedNode(js.InterpolatedNode node) { | 
|  | if (enclosingFunctions > 0) { | 
|  | found = true; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class SideEffectsVisitor extends js.BaseVisitorVoid { | 
|  | final SideEffects sideEffects; | 
|  | SideEffectsVisitor(this.sideEffects); | 
|  |  | 
|  | void visit(js.Node node) { | 
|  | node.accept(this); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitLiteralExpression(js.LiteralExpression node) { | 
|  | sideEffects.setAllSideEffects(); | 
|  | sideEffects.setDependsOnSomething(); | 
|  | node.visitChildren(this); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitLiteralStatement(js.LiteralStatement node) { | 
|  | sideEffects.setAllSideEffects(); | 
|  | sideEffects.setDependsOnSomething(); | 
|  | node.visitChildren(this); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitAssignment(js.Assignment node) { | 
|  | sideEffects.setChangesStaticProperty(); | 
|  | sideEffects.setChangesInstanceProperty(); | 
|  | sideEffects.setChangesIndex(); | 
|  | node.visitChildren(this); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitVariableInitialization(js.VariableInitialization node) { | 
|  | node.visitChildren(this); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitCall(js.Call node) { | 
|  | sideEffects.setAllSideEffects(); | 
|  | sideEffects.setDependsOnSomething(); | 
|  | node.visitChildren(this); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitBinary(js.Binary node) { | 
|  | node.visitChildren(this); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitThrow(js.Throw node) { | 
|  | // TODO(ngeoffray): Incorporate a mayThrow flag in the | 
|  | // [SideEffects] class. | 
|  | sideEffects.setAllSideEffects(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitNew(js.New node) { | 
|  | sideEffects.setAllSideEffects(); | 
|  | sideEffects.setDependsOnSomething(); | 
|  | node.visitChildren(this); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitPrefix(js.Prefix node) { | 
|  | if (node.op == 'delete') { | 
|  | sideEffects.setChangesStaticProperty(); | 
|  | sideEffects.setChangesInstanceProperty(); | 
|  | sideEffects.setChangesIndex(); | 
|  | } | 
|  | node.visitChildren(this); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitVariableUse(js.VariableUse node) { | 
|  | sideEffects.setDependsOnStaticPropertyStore(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitPostfix(js.Postfix node) { | 
|  | node.visitChildren(this); | 
|  | } | 
|  |  | 
|  | @override | 
|  | 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 arbitrary 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); | 
|  | } | 
|  |  | 
|  | /// 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, | 
|  | ) { | 
|  | return first.then(second); | 
|  | } | 
|  |  | 
|  | /// Returns the combined behavior of a choice between two paths with behaviors | 
|  | /// [first] and [second]. | 
|  | static NativeThrowBehavior choice( | 
|  | NativeThrowBehavior first, | 
|  | NativeThrowBehavior second, | 
|  | ) { | 
|  | return first.or(second); | 
|  | } | 
|  |  | 
|  | NativeThrowBehavior visit(js.Node node) { | 
|  | return node.accept(this); | 
|  | } | 
|  |  | 
|  | @override | 
|  | NativeThrowBehavior visitNode(js.Node node) { | 
|  | return NativeThrowBehavior.may; | 
|  | } | 
|  |  | 
|  | @override | 
|  | NativeThrowBehavior visitComment(js.Comment node) { | 
|  | return NativeThrowBehavior.never; | 
|  | } | 
|  |  | 
|  | @override | 
|  | NativeThrowBehavior visitLiteral(js.Literal node) { | 
|  | return NativeThrowBehavior.never; | 
|  | } | 
|  |  | 
|  | @override | 
|  | NativeThrowBehavior visitInterpolatedExpression(js.InterpolatedNode node) { | 
|  | return NativeThrowBehavior.never; | 
|  | } | 
|  |  | 
|  | @override | 
|  | NativeThrowBehavior visitInterpolatedSelector(js.InterpolatedNode node) { | 
|  | return NativeThrowBehavior.never; | 
|  | } | 
|  |  | 
|  | @override | 
|  | NativeThrowBehavior visitArrayInitializer(js.ArrayInitializer node) { | 
|  | return node.elements.map(visit).fold(NativeThrowBehavior.never, sequence); | 
|  | } | 
|  |  | 
|  | @override | 
|  | NativeThrowBehavior visitArrayHole(js.ArrayHole node) { | 
|  | return NativeThrowBehavior.never; | 
|  | } | 
|  |  | 
|  | @override | 
|  | NativeThrowBehavior visitObjectInitializer(js.ObjectInitializer node) { | 
|  | return node.properties.map(visit).fold(NativeThrowBehavior.never, sequence); | 
|  | } | 
|  |  | 
|  | @override | 
|  | NativeThrowBehavior visitProperty(js.Property node) { | 
|  | return sequence(visit(node.name), visit(node.value)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | NativeThrowBehavior visitAssignment(js.Assignment node) { | 
|  | // TODO(sra): Can we make "#.p = #" be null(1)? | 
|  | return NativeThrowBehavior.may; | 
|  | } | 
|  |  | 
|  | @override | 
|  | NativeThrowBehavior visitVariableInitialization( | 
|  | js.VariableInitialization node, | 
|  | ) { | 
|  | final value = node.value; | 
|  | if (value == null) return NativeThrowBehavior.never; | 
|  | return visit(value); | 
|  | } | 
|  |  | 
|  | @override | 
|  | NativeThrowBehavior visitCall(js.Call node) { | 
|  | js.Expression target = node.target; | 
|  | if (target is js.PropertyAccess && _isFirstInterpolatedProperty(target)) { | 
|  | // #.f(...): Evaluate selector 'f', dereference, evaluate arguments, and | 
|  | // finally call target. | 
|  | NativeThrowBehavior result = sequence( | 
|  | visit(target.selector), | 
|  | NativeThrowBehavior.nullNsm, | 
|  | ); | 
|  | for (js.Expression argument in node.arguments) { | 
|  | result = sequence(result, visit(argument)); | 
|  | } | 
|  | return sequence(result, NativeThrowBehavior.may); // Target may throw. | 
|  | } | 
|  | return NativeThrowBehavior.may; | 
|  | } | 
|  |  | 
|  | @override | 
|  | NativeThrowBehavior visitNew(js.New node) { | 
|  | // TODO(sra): `new Array(x)` where `x` is a small number. | 
|  | return NativeThrowBehavior.may; | 
|  | } | 
|  |  | 
|  | @override | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | NativeThrowBehavior visitThrow(js.Throw node) { | 
|  | return sequence(visit(node.expression), NativeThrowBehavior.may); | 
|  | } | 
|  |  | 
|  | @override | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | 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 'Math': | 
|  | case 'Object': | 
|  | return NativeThrowBehavior.never; | 
|  | default: | 
|  | return NativeThrowBehavior.may; | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | NativeThrowBehavior visitAccess(js.PropertyAccess node) { | 
|  | js.Node receiver = node.receiver; | 
|  | NativeThrowBehavior first = visit(receiver); | 
|  | NativeThrowBehavior second = visit(node.selector); | 
|  |  | 
|  | if (_isFirstInterpolatedProperty(node)) { | 
|  | first = NativeThrowBehavior.nullNsm; | 
|  | } else { | 
|  | first = NativeThrowBehavior.may; | 
|  | } | 
|  |  | 
|  | return sequence(first, second); | 
|  | } | 
|  |  | 
|  | bool _isFirstInterpolatedProperty(js.PropertyAccess node) { | 
|  | js.Node receiver = node.receiver; | 
|  | if (receiver is js.InterpolatedExpression && | 
|  | receiver.isPositional && | 
|  | receiver.nameOrPosition == 0) { | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  | } |