Reviewers: Jasvir, metaweta,
Description:
this fixes issue 1452
http://code.google.com/p/google-caja/issues/detail?id=1452
the method reuse() creates a temporary var for an expression, so that
the value can be used multiple times. When reuse() notices the
expression is simple enough, it doesn't create a temporary.
The method reuseAll() is reuse() applied to an argument list.
Currently it just calls reuse() on each argument, which is wrong.
When an argument list is something like this:
o.m(i, i++)
the result is something like this:
var x1___ = i++;
o.m(i, x1___);
This change ensures reuseAll() creates temporaries for all its args
if any of its args needs a temporary.
Please review this at http://codereview.appspot.com/5820071/
Affected files:
M src/com/google/caja/parser/quasiliteral/Rule.java
M tests/com/google/caja/parser/quasiliteral/ES53RewriterTest.java
Index: src/com/google/caja/parser/quasiliteral/Rule.java
===================================================================
--- src/com/google/caja/parser/quasiliteral/Rule.java (revision 4807)
+++ src/com/google/caja/parser/quasiliteral/Rule.java (working copy)
@@ -300,10 +300,11 @@
protected Pair<Expression, Expression> reuse(
ParseTreeNode value, Scope scope) {
Expression rhs = (Expression) rewriter.expand(value, scope);
- if (rhs instanceof Reference || rhs instanceof Literal) {
+ if (noSideEffects(rhs)) {
return new Pair<Expression, Expression>(
rhs, Operation.undefined(FilePosition.UNKNOWN));
}
+
Expression tempRef = new Reference(
scope.declareStartOfScopeTempVariable());
Expression tempInit = (Expression) QuasiBuilder.substV(
@@ -313,6 +314,17 @@
return new Pair<Expression, Expression>(tempRef, tempInit);
}
+ private boolean noSideEffects(Expression e) {
+ if (e instanceof Reference) {
+ return true;
+ } else if (e instanceof Literal) {
+ return true;
+ } else {
+ // conservative answer
+ return false;
+ }
+ }
+
/**
* Returns a pair of a reusable expression list and an initializing
* expression that together represent the reusable expansion of the
@@ -323,19 +335,40 @@
*/
protected Pair<ParseTreeNodeContainer, Expression> reuseAll(
ParseTreeNode arguments, Scope scope) {
+ Expression[] exp = new Expression[arguments.children().size()];
List<ParseTreeNode> refs = Lists.newArrayList();
- Expression[] inits = new Expression[arguments.children().size()];
- for (int i = 0; i < arguments.children().size(); i++) {
- Pair<Expression, Expression> p = reuse(
- arguments.children().get(i), scope);
- refs.add(p.a);
- inits[i] = p.b;
+ boolean pure = true;
+ for (int i = 0; i < exp.length; i++) {
+ ParseTreeNode arg = arguments.children().get(i);
+ exp[i] = (Expression) rewriter.expand(arg, scope);
+ pure = pure && noSideEffects(exp[i]);
}
+ if (pure) {
+ // No arg has side-effects, so we can use the expansions directly.
+ for (int i = 0; i < exp.length; i++) {
+ refs.add(exp[i]);
+ }
+ return new Pair<ParseTreeNodeContainer, Expression>(
+ new ParseTreeNodeContainer(refs),
+ Operation.undefined(FilePosition.UNKNOWN));
+ }
+
+ for (int i = 0; i < exp.length; i++) {
+ Expression tempRef = new Reference(
+ scope.declareStartOfScopeTempVariable());
+ Expression tempInit = (Expression) QuasiBuilder.substV(
+ "@ref = @rhs;",
+ "ref", tempRef,
+ "rhs", exp[i]);
+ refs.add(tempRef);
+ exp[i] = tempInit;
+ }
+
return new Pair<ParseTreeNodeContainer, Expression>(
new ParseTreeNodeContainer(refs),
- commas(inits));
+ commas(exp));
}
/**
Index: tests/com/google/caja/parser/quasiliteral/ES53RewriterTest.java
===================================================================
--- tests/com/google/caja/parser/quasiliteral/ES53RewriterTest.java
(revision 4807)
+++ tests/com/google/caja/parser/quasiliteral/ES53RewriterTest.java
(working copy)
@@ -14,14 +14,6 @@
package com.google.caja.parser.quasiliteral;
-import java.io.IOException;
-import java.net.URI;
-import java.util.Arrays;
-import java.util.EnumSet;
-import java.util.List;
-
-import junit.framework.AssertionFailedError;
-
import com.google.caja.lexer.ExternalReference;
import com.google.caja.lexer.FetchedData;
import com.google.caja.lexer.FilePosition;
@@ -45,10 +37,17 @@
import com.google.caja.reporting.MessageType;
import com.google.caja.reporting.TestBuildInfo;
import com.google.caja.util.Executor;
-import com.google.caja.util.FailureIsAnOption;
import com.google.caja.util.Lists;
import com.google.caja.util.RhinoTestBed;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+
+import junit.framework.AssertionFailedError;
+
public class ES53RewriterTest extends CommonJsRewriterTestCase {
protected class TestUriFetcher implements UriFetcher {
public FetchedData fetch(ExternalReference ref, String mimeType)
@@ -201,25 +200,41 @@
assertConsistent("({ x: 1, y: 2 });");
}
- //TODO(erights): Fix these tests to test for the output we now expect.
- @FailureIsAnOption
+ public final void testFunctionArgsWithSideEffects() throws Exception {
+ assertConsistent(
+ "(function() {\n" +
+ " function f(a, b) { return a + ',' + b; }\n" +
+ " var i = 0;\n" +
+ " return f(i, i++);\n" +
+ "})();");
+ }
+
+ public final void testMethodArgsWithSideEffects() throws Exception {
+ assertConsistent(
+ "(function () {\n" +
+ " var o = { f: function(a, b) { return a + ',' + b; } };\n" +
+ " var i = 0;\n" +
+ " return o.f(i, i++);\n" +
+ "})();");
+ }
+
public final void testFunctionToStringCall() throws Exception {
rewriteAndExecute(
"function foo() {}\n"
- + "assertEquals('function foo() {\\n [cajoled code]\\n}',\n"
+ + "assertEquals('\\nfunction foo() {\\n}\\n',\n"
+ " foo.toString());");
rewriteAndExecute(
- "function foo (a, b) { xx; }\n"
- + "assertEquals('function foo(a, b) {\\n [cajoled code]\\n}',\n"
+ "function foo (a, b) { 1; }\n"
+ + "assertEquals('\\nfunction foo(a, b) {\\n 1;\\n}\\n',\n"
+ " foo.toString());");
rewriteAndExecute(
"function foo() {}\n"
- + "assertEquals('function foo() {\\n [cajoled code]\\n}',\n"
+ + "assertEquals('\\nfunction foo() {\\n}\\n',\n"
+ " Function.prototype.toString.call(foo));");
rewriteAndExecute(
"var foo = function (x$x, y_y) {};\n"
+ "assertEquals("
- + " 'function foo$_var(x$x, y_y) {\\n [cajoled code]\\n}',\n"
+ + " '\\nfunction foo$_var(x$x, y_y) {\\n}\\n',\n"
+ " Function.prototype.toString.call(foo));");
}