Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(120)

Unified Diff: pkg/compiler/lib/src/js/placeholder_safety.dart

Issue 1510633003: Safety analysis for JS template placeholders. (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Make predicate positional Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | pkg/compiler/lib/src/tree_ir/optimization/statement_rewriter.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/compiler/lib/src/js/placeholder_safety.dart
diff --git a/pkg/compiler/lib/src/js/placeholder_safety.dart b/pkg/compiler/lib/src/js/placeholder_safety.dart
new file mode 100644
index 0000000000000000000000000000000000000000..67ff5631a58d195d50550993a5647abbc77a2b78
--- /dev/null
+++ b/pkg/compiler/lib/src/js/placeholder_safety.dart
@@ -0,0 +1,327 @@
+// Copyright (c) 2015, 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.
+
+
+library js.safety;
+
+import "js.dart" as js;
+
+typedef bool PositionPredicate(int position);
+
+/// PlaceholderSafetyAnalysis determines which placeholders in a JavaScript
+/// template may be replaced with an arbitrary expression. Placeholders may be
+/// replaced with an arbitrary expression providied the template ensures the
+/// placeholders are evaluated in the same left-to-right order with no
+/// additional effects interleaved.
+///
+/// The result is semi-conservative, giving reasonable results for many simple
+/// JS fragments. The non-conservative part is the assumption that arithmetic
+/// operators are used on 'good' operands that do not force arbitrary code to be
+/// executed via conversions (valueOf() and toString() methods).
+class PlaceholderSafetyAnalysis extends js.BaseVisitor<int> {
+ final PositionPredicate isNullableInput;
+ int nextPosition = 0;
+ int maxSafePosition = -1;
+ bool safe = true;
+
+ // We do a crude abstract interpretation to find operations that might throw
+ // exceptions. The possible values of expressions are represented by
+ // integers. Small non-negative integers 0, 1, 2, ... represent the values of
+ // the placeholders. Other values are:
+ static const int NONNULL_VALUE = -1; // Unknown but not null.
+ static const int UNKNOWN_VALUE = -2; // Unknown and might be null.
+
+ PlaceholderSafetyAnalysis._(this.isNullableInput);
+
+ /// Returns the number of placeholders that can be substituted into the
+ /// template AST [node] without changing the order of observable effects.
+ /// [isNullableInput] is a function that takes the 0-based index of a
+ /// placeholder and returns `true` if expression at run time may be null, and
+ /// `false` if the value is never null.
+ static int analyze(js.Node node, PositionPredicate isNullableInput) {
+ PlaceholderSafetyAnalysis analysis =
+ new PlaceholderSafetyAnalysis._(isNullableInput);
+ analysis.visit(node);
+ return analysis.maxSafePosition + 1;
+ }
+
+ bool canBeNull(int value) {
+ if (value == NONNULL_VALUE) return false;
+ if (value == UNKNOWN_VALUE) return true;
+ return isNullableInput(value);
+ }
+
+ int unsafe(int value) {
+ safe = false;
+ return value;
+ }
+
+ int visit(js.Node node) {
+ return node.accept(this);
+ }
+
+ int visitNode(js.Node node) {
+ safe = false;
+ super.visitNode(node);
+ return UNKNOWN_VALUE;
+ }
+
+ int visitLiteralNull(js.LiteralNull node) {
+ return UNKNOWN_VALUE;
+ }
+
+ int visitLiteral(js.Literal node) {
+ return NONNULL_VALUE;
+ }
+
+ int handleInterpolatedNode(js.InterpolatedNode node) {
+ assert(node.isPositional);
+ int position = nextPosition++;
+ if (safe) maxSafePosition = position;
+ return position;
+ }
+
+ int visitInterpolatedExpression(js.InterpolatedExpression node) {
+ return handleInterpolatedNode(node);
+ }
+
+ int visitInterpolatedLiteral(js.InterpolatedLiteral node) {
+ return handleInterpolatedNode(node);
+ }
+
+ int visitInterpolatedSelector(js.InterpolatedSelector node) {
+ return handleInterpolatedNode(node);
+ }
+
+ int visitInterpolatedStatement(js.InterpolatedStatement node) {
+ return handleInterpolatedNode(node);
+ }
+
+ int visitInterpolatedDeclaration(js.InterpolatedDeclaration node) {
+ return handleInterpolatedNode(node);
+ }
+
+ int visitObjectInitializer(js.ObjectInitializer node) {
+ for (js.Property property in node.properties) {
+ visit(property);
+ }
+ return NONNULL_VALUE;
+ }
+
+ int visitProperty(js.Property node) {
+ visit(node.name);
+ visit(node.value);
+ return UNKNOWN_VALUE;
+ }
+
+ int visitArrayInitializer(js.ArrayInitializer node) {
+ node.elements.forEach(visit);
+ return NONNULL_VALUE;
+ }
+
+ int visitArrayHole(js.ArrayHole node) {
+ return UNKNOWN_VALUE;
+ }
+
+ int visitAccess(js.PropertyAccess node) {
+ int first = visit(node.receiver);
+ int second = visit(node.selector);
+ // TODO(sra): If the JS is annotated as never throwing, we can avoid this.
+ if (canBeNull(first)) safe = false;
+ return UNKNOWN_VALUE;
+ }
+
+ int visitAssignment(js.Assignment node) {
+ js.Expression left = node.leftHandSide;
+ js.Expression right = node.value;
+
+ int leftToRight() {
+ visit(left);
+ visit(right);
+ return UNKNOWN_VALUE;
+ }
+
+ if (left is js.InterpolatedNode) {
+ // A bare interpolated expression should not be the LHS of an assignment.
+ safe = false;
+ return leftToRight();
+ }
+
+ // Assignment operators dereference the LHS before evaluating the RHS.
+ if (node.op != null) return leftToRight();
+
+ // Assignment (1) evaluates the LHS as a Reference `lval`, (2) evaluates the
+ // RHS as a value, (3) dereferences the `lval` in PutValue.
+ if (left is js.VariableReference) {
+ int value = visit(right);
+ // Assignment could change an observed global or cause a ReferenceError.
+ safe = false;
+ return value;
+ }
+ if (left is js.PropertyAccess) {
+ // "a.b.x = c.y" gives a TypeError for null values in this order: `a`,
+ // `c`, `a.b`.
+ int receiver = visit(left.receiver);
+ int selector = visit(left.selector);
+ int value = visit(right);
+ if (canBeNull(receiver)) safe = false;
+ return value;
+ }
+ // Be conserative with unrecognized LHS expressions.
+ safe = false;
+ return leftToRight();
+ }
+
+ int visitCall(js.Call node) {
+ // TODO(sra): Recognize JavaScript built-ins like
+ // 'Object.prototype.hasOwnProperty.call'.
+ visit(node.target);
+ node.arguments.forEach(visit);
+ return unsafe(UNKNOWN_VALUE);
+ }
+
+ int visitNew(js.New node) {
+ visit(node.target);
+ node.arguments.forEach(visit);
+ return unsafe(NONNULL_VALUE);
+ }
+
+ int visitBinary(js.Binary node) {
+ 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 "|":
+ int left = visit(node.left);
+ int right = visit(node.right);
+ return NONNULL_VALUE; // Number, String, Boolean.
+
+ case ',':
+ int left = visit(node.left);
+ int right = visit(node.right);
+ return right;
+
+ case "&&":
+ case "||":
+ int left = visit(node.left);
+ // TODO(sra): Might be safe, e.g. "x || 0".
+ safe = false;
+ int right = visit(node.right);
+ return UNKNOWN_VALUE;
+
+ case "instanceof":
+ case "in":
+ int left = visit(node.left);
+ int right = visit(node.right);
+ return UNKNOWN_VALUE;
+
+ default:
+ return unsafe(UNKNOWN_VALUE);
+ }
+ }
+
+ int visitConditional(js.Conditional node) {
+ int cond = visit(node.condition);
+ // TODO(sra): Might be safe, e.g. "# ? 1 : 2".
+ safe = false;
+ int thenValue = visit(node.then);
+ int elseValue = visit(node.otherwise);
+ return UNKNOWN_VALUE;
+ }
+
+ int visitThrow(js.Throw node) {
+ visit(node.expression);
+ return unsafe(UNKNOWN_VALUE);
+ }
+
+ int visitPrefix(js.Prefix node) {
+ if (node.op == 'typeof') {
+ // "typeof a" first evaluates to a Reference. If the Reference is to a
+ // variable that is not present, "undefined" is returned without
+ // dereferencing.
+ if (node.argument is js.VariableUse) return NONNULL_VALUE; // A string.
+ }
+
+ visit(node.argument);
+
+ switch (node.op) {
+ case '+':
+ case '-':
+ case '!':
+ case '~':
+ // Non-conservative assumption that these operators are used on values
+ // that do not call arbitrary code via valueOf() or toString().
+ return NONNULL_VALUE;
+
+ case 'typeof':
+ return NONNULL_VALUE; // Always a string.
+
+ case 'void':
+ return UNKNOWN_VALUE;
+
+ case '--':
+ case '++':
+ return NONNULL_VALUE; // Always a number.
+
+ default:
+ safe = false;
+ return UNKNOWN_VALUE;
+ }
+ }
+
+ int visitPostfix(js.Postfix node) {
+ assert(node.op == '--' || node.op == '++');
+ visit(node.argument);
+ return NONNULL_VALUE; // Always a number, even for "(a=null, a++)".
+ }
+
+ int visitVariableUse(js.VariableUse node) {
+ // We could get a ReferenceError unless the variable is in scope. For JS
+ // fragments, the only use of VariableUse outside a `function(){...}` should
+ // be for global references. Certain global names are almost certainly not
+ // reference errors, e.g 'Array'.
+ switch (node.name) {
+ case 'Array':
+ case 'Date':
+ case 'Function':
+ case 'Number':
+ case 'Object':
+ case 'RegExp':
+ case 'String':
+ case 'self':
+ case 'window':
+ return NONNULL_VALUE;
+ default:
+ return unsafe(UNKNOWN_VALUE);
+ }
+ }
+
+ int visitFun(js.Fun node) {
+ bool oldSafe = safe;
+ int oldNextPosition = nextPosition;
+ visit(node.body);
+ // Creating a function has no effect on order unless there are embedded
+ // placeholders.
+ safe = (nextPosition == oldNextPosition) && oldSafe;
+ return NONNULL_VALUE;
+ }
+}
« no previous file with comments | « no previous file | pkg/compiler/lib/src/tree_ir/optimization/statement_rewriter.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698