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

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: 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 | tests/compiler/dart2js/js_safety_test.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..b3632b04b89b9bb537bd7f57577f23fbf6e97806
--- /dev/null
+++ b/pkg/compiler/lib/src/js/placeholder_safety.dart
@@ -0,0 +1,293 @@
+// 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 JavaScriptExpressionPredicate(js.Expression expression);
+
+/// 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<NativeThrowBehavior> {
asgerf 2015/12/08 11:40:37 Extends BaseVisitor<NativeThrowBehavior> but all t
sra1 2015/12/08 20:54:04 Done.
+ final JavaScriptExpressionPredicate 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,
+ JavaScriptExpressionPredicate 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;
+ print(node);
asgerf 2015/12/08 11:40:37 Renegade print statement
sra1 2015/12/08 20:54:04 Done.
+ 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) {
+ safe = false;
asgerf 2015/12/08 11:40:37 Why safe = false here?
sra1 2015/12/08 20:54:04 Not sure why. It would occur only in the body of a
+ return handleInterpolatedNode(node);
+ }
+
+ int visitInterpolatedDeclaration(js.InterpolatedDeclaration node) {
+ safe = false;
+ 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 visitAccess(js.PropertyAccess node) {
+ int first = visit(node.receiver);
+ int second = visit(node.selector);
+ if (canBeNull(first)) safe = false;
asgerf 2015/12/08 11:40:37 If the JS fragment is annotated with throws:never
sra1 2015/12/08 20:54:04 Acknowledged.
+ return UNKNOWN_VALUE;
+ }
+
+ int visitAssignment(js.Assignment node) {
+ js.Expression target = node.leftHandSide;
+
+ int leftToRight() {
+ visit(target);
+ visit(node.value);
+ return UNKNOWN_VALUE;
+ }
+
+ if (target is js.InterpolatedNode) {
+ // A bare interpolated expression should not be the LHS of an assignment.
+ safe = false;
+ return leftToRight();
+ }
+
+ if (node.op == null) {
+ if (target is js.VariableReference) {
+ int value = visit(node.value);
+ // Assignment could change a global or cause a ReferenceError.
+ safe = false;
+ return value;
+ } else if (target is js.PropertyAccess) {
+ // "a.x = b.y" detects a null `b` before a null `a`.
asgerf 2015/12/08 11:40:37 It seems the relevant information is that the RHS
sra1 2015/12/08 20:54:04 I have clarified the comment (I hope).
+ int receiver = visit(target.receiver);
+ int selector = visit(target.selector);
+ int value = visit(node.value);
+ if (canBeNull(receiver)) safe = false;
+ return value;
+ } else {
+ //
asgerf 2015/12/08 11:40:37 Blank comment
sra1 2015/12/08 20:54:04 Done.
+ safe = false;
+ return leftToRight();
+ }
+ }
+ return leftToRight();
+ }
+
+ int visitCall(js.Call node) {
+ visit(node.target);
+ node.arguments.forEach(visit);
+ return unsafe(UNKNOWN_VALUE);
+ }
+
+ int visitNew(js.New node) {
+ // TODO(sra): `new Array(x)` where `x` is a small number.
+ visitCall(node);
+ 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 UNKNOWN_VALUE;
+
+ case ',':
+ int left = visit(node.left);
+ int right = visit(node.right);
+ return right;
+
+ case "&&":
+ case "||":
+ int left = visit(node.left);
+ safe = false;
asgerf 2015/12/08 11:40:37 Just an observation. If there are no placeholders
sra1 2015/12/08 20:54:04 Acknowledged.
+ 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 visitThrow(js.Throw node) {
+ visit(node.expression);
+ return unsafe(UNKNOWN_VALUE);
+ }
+
+ int visitPrefix(js.Prefix node) {
+ if (node.op == 'typeof') {
+ // "typeof a" does not dereference "a".
+ if (node.argument is js.VariableUse) return NONNULL_VALUE;
+ }
+
+ 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;
asgerf 2015/12/08 11:40:37 The binary operators return UNKNOWN_VALUE. Is ther
sra1 2015/12/08 20:54:04 No reason. They all return numbers, strings, bool
+
+ case 'typeof':
+ return NONNULL_VALUE; // Always a string.
+
+ case 'void':
+ return UNKNOWN_VALUE;
+
+ default:
asgerf 2015/12/08 11:40:37 What about prefix ++ and --?
sra1 2015/12/08 20:54:04 Done.
+ safe = false;
+ return UNKNOWN_VALUE;
+ }
+ }
+
+ int visitPostfix(js.Postfix node) {
+ assert(node.op == '--' || node.op == '++');
+ visit(node.argument);
+ return UNKNOWN_VALUE;
+ }
+
+ 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'.
asgerf 2015/12/08 11:40:37 I think we could justify adding window and self to
sra1 2015/12/08 20:54:04 Done.
+ switch (node.name) {
+ case 'Array':
+ case 'Date':
+ case 'Function':
+ case 'Object':
+ 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;
+ }
+}
« no previous file with comments | « no previous file | tests/compiler/dart2js/js_safety_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698