| // 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; | |
| } | |
| } |