summaryrefslogtreecommitdiffstats
path: root/js/src/tests/non262/extensions/setImmutablePrototype.js
blob: 9d107472e5b05e5e4d97feede025621c76c35070 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/*
 * Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

var gTestfile = "setImmutablePrototype.js";
//-----------------------------------------------------------------------------
var BUGNUMBER = 1052139;
var summary =
  "Implement JSAPI and a shell function to prevent modifying an extensible " +
  "object's [[Prototype]]";

print(BUGNUMBER + ": " + summary);

/**************
 * BEGIN TEST *
 **************/

if (typeof evaluate !== "function")
{
  // Not totally faithful semantics, but approximately close enough for this
  // test's purposes.
  evaluate = eval;
}

var usingRealSetImmutablePrototype = true;

if (typeof setImmutablePrototype !== "function")
{
  usingRealSetImmutablePrototype = false;

  if (typeof SpecialPowers !== "undefined")
  {
    setImmutablePrototype =
      SpecialPowers.Cu.getJSTestingFunctions().setImmutablePrototype;
  }
}

if (typeof wrap !== "function")
{
  // good enough
  wrap = function(x) { return x; };
}

function setViaProtoSetter(obj, newProto)
{
  var setter =
    Object.getOwnPropertyDescriptor(Object.prototype, "__proto__").set;
  setter.call(obj, newProto);
}

function checkPrototypeMutationFailure(obj, desc)
{
  var initialProto = Object.getPrototypeOf(obj);

  // disconnected from any [[Prototype]] chains for use on any object at all
  var newProto = Object.create(null);

  function tryMutate(func, method)
  {
    try
    {
      func(obj, newProto);
      throw new Error(desc + ": no error thrown, prototype " +
                      (Object.getPrototypeOf(obj) === initialProto
                       ? "wasn't"
                       : "was") +
                      " changed");
    }
    catch (e)
    {
      // Note: This is always a TypeError from *this* global object, because
      //       Object.setPrototypeOf and the __proto__ setter come from *this*
      //       global object.
      assertEq(e instanceof TypeError, true,
               desc + ": should have thrown TypeError setting [[Prototype]] " +
               "via " + method + ", got " + e);
      assertEq(Object.getPrototypeOf(obj), initialProto,
               desc + ": shouldn't observe [[Prototype]] change");
    }
  }

  tryMutate(Object.setPrototypeOf, "Object.setPrototypeOf");
  tryMutate(setViaProtoSetter, "__proto__ setter");
}

function runNormalTests(global)
{
  if (typeof setImmutablePrototype !== "function")
  {
    print("no testable setImmutablePrototype function available, skipping tests");
    return;
  }

  // regular old object, non-null [[Prototype]]

  var emptyLiteral = global.evaluate("({})");
  assertEq(setImmutablePrototype(emptyLiteral), true);
  checkPrototypeMutationFailure(emptyLiteral, "empty literal");

  // regular old object, null [[Prototype]]

  var nullProto = global.Object.create(null);
  assertEq(setImmutablePrototype(nullProto), true);
  checkPrototypeMutationFailure(nullProto, "nullProto");

  // Shocker: SpecialPowers's little mind doesn't understand proxies.  Abort.
  if (!usingRealSetImmutablePrototype)
    return;

  // direct proxies

  var emptyTarget = global.evaluate("({})");
  var directProxy = new global.Proxy(emptyTarget, {});
  assertEq(setImmutablePrototype(directProxy), true);
  checkPrototypeMutationFailure(directProxy, "direct proxy to empty target");
  checkPrototypeMutationFailure(emptyTarget, "empty target");

  var anotherTarget = global.evaluate("({})");
  var anotherDirectProxy = new global.Proxy(anotherTarget, {});
  assertEq(setImmutablePrototype(anotherTarget), true);
  checkPrototypeMutationFailure(anotherDirectProxy, "another direct proxy to empty target");
  checkPrototypeMutationFailure(anotherTarget, "another empty target");

  var nestedTarget = global.evaluate("({})");
  var nestedProxy = new global.Proxy(new Proxy(nestedTarget, {}), {});
  assertEq(setImmutablePrototype(nestedProxy), true);
  checkPrototypeMutationFailure(nestedProxy, "nested proxy to empty target");
  checkPrototypeMutationFailure(nestedTarget, "nested target");

  // revocable proxies

  var revocableTarget = global.evaluate("({})");
  var revocable = global.Proxy.revocable(revocableTarget, {});
  assertEq(setImmutablePrototype(revocable.proxy), true);
  checkPrototypeMutationFailure(revocableTarget, "revocable target");
  checkPrototypeMutationFailure(revocable.proxy, "revocable proxy");

  assertEq(revocable.revoke(), undefined);
  try
  {
    setImmutablePrototype(revocable.proxy);
    throw new Error("expected to throw on revoked proxy");
  }
  catch (e)
  {
    // Note: In the cross-compartment case, this is a TypeError from |global|,
    //       because the proxy's |setImmutablePrototype| method is what actually
    //       throws here. That's why we check for TypeError from either global.
    //       (Usually the method simply sets |*succeeded| to false and the
    //       caller handles or throws as needed after that.  But not here.)
    assertEq(e instanceof global.TypeError || e instanceof TypeError, true,
             "expected TypeError, instead threw " + e);
  }

  var anotherRevocableTarget = global.evaluate("({})");
  assertEq(setImmutablePrototype(anotherRevocableTarget), true);
  checkPrototypeMutationFailure(anotherRevocableTarget, "another revocable target");

  var anotherRevocable = global.Proxy.revocable(anotherRevocableTarget, {});
  checkPrototypeMutationFailure(anotherRevocable.proxy, "another revocable target");

  assertEq(anotherRevocable.revoke(), undefined);
  try
  {
    var rv = setImmutablePrototype(anotherRevocable.proxy);
    throw new Error("expected to throw on another revoked proxy, returned " + rv);
  }
  catch (e)
  {
    // NOTE: Again from |global| or |this|, as above.
    assertEq(e instanceof global.TypeError || e instanceof TypeError, true,
             "expected TypeError, instead threw " + e);
  }
}

var global = this;
runNormalTests(global); // same-global

if (typeof newGlobal === "function")
{
  var otherGlobal = newGlobal();
  var subsumingNothing = newGlobal({ principal: 0 });
  var subsumingEverything = newGlobal({ principal: ~0 });

  runNormalTests(otherGlobal); // cross-global
  runNormalTests(subsumingNothing);
  runNormalTests(subsumingEverything);
}

/******************************************************************************/

if (typeof reportCompare === "function")
  reportCompare(true, true);

print("Tests complete");