Package com.google.caja.parser.quasiliteral

Source Code of com.google.caja.parser.quasiliteral.ES53RewriterTest$TestUriFetcher

// Copyright (C) 2007 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.caja.parser.quasiliteral;

import com.google.caja.lexer.ExternalReference;
import com.google.caja.lexer.FetchedData;
import com.google.caja.lexer.FilePosition;
import com.google.caja.lexer.InputSource;
import com.google.caja.lexer.ParseException;
import com.google.caja.parser.ParseTreeNode;
import com.google.caja.parser.js.Block;
import com.google.caja.parser.js.FormalParam;
import com.google.caja.parser.js.FunctionConstructor;
import com.google.caja.parser.js.FunctionDeclaration;
import com.google.caja.parser.js.Identifier;
import com.google.caja.parser.js.Operation;
import com.google.caja.parser.js.Operator;
import com.google.caja.parser.js.Reference;
import com.google.caja.parser.js.ReturnStmt;
import com.google.caja.parser.js.Statement;
import com.google.caja.parser.js.SyntheticNodes;
import com.google.caja.parser.js.UncajoledModule;
import com.google.caja.plugin.UriFetcher;
import com.google.caja.reporting.MessageLevel;
import com.google.caja.reporting.MessageType;
import com.google.caja.reporting.TestBuildInfo;
import com.google.caja.util.Executor;
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)
        throws UriFetchException {
      try {
        URI uri = ref.getReferencePosition().source().getUri()
            .resolve(ref.getUri());
        if ("resource".equals(uri.getScheme())) {
          return dataFromResource(uri.getPath(), new InputSource(uri));
        } else {
          throw new UriFetchException(ref, mimeType);
        }
      } catch (IOException ex) {
        throw new UriFetchException(ref, mimeType, ex);
      }
    }
  }

  private Rewriter es53Rewriter;

  /**
   * Tests that an inherited <tt>*_w___</tt> flag does not enable
   * bogus writability.
   * <p>
   * See <a href="http://code.google.com/p/google-caja/issues/detail?id=1052"
   * >issue 1052</a>.
   */
  public final void testNoFastpathWritableInheritance() throws Exception {
    rewriteAndExecute(
            "(function() {" +
            "  var a = {};" +
            "  var b = Object.freeze(Object.create(a));" +
            "  a.x = 8;" +
            "  assertThrowsMsg(function(){b.x = 9;}, 'not extensible');" +
            "  assertEquals(b.x, 8);" +
            "})();");
  }

  public final void testConstant() throws Exception {
    assertConsistent("1;");
  }

  public final void testInit() throws Exception {
    assertConsistent("var a = 0; a;");
  }

  public final void testNew() throws Exception {
    assertConsistent(
        "function f() { this.x = 1; }" +
        "var g = new f();" +
        "g.x;");
  }

  public final void testThrowCatch() throws Exception {
    assertConsistent(
        "var x = 0; try { throw 1; }" +
        "catch (e) { x = e; }" +
        "x;");
    assertConsistent(
        "var x = 0; try { throw { a: 1 }; }" +
        "catch (e) { x = e; }" +
        "'' + x;");
    assertConsistent(
        "var x = 0; try { throw 'err'; }" +
        "catch (e) { x = e; }" +
        "x;");
    assertConsistent(
        "var x = 0; try { throw new Error('err'); }" +
        "catch (e) { x = e.message; }" +
        "x;");
    assertConsistent(
        "var x = 0; try { throw 1; }" +
        "catch (e) { x = e; }" +
        "finally { x = 2; }" +
        "x;");
    assertConsistent(
        "var x = 0; try { throw { a: 1 }; }" +
        "catch (e) { x = e; }" +
        "finally { x = 2; }" +
        "x;");
    assertConsistent(
        "var x = 0; try { throw 'err'; }" +
        "catch (e) { x = e; }" +
        "finally { x = 2; }" +
        "x;");
    assertConsistent(
        "var x = 0; try { throw new Error('err'); }" +
        "catch (e) { x = e.message; }" +
        "finally { x = 2; }" +
        "x;");
  }

  public final void testProtoCall() throws Exception {
    assertConsistent("Array.prototype.sort.call([3, 1, 2]);");
    assertConsistent("[3, 1, 2].sort();");
    assertConsistent("[3, 1, 2].sort.call([4, 2, 7]);");

    assertConsistent("String.prototype.indexOf.call('foo', 'o');");
    assertConsistent("'foo'.indexOf('o');");

    assertConsistent("'foo'.indexOf.call('bar', 'o');");
    assertConsistent("'foo'.indexOf.call('bar', 'a');");
  }

  public final void testInherit() throws Exception {
    assertConsistent(
        "function Point(x) { this.x = x; }\n" +
        "Point.prototype.toString = function () {\n" +
        "  return '<' + this.x + '>';\n" +
        "};\n" +
        "function WP(x) { Point.call(this,x); }\n" +
        "WP.prototype = Object.create(Point.prototype);\n" +
        "var pt = new WP(3);\n" +
        "pt.toString();");
  }

  /** See bug 528 */
  public final void testRegExpLeak() throws Exception {
    rewriteAndExecute(
        "assertEquals('' + (/(.*)/).exec(), 'undefined,undefined');");
  }

  public final void testClosure() throws Exception {
    assertConsistent(
        "function f() {" +
        "  var y = 2; " +
        "  this.x = function() {" +
        "    return y;" +
        "  }; " +
        "}" +
        "var g = new f();" +
        "var h = {};" +
        "f.call(h);" +
        "h.y = g.x;" +
        "h.x() + h.y();");
  }

  public final void testNamedFunctionShadow() throws Exception {
    assertConsistent("function f() { return f; } f === f();");
    assertConsistent(
        "(function () { function f() { return f; } return f === f(); })();");
  }

  public final void testArray() throws Exception {
    assertConsistent("[3, 2, 1].sort();");
    assertConsistent("[3, 2, 1].sort.call([4, 2, 7]);");
  }

  public final void testObject() throws Exception {
    assertConsistent("({ x: 1, y: 2 });");
  }

  public final void testFunctionCallWithSideEffects() throws Exception {
    assertConsistent(
        "(function() {\n" +
        "  function f(a, b, c) { return '' + [a, b, c]; }\n" +
        "  var i = 0;\n" +
        "  return f(f = 208, i, i++);\n" +
        "})();");
  }

  public final void testMethodCallWithSideEffects() throws Exception {
    assertConsistent(
        "(function () {\n" +
        "  var o = { f: function(a, b, c) { return '' + [a, b, c]; } };\n" +
        "  var i = 0;\n" +
        "  return o.f(o = 217, i, i++);\n" +
        "})();");
  }

  public final void testPropertyAssignmentWithSideEffects() throws Exception {
    assertConsistent(
        "(function () {\n" +
        "  var o = {};\n" +
        "  return o.x = (o = 225);\n" +
        "})();");
  }

  public final void testArrayAssignmentWithSideEffects1() throws Exception {
    assertConsistent(
        "(function () {\n" +
        "  var a = [];\n" +
        "  return a['3'] = (a = 233);\n" +
        "})();");
  }

  public final void testArrayAssignmentWithSideEffects2() throws Exception {
    assertConsistent(
        "(function () {\n" +
        "  var a = [];\n" +
        "  return a[+'3'] = (a = 241);\n" +
        "})();");
  }

  public final void testArrayAssignmentWithSideEffects3() throws Exception {
    assertConsistent(
        "(function () {\n" +
        "  var a = [];\n" +
        "  return a[3] = (a = 249);\n" +
        "})();");
  }

  public final void testFunctionToStringCall() throws Exception {
    rewriteAndExecute(
        "function foo() {}\n"
        + "assertEquals('\\nfunction foo() {\\n}\\n',\n"
        + "             foo.toString());");
    rewriteAndExecute(
        "function foo (a, b) { 1; }\n"
        + "assertEquals('\\nfunction foo(a, b) {\\n    1;\\n}\\n',\n"
        + "             foo.toString());");
    rewriteAndExecute(
        "function foo() {}\n"
        + "assertEquals('\\nfunction foo() {\\n}\\n',\n"
        + "             Function.prototype.toString.call(foo));");
    rewriteAndExecute(
        "var foo = function (x$x, y_y) {};\n"
        + "assertEquals("
        + "    '\\nfunction foo$_var(x$x, y_y) {\\n}\\n',\n"
        + "    Function.prototype.toString.call(foo));");
  }

  public final void testDate() throws Exception {
    assertConsistent("(new Date(0)).getTime();");
    assertConsistent("'' + (new Date(0));");
    rewriteAndExecute(
        ""
        + "var time = (new Date - 1);"
        + "assertFalse(isNaN(time));"
        + "assertEquals('number', typeof time);");
    rewriteAndExecute(
        "assertThrowsMsg(function(){Date.prototype.setFullYear(1066)}," +
        "    'Cannot change the internal state');");
  }

  public final void testMultiDeclaration2() throws Exception {
    rewriteAndExecute("var a, b, c;");
    rewriteAndExecute(
        ""
        + "var a = 0, b = ++a, c = ++a;"
        + "assertEquals(++a * b / c, 1.5);");
  }

  public final void testDelete() throws Exception {
    assertConsistent(
        "(function () { var a = { x: 1 }; delete a.x; return typeof a.x; })();"
        );
    assertConsistent("var a = { x: 1 }; delete a.x; typeof a.x;");
    // Tests for the gotcha rather than the spec'ed behavior.
    // See http://code.google.com/p/google-caja/wiki/DifferencesBetweenES5Over3AndES5
    rewriteAndExecute(
        "var x = {a:1, '[object Object]':2};" +
        "delete x[{valueOf:function(){return 'a';}}];" +
        "assertEquals(x.a, void 0);" +
        "assertEquals(x['[object Object]'], 2);");
  }

  public final void testIn2() throws Exception {
    assertConsistent(
        "(function () {" +
        "  var a = { x: 1 };\n" +
        "  return '' + ('x' in a) + ('y' in a);" +
        "})();");
    assertConsistent(
        "var a = { x: 1 };\n" +
        "[('x' in a), ('y' in a)];");
  }

  /**
   * Try to construct some class instances.
   */
  public final void testFuncCtor() throws Exception {
    rewriteAndExecute(
        "function Foo(x) { this.x = x; }" +
        "var foo = new Foo(2);" +
        "if (!foo) { fail('Failed to construct a global object.'); }" +
        "assertEquals(2, foo.x);" +
        "assertEquals(Foo, foo.constructor);");
    rewriteAndExecute(
        "(function () {" +
        "  function Foo(x) { this.x = x; }" +
        "  var foo = new Foo(2);" +
        "  if (!foo) { fail('Failed to construct a local object.'); }" +
        "  assertEquals(2, foo.x);" +
        "})();");
    rewriteAndExecute(
        "function Foo() { }" +
        "var foo = new Foo();" +
        "if (!foo) {" +
        "  fail('Failed to use a simple named function as a constructor.');" +
        "}");
  }

  public final void testFuncArgs() throws Exception {
    rewriteAndExecute(
        ""
        + "var x = 0;"
        + "function f() { x = arguments[0]; }"
        + "f(3);"
        + "assertEquals(3, x);");
  }

  public final void testStatic() throws Exception {
    assertConsistent("Array.prototype.slice([3, 4, 5, 6], 1);");
  }

  public final void testConcatArgs() throws Exception {
    rewriteAndExecute("", "(function(x, y){ return [x, y]; })",
        "var f = ___.getNewModuleHandler().getLastValue();"
        + "function g(var_args) { return f.apply(___.USELESS, arguments); }"
        + "assertEquals(g(3, 4).toString(), [3, 4].toString());");
  }

  public final void testReformedGenerics() throws Exception {
    assertConsistent(
        "var x = [33];" +
        "x.foo = [].push;" +
        "x.foo(44);" +
        "x;");
    assertConsistent(
        "var x = {blue:'green'};" +
        "x.foo = [].push;" +
        "x.foo(44);" +
        "var keys = [];" +
        "for (var i in x) { if (x.hasOwnProperty(i)) { keys.push(i); } }" +
        "keys.sort();");
    assertConsistent(
        "var x = [33];" +
        "Array.prototype.push.apply(x, [3,4,5]);" +
        "x;");
    assertConsistent(
        "var x = {blue:'green'};" +
        "Array.prototype.push.apply(x, [3,4,5]);" +
        "var keys = [];" +
        "for (var i in x) { if (x.hasOwnProperty(i)) { keys.push(i); } }" +
        "keys.sort();");
    assertConsistent(
        "var x = {blue:'green'};" +
        "x.foo = [].push;" +
        "x.foo.call(x, 44);" +
        "var keys = [];" +
        "for (var i in x) { if (x.hasOwnProperty(i)) { keys.push(i); } }" +
        "keys.sort();");
  }

  public final void testMonkeyPatchPrimordialFunction() throws Exception {
    assertConsistent(
        "isNaN.foo = 'bar';" +
        "isNaN.foo;");
  }

  public final void testInMonkeyDelete() throws Exception {
    assertConsistent(
        "var x = {y:1 };" +
        "delete x.y;" +
        "('y' in x);");
  }

  public final void testMonkeyOverride() throws Exception {
    assertConsistent(
        // TODO(erights): Fix when bug 953 is fixed.
        "Date.prototype.propertyIsEnumerable = function(p) { return true; };" +
        "(new Date()).propertyIsEnumerable('foo');");
  }

  public final void testEmbeddedcajaVM() throws Exception {
    assertConsistent(
        ""
        + "\"use strict,cajaVM\"; \n"
        + "var foo; \n"
        + "(function () { \n"
        + "  foo = function () { return 8; }; \n"
        + "})(); \n"
        + "foo();"
        );
  }

  /**
   * Tests freezing objects.
   */
  public final void testObjectFreeze() throws Exception {
    rewriteAndExecute(
        "var r = Object.freeze({});" +
        "assertThrowsMsg(function(){r.foo = 8;}, 'not extensible');");
    rewriteAndExecute(
        "var f = function(){};" +
        "f.foo = 8;");
    rewriteAndExecute(
        "var f = Object.freeze(function(){});" +
        "assertThrowsMsg(function(){f.foo = 8;}, 'not extensible');");
    rewriteAndExecute(
        "function Point(x,y) {" +
        "  this.x = x;" +
        "  this.y = y;" +
        "}" +
        "var pt = new Point(3,5);" +
        "pt.x = 8;" +
        "Object.freeze(pt);" +
        "assertThrowsMsg(function(){pt.y = 9;}, 'not writable');");
    // Check that deferred creation of prototype property doesn't make it
    // writable.
    rewriteAndExecute(
        "function f(){}" +
        "Object.freeze(f);" +
        "assertThrowsMsg(function() { f.prototype = {}; }, 'not writable');");
  }

  /**
   * Tests that the {@code prototype}, {@code name}, and {@code length}
   * properties of function instances are set properly.
   */
  public final void testFunctionInstance() throws Exception {
    rewriteAndExecute(
        "assertTrue(!!((function(){}).prototype));");
    rewriteAndExecute(
        "assertEquals((function(a,b,c){}).length, 3);");
    rewriteAndExecute(
        "assertEquals((function x(a,b,c){}).name, 'x');");
    // Check frozen functions created early in es53.js
    rewriteAndExecute(
        "assertTrue(!!(cajaVM.USELESS.toString.prototype));");
  }

  /**
   * Tests that the special handling of null on tamed exophora works.
   * <p>
   * The reification of tamed exophoric functions contains
   * special cases for when the first argument to call, bind, or apply
   * is null or undefined, in order to protect against privilege escalation.
   * {@code #testNoPrivilegeEscalation()} tests that we do prevent the
   * privilege escalation. Here, we test that this special case preserves
   * correct functionality.
   */
  public final void testTamedXo4aOkOnNull() throws Exception {
    rewriteAndExecute("this.foo = 8;",

        "var x = Object.create(cajaVM.USELESS);" +
        "assertFalse(({foo: 7}).hasOwnProperty.call(null, 'foo'));" +
        "assertTrue(cajaVM.USELESS.isPrototypeOf(x));" +
        "assertTrue(({foo: 7}).isPrototypeOf.call(null, x));",

        "assertTrue(({}).hasOwnProperty.call(null, 'foo'));" +
        "assertFalse(({bar: 7}).hasOwnProperty.call(null, 'bar'));");
    rewriteAndExecute("this.foo = 8;",

        "var x = Object.create(cajaVM.USELESS);" +
        "assertFalse(({foo: 7}).hasOwnProperty.apply(null, ['foo']));" +
        "assertTrue(cajaVM.USELESS.isPrototypeOf(x));" +
        "assertTrue(({foo: 7}).isPrototypeOf.apply(null, [x]));",

        "assertTrue(({}).hasOwnProperty.apply(null, ['foo']));" +
        "assertFalse(({bar: 7}).hasOwnProperty.apply(null, ['bar']));");
    rewriteAndExecute(
        "var x = Object.create(cajaVM.USELESS);" +
        "assertFalse(({foo: 7}).hasOwnProperty.bind(null)('foo'));" +
        "assertTrue(cajaVM.USELESS.isPrototypeOf(x));" +
        "assertTrue(({foo: 7}).isPrototypeOf.bind(null)(x));");
  }

  public final void testToString() throws Exception {
    assertConsistent(
        "var z = { toString: function () { return 'blah'; } };" +
        "try {" +
        "  '' + z;" +
        "} catch (e) {" +
        "  throw new Error('PlusPlus error: ' + e);" +
        "}");
    assertConsistent(
        "  function foo() {"
        + "  var x = 1;"
        + "  return {"
        + "    toString: function () {"
        + "      return x;"
        + "    }"
        + "  };"
        + "}"
        + "'' + (new foo);");
  }

  public final void testToStringToxicity() throws Exception {
    rewriteAndExecute(
        "",
        "function objMaker(f) {return {toString:f};}",
        "assertThrowsMsg(" +
        "    function() {testImports.objMaker(function(){return '1';});}," +
        "    'toxic');"
        );
  }

  public final void testInitializeMap() throws Exception {
    assertConsistent("var zerubabel = {bobble:2, apple:1}; zerubabel.apple;");
  }

  public final void testValueOf() throws Exception {
    assertConsistent("''+{valueOf:function(){return 5;}}");
  }

  public final void testAssertEqualsCajoled() throws Exception {
    try {
      rewriteAndExecute("assertEquals(1, 2);");
    } catch (AssertionFailedError e) {
      return;
    }
    fail("Assertions do not work in cajoled mode");
  }

  public final void testAssertThrowsCajoledNoError() throws Exception {
    rewriteAndExecute(
        "  assertThrows(function() { throw 'foo'; });");
    rewriteAndExecute(
        "  assertThrows("
        + "    function() { throw 'foo'; },"
        + "    'foo');");
  }

  public final void testAssertThrowsCajoledErrorNoMsg() throws Exception {
    try {
      rewriteAndExecute("assertThrows(function() {});");
    } catch (AssertionFailedError e) {
      return;
    }
    fail("Assertions do not work in cajoled mode");
  }

  public final void testAssertThrowsCajoledErrorWithMsg() throws Exception {
    try {
      rewriteAndExecute("assertThrows(function() {}, 'foo');");
    } catch (AssertionFailedError e) {
      return;
    }
    fail("Assertions do not work in cajoled mode");
  }

  public final void testConstructionWithFunction() throws Exception {
    assertConsistent(
        "  function Point() {}"
        + "var p = new Point();"
        + "(p !== undefined);");
    assertConsistent(
        "  var Point = function() {};"
        + "var p = new Point();"
        + "(p !== undefined);");
  }

  public final void testReflectiveMethodInvocation() throws Exception {
    assertConsistent(
        "(function (first, second) { return 'a' + first + 'b' + second; })"
        + ".call([], 8, 9);");
    assertConsistent(
        "var a = []; [].push.call(a, 5, 6); a;");
    assertConsistent(
        "(function (a, b) { return 'a' + a + 'b' + b; }).apply([], [8, 9]);");
    assertConsistent(
        "var a = []; [].push.apply(a, [5, 6]); a;");
    assertConsistent(
        "[].sort.apply([6, 5]);");
    assertConsistent(
        "function f(first, second) {" +
        "  return 'a' + first + 'b' + second;" +
        "}\n" +
        "var g = f.bind([], 8);\n" +
        "g(9);");
  }

  /**
   * Tests that <a href=
   * "http://code.google.com/p/google-caja/issues/detail?id=242"
   * >bug#242</a> is fixed.
   * <p>
   * The actual Function.bind() method used to be whitelisted and
   * written to return a frozen simple-function, allowing it to be called
   * from all code on all functions. As a result, if an <i>outer hull breach</i>
   * occurs -- if Caja code obtains a reference to a JavaScript
   * function value not marked as Caja-callable -- then
   * that Caja code could call the whitelisted bind() on it,
   * and then call the result, causing an <i>inner hull breach</i>,
   * which threatens kernel integrity.
   */
  public final void testToxicBind() throws Exception {
    rewriteAndExecute(
        "var confused = false;" +
        "testImports.keystone = function keystone() { confused = true; };" +
        "___.grantRead(testImports, 'keystone');",
        "assertThrowsMsg(function() {keystone.bind()();}, 'toxic');",
        "assertFalse(confused);");
  }

  /**
   * Tests that <a href=
   * "http://code.google.com/p/google-caja/issues/detail?id=590"
   * >bug#590</a> is fixed.
   * <p>
   * As a client of an object, Caja code must only be able to directly delete
   * <i>public</i> properties of non-frozen JSON containers. Due to this bug,
   * Caja code was able to delete properties in the Caja namespace.
   */
  public final void testBadDelete() throws Exception {
    rewriteAndExecute(
        "testImports.badContainer = {secret__: 3469};" +
        "___.grantRead(testImports, 'badContainer');",
        "assertThrowsMsg(function() {delete badContainer['secret__'];}," +
        "    'double underscore');",
        "assertEquals(testImports.badContainer.secret__, 3469);");
    rewriteAndExecute(
        "assertThrowsMsg(function() {delete ({})['proto___'];}," +
        "    'double underscore');");
  }

  /**
   * Tests that apply works.
   */
  public final void testApply() throws Exception {
    rewriteAndExecute(
        "",
        "var x = 0;" +
        "function f() { x = 1 }\n" +
        "f.apply({});",
        "assertEquals(testImports.x, 1);");
    // TODO(erights): Need more tests.
  }

  /**
   * Tests that <a href=
   * "http://code.google.com/p/google-caja/issues/detail?id=347"
   * >bug#347</a> is fixed.
   * <p>
   * The <tt>in</tt> operator should only test for properties visible to Caja.
   */
  public final void testInVeil() throws Exception {
    rewriteAndExecute(
        "assertFalse('f___' in Object);");
  }

  ////////////////////////////////////////////////////////////////////////
  // Handling of synthetic nodes
  ////////////////////////////////////////////////////////////////////////

  public final void testSyntheticIsUntouched() throws Exception {
    String source = "function foo() { this; arguments; }";
    ParseTreeNode input = js(fromString(source));
    syntheticTree(input);
    checkSucceeds(input, js(fromString("var dis___ = IMPORTS___;" + source)));
  }

  public final void testSyntheticMemberAccess() throws Exception {
    ParseTreeNode input = js(fromString("({}).foo"));
    syntheticTree(input);
    checkSucceeds(
        input,
        js(fromString("var dis___ = IMPORTS___; ___.iM([]).foo;")));
  }

  public final void testSyntheticFormals() throws Exception {
    FilePosition unk = FilePosition.UNKNOWN;
    FunctionConstructor fc = new FunctionConstructor(
        unk,
        new Identifier(unk, "f"),
        Arrays.asList(
            new FormalParam(new Identifier(unk, "x")),
            new FormalParam(
                SyntheticNodes.s(new Identifier(unk, "y___")))),
        new Block(
            unk,
            Arrays.<Statement>asList(new ReturnStmt(
                unk,
                Operation.createInfix(
                    Operator.MULTIPLICATION,
                    Operation.createInfix(
                        Operator.ADDITION,
                        new Reference(new Identifier(unk, "x")),
                        new Reference(SyntheticNodes.s(
                            new Identifier(unk, "y___")))),
                    new Reference(new Identifier(unk, "z")))))));
    checkSucceeds(
        new Block(
            unk,
            Arrays.asList(
                new FunctionDeclaration((FunctionConstructor) fc.clone()))),
        js(fromString(
            ""
            // x and y___ are formals, but z is free to the function.
            + "var dis___ = IMPORTS___;"
            + "{"
            + "  function f(x, y___) {"
            + "    return (x + y___) *"
            + "        (IMPORTS___.z_v___ ?"
            + "        IMPORTS___.z :"
            + "        ___.ri(IMPORTS___, 'z'));"
            + "  }"
            + "  IMPORTS___.w___('f', ___.f(f, 'f'));"
            + "}")));

    SyntheticNodes.s(fc);
    checkSucceeds(
        new Block(
            unk,
            Arrays.asList(
                new FunctionDeclaration((FunctionConstructor) fc.clone()))),
        js(fromString(
            ""
            // x and y___ are formals, but z is free to the function.
            + "var dis___ = IMPORTS___;"
            + "function f(x, y___) {"
            + "  return (x + y___) *"
            + "        (IMPORTS___.z_v___ ?"
            + "        IMPORTS___.z :"
            + "        ___.ri(IMPORTS___, 'z'));"
            + "}"
            // Since the function is synthetic, it is not marked.
            )));
  }

  ////////////////////////////////////////////////////////////////////////
  // Specific rules
  ////////////////////////////////////////////////////////////////////////

  public final void testWith() throws Exception {
    checkFails("with (dreams || ambiguousScoping) anything.isPossible();",
               "\"with\" blocks are not allowed");
    checkFails("with (dreams || ambiguousScoping) { anything.isPossible(); }",
               "\"with\" blocks are not allowed");
  }

  public final void testSESCompatibilityWarnings() throws Exception {
    checkAddsMessage(js(fromString(
        "var xxx = (1 + 2) /3;")),
        RewriterMessageType.TOP_LEVEL_VAR_INCOMPATIBLE_WITH_CAJA);
    checkAddsMessage(js(fromString(
        "var xxx;")),
        RewriterMessageType.TOP_LEVEL_VAR_INCOMPATIBLE_WITH_CAJA);
    checkAddsMessage(js(fromString(
        "function yyy() {};")),
        RewriterMessageType.TOP_LEVEL_FUNC_INCOMPATIBLE_WITH_CAJA);
  }

  public final void testTryCatch() throws Exception {
    checkAddsMessage(js(fromString(
        "try {" +
        "  throw 2;" +
        "} catch (e) {" +
        "  var e;" +
        "}")),
        MessageType.MASKING_SYMBOL,
        MessageLevel.LINT);
    checkAddsMessage(js(fromString(
        "var e;" +
        "try {" +
        "  throw 2;" +
        "} catch (e) {" +
        "}")),
        MessageType.MASKING_SYMBOL,
        MessageLevel.LINT);
    checkAddsMessage(js(fromString(
        "try {} catch (x__) { }")),
        RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE);
    checkAddsMessage(js(fromString(
        "var x;" +
        "try {" +
        "  g[x + 0];" +
        "  g[x + 1];" +
        "} catch (e) {" +
        "  g[x + 2];" +
        "  e;" +
        "  g[x + 3];" +
        "}" +
        "var e;")),
        MessageType.MASKING_SYMBOL,
        MessageLevel.LINT);
    rewriteAndExecute(
        "var handled = false;" +
        "try {" +
        "  throw null;" +
        "} catch (ex) {" +
        "  assertEquals(null, ex);" // Right value in ex.
        "  handled = true;" +
        "}" +
        "assertTrue(handled);")// Control reached and left the catch block.
    rewriteAndExecute(
        "var handled = false;" +
        "try {" +
        "  throw undefined;" +
        "} catch (ex) {" +
        "  assertEquals(undefined, ex);" +
        "  handled = true;" +
        "}" +
        "assertTrue(handled);");
    rewriteAndExecute(
        "var handled = false;" +
        "try {" +
        "  throw true;" +
        "} catch (ex) {" +
        "  assertEquals(true, ex);" +
        "  handled = true;" +
        "}" +
        "assertTrue(handled);");
    rewriteAndExecute(
        "var handled = false;" +
        "try {" +
        "  throw 37639105;" +
        "} catch (ex) {" +
        "  assertEquals(37639105, ex);" +
        "  handled = true;" +
        "}" +
        "assertTrue(handled);");
    rewriteAndExecute(
        "var handled = false;" +
        "try {" +
        "  throw 'panic';" +
        "} catch (ex) {" +
        "  assertEquals('panic', ex);" +
        "  handled = true;" +
        "}" +
        "assertTrue(handled);");
    rewriteAndExecute(
        "var handled = false;" +
        "try {" +
        "  throw new Error('hello');" +
        "} catch (ex) {" +
        "  assertEquals('hello', ex.message);" +
        "  assertEquals('Error', ex.name);" +
        "  handled = true;" +
        "}" +
        "assertTrue(handled);");
    rewriteAndExecute(
        "var handled = false;" +
        "try {" +
        "  throw { toString: function () { return 'hiya'; }, y: 4 };" +
        "} catch (ex) {" +
        "  assertEquals('object', typeof ex);" +
        "  assertEquals('hiya', ex.toString());" +
        "  assertEquals(4, ex.y);" +
        "  handled = true;" +
        "}" +
        "assertTrue(handled);");
  }

  public final void testTryCatchFinally() throws Exception {
    checkAddsMessage(js(fromString(
        "try {" +
        "} catch (e) {" +
        "  var e;" +
        "} finally {" +
        "}")),
        MessageType.MASKING_SYMBOL,
        MessageLevel.LINT);
    checkAddsMessage(js(fromString(
        "var e;" +
        "try {" +
        "} catch (e) {" +
        "} finally {" +
        "}")),
        MessageType.MASKING_SYMBOL,
        MessageLevel.LINT);
    checkAddsMessage(js(fromString(
        "try {} catch (x__) { } finally { }")),
        RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE);
    assertConsistent(
        "var out = '';" +
        "try {" +
        "  throw 'hi ';" +
        "} catch (e) {" +
        "  out += e;" +
        "} finally {" +
        "  out += 'there';" +
        "}" +
        "out;");
  }

  public final void testTryFinally() throws Exception {
    assertConsistent(
        "var out = 0;" +
        "try {" +
        "  try {" +
        "    throw 2;" +
        "  } finally {" +
        "    out = 1;" +
        "  }" +
        "  out = 2;" +
        "} catch (e) {" +
        "}" +
        "out;");
  }

  public final void testGlobalBadSuffix() throws Exception {
    checkFails(
        "x__ = 1;",
        "Variables cannot end in \"__\"");
    checkFails(
        "var x__ = 1;",
        "Variables cannot end in \"__\"");
    checkFails(
        "var x = x__;",
        "Variables cannot end in \"__\"");
    checkFails(
        "var x\\u005f\\u005f;",
        "Variables cannot end in \"__\"");
    checkFails(
        "var x__;",
        "Variables cannot end in \"__\"");
    checkFails(
        "x\u005F\u005F = 1;",
        "Variables cannot end in \"__\"");
    checkFails(
        "__ = 1;",
        "Variables cannot end in \"__\"");
    checkFails(
        "\u005F\u005F = 1;",
        "Variables cannot end in \"__\"");
  }

  public final void testBadSuffix() throws Exception {
    checkFails(
        "function() { foo__; };",
        "Variables cannot end in \"__\"");
    // Make sure *single* underscore is okay
    checkSucceeds(
        "function() { var foo_ = 3; };",
        "var dis___ = IMPORTS___;" +
        "___.f(function () {" +
        "    var foo_;" +
        "    foo_ = 3;" +
        "  });");
    checkFails(
        "var x = function __() { };",
        "Variables cannot end in \"__\"");
    checkFails(
        "function () { var x = function __() { }; }",
        "Variables cannot end in \"__\"");
    checkFails(
        "function foo__() { }",
        "Variables cannot end in \"__\"");
    checkFails(
        "var foo__ = 3;",
        "Variables cannot end in \"__\"");
    checkFails(
        "var foo__;",
        "Variables cannot end in \"__\"");
    checkFails(
        "function() { function foo__() { } };",
        "Variables cannot end in \"__\"");
    checkFails(
        "function() { var foo__ = 3; };",
        "Variables cannot end in \"__\"");
    checkFails(
        "function() { var foo__; };",
        "Variables cannot end in \"__\"");
  }

  public final void testVarFuncFreeze() throws Exception {
    // We can cajole and refer to a function
    rewriteAndExecute(
        "function foo() {}" +
        "foo();");
    // We can assign a dotted property of a variable
    rewriteAndExecute(
        "var foo = {};" +
        "foo.x = 3;" +
        "assertEquals(foo.x, 3);");
    assertConsistent(
        "function foo() {}" +
        "var bar = foo;" +
        "bar.x = 3;" +
        "bar.x;");
  }

  public final void testReadBadSuffix() throws Exception {
    checkFails(
        "x.y__;",
        "Properties cannot end in \"__\"");
  }

  /**
   * Tests assignment to unmaskable and maskable globals.
   */
  public final void testSetBadFreeVariable() throws Exception {
    // Array is in Scope.UNMASKABLE_IDENTIFIERS
    checkAddsMessage(
        js(fromString("Array = function () { return [] };")),
        RewriterMessageType.CANNOT_ASSIGN_TO_IDENTIFIER);
    // Throws a ReferenceError
    rewriteAndExecute("assertThrowsMsg(function () { x = 1; }, 'not defined')");
  }

  public final void testSetBadSuffix() throws Exception {
    checkFails(
        "x.y__ = z;",
        "Properties cannot end in \"__\"");
  }

  public final void testSetBadInitialize() throws Exception {
    checkFails(
        "var x__ = 3;",
        "Variables cannot end in \"__\"");
  }

  public final void testSetBadDeclare() throws Exception {
    checkFails(
        "var x__;",
        "Variables cannot end in \"__\"");
  }

  public final void testSetVar() throws Exception {
    checkAddsMessage(
        js(fromString("try {} catch (x__) { x__ = 3; }")),
        RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE);
  }

  public final void testSetReadModifyWriteLocalVar() throws Exception {
    checkFails("x__ *= 2;", "");
    checkFails("x *= y__;", "");

    assertConsistent("var x = 3; x *= 2;");
    assertConsistent("var x = 1; x += 7;");
    assertConsistent("var x = 1; x /= '2';");
    assertConsistent("var o = { x: 'a' }; o.x += 'b'; o;");

    EnumSet<Operator> ops = EnumSet.of(
        Operator.ASSIGN_MUL,
        Operator.ASSIGN_DIV,
        Operator.ASSIGN_MOD,
        Operator.ASSIGN_SUM,
        Operator.ASSIGN_SUB,
        Operator.ASSIGN_LSH,
        Operator.ASSIGN_RSH,
        Operator.ASSIGN_USH,
        Operator.ASSIGN_AND,
        Operator.ASSIGN_XOR,
        Operator.ASSIGN_OR
        );
    for (Operator op : ops) {
      assertConsistent("var x = 41, y = 0, g = [17]; x " +
          op.getSymbol() + " g[y];");
    }
  }

  public final void testSetIncrDecr() throws Exception {
    checkFails("x__--;", "");
    assertConsistent(
        "var x = 2;" +
        "var arr = [--x, x, x--, x, ++x, x, x++, x];" +
        "assertEquals('1,1,1,0,1,1,1,2', arr.join(','));" +
        "arr;");
    assertConsistent(
        "var x = '2';" +
        "var arr = [--x, x, x--, x, ++x, x, x++, x];" +
        "assertEquals('1,1,1,0,1,1,1,2', arr.join(','));" +
        "arr;");
  }

  public final void testSetIncrDecrOnLocals() throws Exception {
    checkFails("++x__;", "");
    assertConsistent(
        "(function () {" +
        "  var x = 2;" +
        "  var arr = [--x, x, x--, x, ++x, x, x++, x];" +
        "  assertEquals('1,1,1,0,1,1,1,2', arr.join(','));" +
        "  return arr;" +
        "})();");
  }

  public final void testSetIncrDecrOfComplexLValues() throws Exception {
    checkFails("arr[x__]--;", "Variables cannot end in \"__\"");
    checkFails("arr__[x]--;", "Variables cannot end in \"__\"");

    assertConsistent(
        "(function () {" +
        "  var o = { x: 2 };" +
        "  var arr = [--o.x, o.x, o.x--, o.x, ++o.x, o.x, o.x++, o.x];" +
        "  assertEquals('1,1,1,0,1,1,1,2', arr.join(','));" +
        "  return arr;" +
        "})();");
  }

  public final void testSetIncrDecrOrderOfAssignment() throws Exception {
    assertConsistent(
        "(function () {" +
        "  var arrs = [1, 2];" +
        "  var j = 0;" +
        "  arrs[++j] *= ++j;" +
        "  assertEquals(2, j);" +
        "  assertEquals(1, arrs[0]);" +
        "  assertEquals(4, arrs[1]);" +
        "  return arrs;" +
        "})();");
    assertConsistent(
        "(function () {" +
        "  var foo = (function () {" +
        "               var k = 0;" +
        "               return function () {" +
        "                 switch (k++) {" +
        "                   case 0: return [10, 20, 30];" +
        "                   case 1: return 1;" +
        "                   case 2: return 2;" +
        "                   default: throw new Error(k);" +
        "                 }" +
        "               };" +
        "             })();" +
        "  return foo()[foo()] -= foo();" +
        "})();"
        );
  }

  public final void testDeletePub() throws Exception {
    checkFails("delete x.foo___;", "Properties cannot end in \"__\"");
    assertConsistent(
        "(function() {" +
        "  var o = { x: 3, y: 4 };" +    // A JSON object.
        "  function ptStr(o) { return '(' + o.x + ',' + o.y + ')'; }" +
        "  var history = [ptStr(o)];" // Record state before deletion.
        "  delete o.y;" +                // Delete
        "  delete o.z;" +                // Not present.  Delete a no-op
        "  history.push(ptStr(o));" +    // Record state after deletion.
        "  return history.toString();" +
        "})();");
    assertConsistent(
        "var alert = 'a';" +
        "var o = { a: 1 };" +
        "delete o[alert];" +
        "assertEquals(undefined, o.a);" +
        "o;");
  }

  public final void testDeleteFails() throws Exception {
    rewriteAndExecute(
        "assertThrowsMsg(function (){delete (function f(){}).name;}," +
        "    'Cannot delete');");
  }

  public final void testDeleteNonLvalue() throws Exception {
    checkFails("delete 4;", "Invalid operand to delete");
  }

  public final void testFuncAnonSimple() throws Exception {
    assertConsistent(
        "var foo = (function () {" +
        "             function foo() {}" +
        "             foo.x = 3;" +
        "             return foo;" +
        "           })();" +
        "foo();" +
        "foo.x;");
  }

  public final void testFuncNamedSimpleDecl() throws Exception {
    rewriteAndExecute(
        "(function () {" +
        "  function foo() {}" +
        "  Object.freeze(foo);" +
        "  foo();" +
        "  try {" +
        "    foo.x = 3;" +
        "  } catch (e) { return; }" +
        "  fail('mutated frozen function');" +
        "})();");
    assertConsistent(
        "function foo() {}" +
        "foo.x = 3;" +
        "foo();" +
        "foo.x;");
    rewriteAndExecute(
        "  function f_() { return 31415; }"
        + "var x = f_();"
        + "assertEquals(x, 31415);");
  }

  public final void testMapSingle() throws Exception {
    checkFails("var o = { x___: p.x, k1: p.y };",
               "Properties cannot end in \"__\"");
  }

  public final void testInstanceof() throws Exception {
    assertConsistent("[ (({}) instanceof Object)," +
                     "  ((new Date) instanceof Date)," +
                     "  (({}) instanceof Date)" +
                     "];");
    assertConsistent("function foo() {}  (new foo) instanceof foo;");
    assertConsistent("function foo() {}  !(({}) instanceof foo);");
  }

  public final void testTypeof() throws Exception {
    rewriteAndExecute("assertThrowsMsg(function () { return typeof ___; }," +
        "'double underscore')");
    assertConsistent("typeof true;");
    assertConsistent("typeof 0;");
    assertConsistent("typeof undefined;");
    assertConsistent("typeof null;");
    assertConsistent("typeof 'string';");
    assertConsistent("typeof function () {};");
    assertConsistent("typeof ({});");
  }

  public final void testMaskingFunction() throws Exception {
    assertAddsMessage(
        "function Goo() { function Goo() {} }",
        MessageType.SYMBOL_REDEFINED,
        MessageLevel.LINT );
    assertAddsMessage(
        "function Goo() { var Goo = 1; }",
        MessageType.MASKING_SYMBOL,
        MessageLevel.LINT );
    assertMessageNotPresent(
        "function Goo() { this.x = 1; }",
        MessageType.MASKING_SYMBOL );
  }

  public final void testMapBadKeySuffix() throws Exception {
    checkAddsMessage(
        js(fromString("var o = { x__: 3 };")),
        RewriterMessageType.PROPERTIES_CANNOT_END_IN_DOUBLE_UNDERSCORE);
  }

  public final void testMapNonEmpty() throws Exception {
    // Ensure that calling an untamed function throws
    rewriteAndExecute(
        "testImports.f = function() {};" +
        "___.grantRead(testImports, 'f');",
        "assertThrowsMsg(function() { f(); }, 'toxic');",
        "");
    // Ensure that calling a tamed function in an object literal works
    rewriteAndExecute(
        "  var f = function() {};"
        + "var m = { f : f };"
        + "m.f();");
    // Ensure that putting an untamed function into an object literal
    // causes an exception.
    rewriteAndExecute(
        "testImports.f = function() {};" +
        "___.grantRead(testImports, 'f');",
        "assertThrowsMsg(function(){({ isPrototypeOf : f });}, 'toxic');",
        ";");
  }

  public final void testLabeledStatement() throws Exception {
    checkFails("IMPORTS___: 1;", "Labels cannot end in \"__\"");
    checkFails("IMPORTS___: while (1);", "Labels cannot end in \"__\"");
    checkFails("while (1) { break x__; }", "Labels cannot end in \"__\"");
    checkFails("while (1) { continue x__; }", "Labels cannot end in \"__\"");
    assertConsistent(
        "var k = 0;" +
        "a: for (var i = 0; i < 10; ++i) {" +
        "  b: for (var j = 0; j < 10; ++j) {" +
        "    if (++k > 5) break a;" +
        "  }" +
        "}" +
        "k;");
    assertConsistent(
        "var k = 0;" +
        "a: for (var i = 0; i < 10; ++i) {" +
        "  b: for (var j = 0; j < 10; ++j) {" +
        "    if (++k > 5) break b;" +
        "  }" +
        "}" +
        "k;");
  }

  /**
   * Tests that the container can get access to
   * "virtual globals" defined in cajoled code.
   */
  public final void testWrapperAccess() throws Exception {
    rewriteAndExecute(
        "",
        "var x = 'test';",
        "if (___.getNewModuleHandler().getImports().x != 'test') {" +
          "fail('Cannot see inside the wrapper');" +
        "}");
  }

  /**
   * Tests that Object.prototype cannot be modified.
   */
  public final void testFrozenObjectPrototype() throws Exception {
    rewriteAndExecute(
        "var success = false;" +
        "try {" +
          "Object.prototype.x = 'X';" +
        "} catch (e){" +
          "success = true;" +
        "}" +
        "if (!success) { fail('Object.prototype not frozen.'); }");
  }

  public final void testStamp() throws Exception {
    rewriteAndExecute(
        "function Foo(){}" +
        "var foo = new Foo();" +
        "Object.freeze(foo);" +
        "var TestMark = cajaVM.Trademark('Test');" +
        "var passed = false;" +
        "try { " +
        "  cajaVM.stamp([TestMark.stamp], foo);" +
        "} catch (e) {" +
        "  if (e.message !== " +
        "      'Can\\'t stamp frozen objects: [object Object]') {" +
        "    fail(e.message);" +
        "  }" +
        "  passed = true;" +
        "}" +
        "if (!passed) { fail ('Able to stamp frozen objects.'); }");
    rewriteAndExecute(
        // Shows how privileged or uncajoled code can stamp
        // frozen objects anyway.
        "___.getNewModuleHandler()." +
        "    getImports().DefineOwnProperty___('stampAnyway', {" +
        "      value: ___.markFuncFreeze(function(stamp, obj) {" +
        "          stamp.mark___(obj);" +
        "        })," +
        "      enumerable: false," +
        "      writable: true," +
        "      configurable: false" +
        "    });",
        "function Foo(){}" +
        "var foo = new Foo();" +
        "Object.freeze(foo);" +
        "var TestMark = cajaVM.Trademark('Test');" +
        "try { " +
        "  stampAnyway(TestMark.stamp, foo);" +
        "} catch (e) {" +
        "  fail(e.message);" +
        "}" +
        "cajaVM.guard(TestMark.guard, foo);",
        "");
    rewriteAndExecute(
        "var foo = {};" +
        "var TestMark = cajaVM.Trademark('Test');" +
        "cajaVM.stamp([TestMark.stamp], foo);" +
        "cajaVM.guard(TestMark.guard, foo);");
    rewriteAndExecute(
        "var foo = {};" +
        "var TestMark = cajaVM.Trademark('Test');" +
        "cajaVM.stamp([TestMark.stamp], foo);" +
        "TestMark.guard.coerce(foo);");
    rewriteAndExecute(
        "var foo = {};" +
        "var TestMark = cajaVM.Trademark('Test');" +
        "var passed = false;" +
        "try { " +
        "  cajaVM.guard(TestMark.guard, foo);" +
        "} catch (e) {" +
        "  if (e.message !== " +
        "      'Specimen does not have the \"Test\" trademark') {" +
        "    fail(e.message);" +
        "  }" +
        "  passed = true;" +
        "}" +
        "if (!passed) { fail ('Able to forge trademarks.'); }");
    rewriteAndExecute(
        "var foo = {};" +
        "var T1Mark = cajaVM.Trademark('T1');" +
        "var T2Mark = cajaVM.Trademark('T2');" +
        "var passed = false;" +
        "try { " +
        "  cajaVM.stamp([T1Mark.stamp], foo);" +
        "  cajaVM.guard(T2Mark.guard, foo);" +
        "} catch (e) {" +
        "  if (e.message !== 'Specimen does not have the \"T2\" trademark') {" +
        "    fail(e.message);" +
        "  }" +
        "  passed = true;" +
        "}" +
        "if (!passed) { fail ('Able to forge trademarks.'); }");
    rewriteAndExecute(
        "var foo = {};" +
        "var bar = Object.create(foo);" +
        "var baz = Object.create(bar);" +
        "var TestMark = cajaVM.Trademark('Test');" +
        "cajaVM.stamp([TestMark.stamp], bar);" +
        "assertFalse(cajaVM.passesGuard(TestMark.guard, foo));" +
        "assertTrue(cajaVM.passesGuard(TestMark.guard, bar));" +
        "assertFalse(cajaVM.passesGuard(TestMark.guard, baz));");
  }

  public final void testIndexOf() throws Exception {
    assertConsistent("''.indexOf('1');");
  }

  public final void testCallback() throws Exception {
    assertConsistent(
        "(function(){}).apply.call(function(a, b) {return a + b;}, {}, [3, 4]);"
        );
    assertConsistent(
        "(function(){}).call.call(function(a, b) {return a + b;}, {}, 3, 4);");
    rewriteAndExecute(
        "var a = [], b = {x:3};\n" +
        "for (var i in b) { a.push(i, b[i]); };" +
        "assertEquals(a.toString(), 'x,3');");
    assertConsistent(
        "Function.prototype.apply.call(" +
        "    function(a, b) {" +
        "      return a + b;" +
        "    }, " +
        "    {}, " +
        "    [3, 4]);");
    assertConsistent(
        "Function.prototype.call.call(" +
        "    function(a, b) {" +
        "      return a + b;" +
        "    }," +
        "    {}," +
        "    3," +
        "    4);");
    assertConsistent(
        "Function.prototype.bind.call(" +
        "    function(a, b) {" +
        "      return a + b;" +
        "    }," +
        "    {}," +
        "    3)(4);");
  }

  /**
   * Tests the WeakMap(opt_useKeyLifetime) abstraction.
   * <p>
   * From here, we are not in a position to test the weak-GC properties this
   * abstraction is designed to provide, nor its O(1) complexity measure.
   * However, we can test that it works as a simple lookup table.
   */
  public final void testTable() throws Exception {
    rewriteAndExecute(
        "var t = new WeakMap();" +
        "var k1 = {};" +
        "var k2 = {};" +
        "var k3 = {};" +
        "t.set(k1, 'v1');" +
        "t.set(k2, 'v2');" +
        "assertEquals(t.get(k1), 'v1');" +
        "assertEquals(t.get(k2), 'v2');" +
        "assertTrue(t.get(k3) === void 0);");
    rewriteAndExecute(
        "var t = new WeakMap(true);" +
        "var k1 = {};" +
        "var k2 = {};" +
        "var k3 = {};" +
        "t.set(k1, 'v1');" +
        "t.set(k2, 'v2');" +
        "assertEquals(t.get(k1), 'v1');" +
        "assertEquals(t.get(k2), 'v2');" +
        "assertTrue(t.get(k3) === void 0);");
    rewriteAndExecute(
        "var t = new WeakMap(true);" +
        "assertThrowsMsg(function(){t.set('foo', 'v1');}," +
        "    'primitive keys');");
    rewriteAndExecute(
        "var t = new WeakMap(true);" +
        "var k1 = {};" +
        "var k2 = Object.create(k1);" +
        "var k3 = Object.create(k2);" +
        "var k4 = Object.create(k3);" +
        "t.set(k2, 'foo');" +
        "t.set(k3, 'bar');" +
        "assertEquals(t.get(k2), 'foo');\n" +
        "assertEquals(t.get(k3), 'bar');\n" +
        "assertTrue(t.get(k1) === void 0);\n" +
        "assertTrue(t.get(k4) === void 0);");
    rewriteAndExecute(
        "var t = new WeakMap();" +
        "var k1 = {};" +
        "var k2 = Object.create(k1);" +
        "var k3 = Object.create(k2);" +
        "var k4 = Object.create(k3);" +
        "t.set(k2, 'foo');" +
        "t.set(k3, 'bar');" +
        "assertEquals(t.get(k2), 'foo');\n" +
        "assertEquals(t.get(k3), 'bar');\n" +
        "assertTrue(t.get(k1) === void 0);\n" +
        "assertTrue(t.get(k4) === void 0);");
    rewriteAndExecute(
        "var t1 = new WeakMap(true);" +
        "var t2 = new WeakMap(true);" +
        "var k = {};" +
        "t1.set(k, 'foo');" +
        "t2.set(k, 'bar');" +
        "assertEquals(t1.get(k), 'foo');" +
        "assertEquals(t2.get(k), 'bar');" +
        "t1.set(k, void 0);" +
        "assertTrue(t1.get(k) === void 0);" +
        "assertEquals(t2.get(k), 'bar');");
    rewriteAndExecute(
        "var t1 = new WeakMap();" +
        "var t2 = new WeakMap();" +
        "var k = {};" +
        "t1.set(k, 'foo');" +
        "t2.set(k, 'bar');" +
        "assertEquals(t1.get(k), 'foo');" +
        "assertEquals(t2.get(k), 'bar');" +
        "t1.set(k, void 0);" +
        "assertTrue(t1.get(k) === void 0);" +
        "assertEquals(t2.get(k), 'bar');");
  }

  /**
   * Tests that begetting works.
   */
  public final void testInheritance() throws Exception {
    rewriteAndExecute(
        "var x = {a:8}, y = Object.create(x); assertTrue(y.a === 8);");
  }

  /**
   * Tests that ES5/3 code can't cause a privilege
   * escalation by calling a tamed exophoric function with null as the
   * this-value.
   * <p>
   * The uncajoled branch of the tests below establish that a null does cause
   * a privilege escalation for normal non-strict JavaScript.
   */
  public final void testNoPrivilegeEscalation() throws Exception {
    rewriteAndExecute("assertTrue([].valueOf.call(null) === cajaVM.USELESS);");
    rewriteAndExecute("assertTrue([].valueOf.apply(null) === cajaVM.USELESS);");
    rewriteAndExecute(
        "assertTrue([].valueOf.bind(null)() === cajaVM.USELESS);");
  }

  /**
   * Tests that the apparent [[Class]] of the tamed JSON object is 'JSON', as
   * it should be according to ES5.  Also tests parse and stringify.
   *
   * See issue 1086
   */
  public final void testJSONClass() throws Exception {
    rewriteAndExecute("assertTrue(''+JSON === '[object JSON]');");
    rewriteAndExecute(
        "assertTrue(({}).toString.call(JSON) === '[object JSON]');");
    rewriteAndExecute(
        "var x = JSON.parse('{\"a\":[{\"b\":33}]}');" +
        "assertEquals(33, x.a[0].b);");
    rewriteAndExecute(
        "var x = JSON.stringify({a:33});" +
        "assertEquals('{\"a\":33}', x);");
    rewriteAndExecute(
        "var pass = false;" +
        "try { var x = JSON.parse('{\"b\":1, \"a___\":33}'); }" +
        "catch (e) { " +
        "  assertTrue(e.message.indexOf('a___') !== -1);" +
        "  pass = true;" +
        "}" +
        "assertTrue(pass);");
  }

  /**
   * Tests Object.getPrototypeOf().
   */
  public final void testGetPrototypeOf() throws Exception {
    rewriteAndExecute(
        "assertEquals(Object.getPrototypeOf({}), Object.prototype);");
    rewriteAndExecute(
        "assertEquals(Object.getPrototypeOf([]), Array.prototype);");
    rewriteAndExecute(
        "function Foo() {}" +
        "var foo = new Foo();" +
        "assertEquals(Object.getPrototypeOf(foo), Foo.prototype);");
  }

  /**
   * Tests Array.forEach
   */
  public final void testArrayForEach() throws Exception {
    rewriteAndExecute(
        "var testArray = ['a', 'b', 'c'];" +
        "var expectedI = 0;" +
        "testArray.forEach(function(item, index, orig) {" +
        "  assertEquals(item, testArray[expectedI]);" +
        "  assertEquals(index, expectedI);" +
        "  assertEquals(orig, testArray);" +
        "  expectedI++; " +
        "});");
  }

  /**
   * Tests assigning to an inherited read-only property
   */
  public final void testCanPut() throws Exception {
    // ES5/3 is consistent with Chrome and Safari in asserting that the
    // ES5 spec was mistaken to prohibit overriding read-only properties.
    // http://wiki.ecmascript.org/doku.php?id=strawman:fixing_override_mistake
    rewriteAndExecute(
        "var a = {};" +
        "Object.defineProperty(a, 'x', {value: 1, writable: false});" +
        "var b = Object.create(a);" +
        "b.x = 2;" +
        "assertEquals(b.x, 2);");
  }

  /**
   * Regression test: Object.defineProperties was nonfunctional.
   */
  public final void testDefineProperties() throws Exception {
    rewriteAndExecute(
        "var o = {};" +
        "Object.defineProperties(o, " +
        "  {p1: {value:1}, p2: {value:2, enumerable:true}});" +
        "function pd(p) { return Object.getOwnPropertyDescriptor(o, p); }" +
        "assertEquals(pd('p1').enumerable, false);" +
        "assertEquals(pd('p2').enumerable, true);" +
        "assertEquals(o.p1, 1);" +
        "assertEquals(o.p2, 2);");
  }

  /**
   * Test that objects inheriting properties marked as writable but not yet
   * fastpathed are actually writable.
   */
  public final void testWritableInheritance() throws Exception {
    rewriteAndExecute(
        "var o = {};" +
        "o.x = 1;" +
        "var o2 = Object.create(o);" +
        "o2.x = 2;");
  }

  /**
   * Tests that assignment returns the rhs, not the result of a setter.
   */
  public final void testAssignmentReturnValue() throws Exception {
    rewriteAndExecute(
        "var o = {};" +
        "Object.defineProperty(o, 'x', {set:function(v) { return 5; }});" +
        "assertEquals(1, o.x=1);");
  }

  public final void testExtensibile() throws Exception {
    rewriteAndExecute(
        "var o = {};" +
        "o.x = 1;" +
        "var o2 = Object.create(o);" +
        "Object.preventExtensions(o2);" +
        "assertThrowsMsg(function () { o2.x = 2; }, 'extensible');");
    rewriteAndExecute(
        "var o = {};" +
        "var o2 = Object.create(o);" +
        "Object.preventExtensions(o2);" +
        "assertThrowsMsg(function () { Array.prototype.sort.call(o2); }," +
        "    'extensible');");
    rewriteAndExecute(
        "var o = {};" +
        "o.x = 1;" +
        "var o2 = Object.create(o);" +
        "o2.y = 1;" +
        "Object.seal(o2);" +
        "assertThrowsMsg(function () { o2.x = 2; }, 'extensible');" +
        "var desc = Object.getOwnPropertyDescriptor(o2, 'y');" +
        "assertEquals(desc.configurable, false);");
  }

  public final void testProxy() throws Exception {
      rewriteAndExecute(
          // Taken from http://wiki.ecmascript.org/doku.php?id=harmony:proxies
          "Object.freeze(Function.prototype);" +
          "function handlerMaker(obj) {" +
          "  return {" +
          "    getOwnPropertyDescriptor: function(name) {" +
          "      var desc = Object.getOwnPropertyDescriptor(obj, name);" +
          "      if (desc !== undefined) { desc.configurable = true; }" +
          "      return desc;" +
          "    }," +
          "    getPropertyDescriptor: function(name) {" +
          "      return {" +
          "          value: obj[name]," +
          "          configurable:true," +
          "          enumerable:true," +
          "          writable:true" +
          "        };" +
          "    }," +
          "    getOwnPropertyNames: function() {" +
          "      return Object.getOwnPropertyNames(obj);" +
          "    }," +
          "    getPropertyNames: function() {" +
          "      return cajaVM.allKeys(obj);" +
          "    }," +
          "    defineProperty: function(name, desc) {" +
          "      Object.defineProperty(obj, name, desc);" +
          "    }," +
          "    delete: function(name) { return delete obj[name]; }," +
          "    fix: function() {" +
          "      var names = Object.getOwnPropertyNames(obj);" +
          "      var len = names.length;" +
          "      var result = {};" +
          "      for (var i = 0; i < len; ++i) {" +
          "        result[names[i]] = Object.getOwnPropertyDescriptor(" +
          "            obj, names[i]);" +
          "      }" +
          "      return result;" +
          "    }," +
          "    has: function(name) { return name in obj; }," +
          "    hasOwn: function(name) {" +
          "      return ({}).hasOwnProperty.call(obj, name);" +
          "    }," +
          "    get: function(name) { return obj[name]; }," +
          "    set: function(name, val) {" +
          "      obj[name] = val;" +
          "      return true;" +
          "    }, " +
          "    enumerate: function() {" +
          "      var result = [];" +
          "      for (var name in obj) { result.push(name); };" +
          "      return result;" +
          "    }," +
          "    keys: function() { return Object.keys(obj); }" +
          "  };" +
          "}" +
          "var o1 = {x: 1, y:function(x) { return x*2; }};" +
          "var obj = Object.create(o1);" +
          "obj.z = 3;" +
          "var proxy = Proxy.create(handlerMaker(obj));" +
          "cajaVM.log('for-in'); var keys = [];" +
          "  for (var i in proxy) { keys.push(i); }" +
          "  assertEquals('x,y,z', ''+keys.sort());" +
          "cajaVM.log('get'); assertEquals(1, proxy.x);" +
          "cajaVM.log('method'); assertEquals(10, proxy.y(5));" +
          "cajaVM.log('has'); assertTrue('x' in proxy);" +
          "cajaVM.log('hasOwn'); " +
          "  assertTrue(({}).hasOwnProperty.call(proxy, 'z'));" +
          "cajaVM.log('set'); proxy.x = 2; assertEquals(2, proxy.x);" +
          "cajaVM.log('desc value');" +
          "  var desc = Object.getOwnPropertyDescriptor(proxy, 'z');" +
          "  assertEquals(3, desc.value);" +
          "cajaVM.log('desc conf'); assertTrue(desc.configurable);" +
          "cajaVM.log('desc writ'); assertTrue(desc.writable);" +
          "cajaVM.log('fix pe');" +
          "  Object.preventExtensions(proxy);" +
          "  assertEquals(false, Object.isExtensible(proxy));" +
          "cajaVM.log('fix seal');" +
          "  proxy = Proxy.create(handlerMaker(obj));" +
          "  Object.seal(proxy);" +
          "  assertEquals(false, Object.isExtensible(proxy));" +
          "  desc = Object.getOwnPropertyDescriptor(proxy, 'z');" +
          "  assertEquals(false, desc.configurable);" +
          "cajaVM.log('fix freeze and arrays');" +
          "  proxy = Proxy.create(handlerMaker(['hi', 'there']));" +
          "  /* can't intercept numerics */" +
          "  assertEquals(void 0, proxy[1]);" +
          "  Object.freeze(proxy);" +
          "  assertTrue(Object.isFrozen(proxy));" +
          "  /* can have numeric properties after fixing */" +
          "  assertEquals('there', proxy[1]);" +
          "cajaVM.log('keys');" +
          "  proxy = Proxy.create(handlerMaker(obj));" +
          "  assertEquals('x,z', ''+Object.keys(proxy).sort());" +
          "cajaVM.log('defineProperty');" +
          "  Object.defineProperty(proxy, 'w', { value: 4 });" +
          "  assertEquals(4, obj.w);" +
          "cajaVM.log('extensibility');" +
          "  var o2 = {};" +
          "  var o3 = Object.create(o2);" +
          "  assertThrowsMsg(function(){Proxy.create(handlerMaker({}), o3);}," +
          "      'extensible');" +
          "  Object.preventExtensions(o3);" +
          "  assertThrowsMsg(function(){Proxy.create(handlerMaker({}), o3);}," +
          "      'extensible');" +
          "  Object.preventExtensions(o2);" +
          "  Proxy.create(handlerMaker({}), o3);" +
          "cajaVM.log('function as object');" +
          "  var f = function joe(){return 3;};" +
          "  proxy = Proxy.create(handlerMaker(f));" +
          "  assertEquals('joe', proxy.name);" +
          "cajaVM.log('function proxy');" +
          "  proxy = Proxy.createFunction(handlerMaker(f)," +
          "      function(){return 5;}," +
          "      function(){" +
          "        this.x = 1; " +
          "        this.f = function(){return this.x;};" +
          "      });" +
          "  assertEquals('joe', proxy.name);" +
          "  assertEquals(5, proxy());" +
          "  assertEquals(1, (new proxy()).f());" +
          "cajaVM.log('missing get');" +
          "  var noGetHandler = handlerMaker(obj);" +
          "  delete noGetHandler.get;" +
          "  proxy = Proxy.create(noGetHandler);" +
          "  assertEquals(3, proxy.z);" +
          "  /* proxy.x assigned to 2 in set test */" +
          "  assertEquals(2, proxy.x);" +
          "cajaVM.log('getOwnPropertyNames');" +
          "  proxy = Proxy.create(handlerMaker(obj));" +
          "  assertEquals('w,x,z'," +
          "      '' + Object.getOwnPropertyNames(proxy).sort());" +
          "  var o4 = {d:4};" +
          "  Object.freeze(o4);" +
          "  proxy = Proxy.create(handlerMaker({a:1, b:2, c:3}), o4);" +
          "  assertEquals('a,b,c'," +
          "      '' + Object.getOwnPropertyNames(proxy).sort());" +
          "cajaVM.log('getPropertyNames');" +
          "  var allKeys = cajaVM.allKeys(proxy).sort();" +
          "  assertEquals('a,b,c', '' + allKeys.slice(0,3));" +
          "  assertTrue('d' !== allKeys[3]);");
  }

  public final void testElision() throws Exception {
    rewriteAndExecute("var x = [0,,2,];\n" +
        "assertEquals(3, x.length);" +
        "assertEquals(0, x[0]);" +
        "assertEquals(undefined, x[1]);" +
        "assertEquals(2, x[2]);"
    );
  }

  public final void testBindOk() throws Exception {
    // Checks that bound functions get marked as safe to execute.
    rewriteAndExecute("{foo: (function (){}).bind(Number)}");
  }

  public final void testNoSetter() throws Exception {
    // Checks that configurable properties with no setter
    // and no backing property.
    rewriteAndExecute(
        "testImports.x = {};" +
        "testImports.x.DefineOwnProperty___('g'," +
        "    {configurable: true, get:___.markFunc(function(){}) });" +
        "delete testImports.x.g;" +
        "___.grantRead(testImports,'x');",
        "assertThrowsMsg(function(){x.g = 1;}, 'no setter');",
        "");
  }

  @Override
  public void setUp() throws Exception {
    super.setUp();
    es53Rewriter = new ES53Rewriter(TestBuildInfo.getInstance(), mq, false);
    setRewriter(es53Rewriter);
  }

  private static String assertThrowsMsg =
      "function assertThrowsMsg(f, msg) {\n" +
      "  try { f(); } catch (e) {\n" +
      "    assertTrue(e.message.indexOf(msg) > -1);\n" +
      "    return true;\n" +
      "  }\n" +
      "  return false;\n" +
      "}\n";

  @Override
  protected Object executePlain(String caja) throws IOException {
    mq.getMessages().clear();
    return RhinoTestBed.runJs(
        new Executor.Input(
            getClass(), "../../../../../js/jsunit/2.2/jsUnitCore.js"),
        new Executor.Input(assertThrowsMsg, "assertThrowsMsg"),
        new Executor.Input(caja, getName() + "-uncajoled"));
  }

  @Override
  protected Object rewriteAndExecute(String pre, String caja, String post)
      throws IOException, ParseException {
    mq.getMessages().clear();

    List<Statement> children = Lists.newArrayList();
    children.add(js(fromString(caja, is)));
    String cajoledJs = render(rewriteTopLevelNode(
        new UncajoledModule(new Block(FilePosition.UNKNOWN, children))));

    assertNoErrors();

    final String[] assertFunctions = new String[] {
        "fail",
        "assertEquals",
        "assertTrue",
        "assertFalse",
        "assertLessThan",
        "assertNull",
        "assertThrows",
        "assertThrowsMsg",
    };

    StringBuilder importsSetup = new StringBuilder();
    importsSetup.append(
        "var testImports = ___.copy(___.whitelistAll(___.sharedImports));");
    for (String f : assertFunctions) {
      importsSetup
          .append("testImports." + f + " = ___.markFuncFreeze(" + f + ");")
          .append("___.grantRead(testImports, '" + f + "');");
    }
    importsSetup.append(
        "___.getNewModuleHandler().setImports(___.whitelistAll(testImports));");

    Object result = RhinoTestBed.runJs(
        new Executor.Input(
            getClass(), "/com/google/caja/plugin/console-stubs.js"),
        new Executor.Input(
            getClass(), "../../../../../js/json_sans_eval/json_sans_eval.js"),
        new Executor.Input(getClass(), "/com/google/caja/es53.js"),
        new Executor.Input(
            getClass(), "../../../../../js/jsunit/2.2/jsUnitCore.js"),
        new Executor.Input(assertThrowsMsg, "assertThrowsMsg"),
        new Executor.Input(
            getClass(), "/com/google/caja/log-to-console.js"),
        new Executor.Input(
            importsSetup.toString(),
            getName() + "-test-fixture"),
        new Executor.Input(pre, getName()),
        // Load the cajoled code.
        new Executor.Input(cajoledJs, getName() + "-cajoled"),
        new Executor.Input(post, getName()),
        // Return the output field as the value of the run.
        new Executor.Input(
            "___.getNewModuleHandler().getLastValue();", getName()));

    assertNoErrors();
    return result;
  }
}
TOP

Related Classes of com.google.caja.parser.quasiliteral.ES53RewriterTest$TestUriFetcher

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.