Bug 858381 - Implement non-writable array lengths, and add a boatload of tests. r=jorendorff and r=bhackett for the major parts of this, r=jandem for the methodjit changes, r=jimb on a debugger test change, r=nmatsakis for the parallel test. (More details available in the bug, where individual components of the fix were separately reviewed.)
authorJeff Walden <jwalden@mit.edu>
Tue, 19 Mar 2013 17:12:06 -0700
changeset 140780 8eac2a78a7918ad0bd4d7d05f529f2916d2eeb22
parent 140779 96509dd0406deeaacd1ba2b2bc7a8ab2b612e128
child 140781 5d07eabd9a876afd2a98183958849dc7cf3e919f
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff, bhackett, jandem, jimb, nmatsakis
bugs858381
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 858381 - Implement non-writable array lengths, and add a boatload of tests. r=jorendorff and r=bhackett for the major parts of this, r=jandem for the methodjit changes, r=jimb on a debugger test change, r=nmatsakis for the parallel test. (More details available in the bug, where individual components of the fix were separately reviewed.) More random details: Various JIT components required updating for this. In the case of some methodjit bits, this meant simply disabling those optimizations. This patch also, passing, improves the Array.prototype.push method's fast-path to work for any number of provided arguments, not just one. The patch also fixes a few pre-existing bugs in how array length setting works and includes the appropriate tests. (If anyone notices, it's because they were a test in a test suite.) I also added a ParallelArray test that verifies that arrays with non-writable length function correctly in parallel code. We bail before getting there now, because Object.defineProperty isn't parallel-friendly, but if it ever becomes so, hopefully the test will start failing. Hello, is this thing on?
js/src/ion/CodeGenerator.cpp
js/src/jit-test/tests/arrays/ion-pop-denseinitializedlength-less-than-length.js
js/src/jit-test/tests/arrays/ion-pop-nonwritable-length.js
js/src/jit-test/tests/arrays/ion-push-nonwritable-length.js
js/src/jit-test/tests/arrays/ion-shift-nonwritable-length.js
js/src/jit-test/tests/arrays/length-set-after-define-nonconfigurable.js
js/src/jit-test/tests/arrays/length-set-after-has-sparse.js
js/src/jit-test/tests/arrays/nonwritable-length-grow-capacity.js
js/src/jit-test/tests/arrays/pop-nonwritable-length-denseinitializedlength-below-length.js
js/src/jit-test/tests/arrays/push-densely-loopy-nonwritable-length.js
js/src/jit-test/tests/arrays/push-densely-nonwritable-length.js
js/src/jit-test/tests/arrays/push-slowly-loopy-nonwritable-length.js
js/src/jit-test/tests/arrays/push-slowly-nonwritable-length.js
js/src/jit-test/tests/arrays/setelem-one-past-nonwritable-length.js
js/src/jit-test/tests/arrays/splice-nonwritable-length.js
js/src/jit-test/tests/arrays/unshift-nonwritable-length.js
js/src/jit-test/tests/basic/splice-fail-step-16.js
js/src/jit-test/tests/debug/Object-apply-02.js
js/src/jit-test/tests/parallelarray/alloc-array-nonwritable.js
js/src/js.msg
js/src/jsarray.cpp
js/src/jsarray.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/methodjit/FastBuiltins.cpp
js/src/tests/ecma_5/Array/length-nonwritable-redefine-nop.js
js/src/tests/ecma_5/Array/length-truncate-nonconfigurable-sparse.js
js/src/tests/ecma_5/Array/length-truncate-nonconfigurable.js
js/src/tests/ecma_5/Array/length-truncate-with-indexed.js
js/src/tests/ecma_5/Array/pop-empty-nonwritable.js
js/src/tests/ecma_5/extensions/array-pop-proxy.js
js/src/tests/ecma_5/strict/15.4.5.1.js
js/src/tests/js1_6/Array/regress-304828.js
js/src/vm/ObjectImpl.h
js/src/vm/Shape.cpp
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -4157,16 +4157,25 @@ CodeGenerator::emitArrayPopShift(LInstru
         masm.loadElementTypedOrValue(BaseIndex(elementsTemp, lengthTemp, TimesEight), out,
                                      mir->needsHoleCheck(), ool->entry());
     } else {
         JS_ASSERT(mir->mode() == MArrayPopShift::Shift);
         masm.loadElementTypedOrValue(Address(elementsTemp, 0), out, mir->needsHoleCheck(),
                                      ool->entry());
     }
 
+    // Handle the failure case when the array length is non-writable in the
+    // OOL path.  (Unlike in the adding-an-element cases, we can't rely on the
+    // capacity <= length invariant for such arrays to avoid an explicit
+    // check.)
+    Address elementFlags(elementsTemp, ObjectElements::offsetOfFlags());
+    Imm32 bit(ObjectElements::NONWRITABLE_ARRAY_LENGTH);
+    masm.branchTest32(Assembler::NonZero, elementFlags, bit, ool->entry());
+
+    // Now adjust length and initializedLength.
     masm.store32(lengthTemp, Address(elementsTemp, ObjectElements::offsetOfLength()));
     masm.store32(lengthTemp, Address(elementsTemp, ObjectElements::offsetOfInitializedLength()));
 
     if (mir->mode() == MArrayPopShift::Shift) {
         // Don't save the temp registers.
         RegisterSet temps;
         temps.add(elementsTemp);
         temps.add(lengthTemp);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/ion-pop-denseinitializedlength-less-than-length.js
@@ -0,0 +1,49 @@
+load(libdir + "asserts.js");
+
+function f(arr)
+{
+  arr.pop();
+}
+
+var N = 100;
+
+function test()
+{
+  // Create an array of arrays, to be iterated over for [].pop-calling.  We
+  // can't just loop on pop on a single array with non-writable length because
+  // pop throws when called on an array with non-writable length.
+  var arrs = [];
+  for (var i = 0; i < N; i++)
+    arrs.push([0, 1, 2, 3]);
+
+  // Test Ion-pop where dense initialized length < length.
+  var a = [0, 1, 2];
+  a.length = 4;
+
+  arrs.push(a);
+
+  for (var i = 0, sz = arrs.length; i < sz; i++)
+    f(arrs[i]);
+
+  return arrs;
+}
+
+var arrs = test();
+assertEq(arrs.length, N + 1);
+for (var i = 0; i < N; i++)
+{
+  assertEq(arrs[i].length, 3, "unexpected length for arrs[" + i + "]");
+  assertEq(arrs[i][0], 0, "bad element for arrs[" + i + "][0]");
+  assertEq(arrs[i][1], 1, "bad element for arrs[" + i + "][1]");
+  assertEq(arrs[i][2], 2, "bad element for arrs[" + i + "][2]");
+  assertEq(3 in arrs[i], false, "shouldn't be a third element");
+  assertEq(arrs[i][3], undefined);
+}
+
+var a = arrs[N];
+assertEq(a.length, 3, "unexpected length for arrs[" + i + "]");
+assertEq(a[0], 0, "bad element for arrs[" + i + "][0]");
+assertEq(a[1], 1, "bad element for arrs[" + i + "][1]");
+assertEq(a[2], 2, "bad element for arrs[" + i + "][2]");
+assertEq(3 in a, false, "shouldn't be a third element");
+assertEq(a[3], undefined);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/ion-pop-nonwritable-length.js
@@ -0,0 +1,48 @@
+load(libdir + "asserts.js");
+
+function f(arr)
+{
+  arr.pop();
+}
+
+var N = 100;
+
+function test(out)
+{
+  // Create an array of arrays, to be iterated over for [].pop-calling.  We
+  // can't just loop on pop on a single array with non-writable length because
+  // pop throws when called on an array with non-writable length.
+  var arrs = out.arrs = [];
+  for (var i = 0; i < N; i++)
+    arrs.push([0, 1, 2, 3]);
+
+  // Use a much-greater capacity than the eventual non-writable length.
+  var a = [0, 1, 2, 3, 4, 5, 6, 7];
+  Object.defineProperty(a, "length", { writable: false, value: 4 });
+
+  arrs.push(a);
+
+  for (var i = 0, sz = arrs.length; i < sz; i++)
+    f(arrs[i]);
+}
+
+var obj = {};
+
+assertThrowsInstanceOf(function() { test(obj); }, TypeError);
+
+var arrs = obj.arrs;
+assertEq(arrs.length, N + 1);
+for (var i = 0; i < N; i++)
+{
+  assertEq(arrs[i].length, 3, "unexpected length for arrs[" + i + "]");
+  assertEq(arrs[i][0], 0, "bad element for arrs[" + i + "][0]");
+  assertEq(arrs[i][1], 1, "bad element for arrs[" + i + "][1]");
+  assertEq(arrs[i][2], 2, "bad element for arrs[" + i + "][2]");
+  assertEq(3 in arrs[i], false, "shouldn't be a third element");
+  assertEq(arrs[i][3], undefined);
+}
+
+var a = arrs[N];
+assertEq(a.hasOwnProperty(3), false, "should have been deleted before throw");
+assertEq(a[3], undefined);
+assertEq(a.length, 4, "length shouldn't have been changed");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/ion-push-nonwritable-length.js
@@ -0,0 +1,61 @@
+function f(arr)
+{
+  assertEq(arr.push(4), 5); // if it doesn't throw :-)
+}
+
+function test(out)
+{
+  // Create an array of arrays, to be iterated over for [].push-calling.  We
+  // can't just loop on push on a single array with non-writable length because
+  // push throws when called on an array with non-writable length.
+  var arrs = out.arrs = [];
+  for (var i = 0; i < 100; i++)
+    arrs.push([0, 1, 2, 3]);
+
+  // Use a much-greater capacity than the eventual non-writable length, so that
+  // the inline-push will work.
+  var a = [0, 1, 2, 3, 4, 5, 6, 7];
+  Object.defineProperty(a, "length", { writable: false, value: 4 });
+
+  arrs.push(a);
+
+  for (var i = 0, sz = arrs.length; i < sz; i++)
+  {
+    var arr = arrs[i];
+    f(arr);
+  }
+}
+
+var obj = {};
+var a, arrs;
+
+try
+{
+  test(obj);
+  throw new Error("didn't throw!");
+}
+catch (e)
+{
+  assertEq(e instanceof TypeError, true, "expected TypeError, got " + e);
+
+  arrs = obj.arrs;
+  assertEq(arrs.length, 101);
+  for (var i = 0; i < 100; i++)
+  {
+    assertEq(arrs[i].length, 5, "unexpected length for arrs[" + i + "]");
+    assertEq(arrs[i][0], 0, "bad element for arrs[" + i + "][0]");
+    assertEq(arrs[i][1], 1, "bad element for arrs[" + i + "][1]");
+    assertEq(arrs[i][2], 2, "bad element for arrs[" + i + "][2]");
+    assertEq(arrs[i][3], 3, "bad element for arrs[" + i + "][3]");
+    assertEq(arrs[i][4], 4, "bad element for arrs[" + i + "][4]");
+  }
+
+  a = arrs[100];
+  assertEq(a[0], 0, "bad element for a[" + i + "]");
+  assertEq(a[1], 1, "bad element for a[" + i + "]");
+  assertEq(a[2], 2, "bad element for a[" + i + "]");
+  assertEq(a[3], 3, "bad element for a[" + i + "]");
+  assertEq(a.hasOwnProperty(4), false, "element addition should have thrown");
+  assertEq(a[4], undefined);
+  assertEq(a.length, 4, "length shouldn't have been changed");
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/ion-shift-nonwritable-length.js
@@ -0,0 +1,59 @@
+function f(arr)
+{
+  assertEq(arr.shift(), 0);
+}
+
+function test(out)
+{
+  // Create an array of arrays, to be iterated over for [].shift-calling.  We
+  // can't just loop on shift on a single array with non-writable length because
+  // shift throws when called on an array with non-writable length.
+  var arrs = out.arrs = [];
+  for (var i = 0; i < 100; i++)
+    arrs.push([0, 1, 2, 3]);
+
+  // Use a much-greater capacity than the eventual non-writable length.
+  var a = [0, 1, 2, 3, 4, 5, 6, 7];
+  Object.defineProperty(a, "length", { writable: false, value: 4 });
+
+  arrs.push(a);
+
+  for (var i = 0, sz = arrs.length; i < sz; i++)
+  {
+    var arr = arrs[i];
+    f(arr);
+  }
+}
+
+var obj = {};
+var a, arrs;
+
+try
+{
+  test(obj);
+  throw new Error("didn't throw!");
+}
+catch (e)
+{
+  assertEq(e instanceof TypeError, true, "expected TypeError, got " + e);
+
+  arrs = obj.arrs;
+  assertEq(arrs.length, 101);
+  for (var i = 0; i < 100; i++)
+  {
+    assertEq(arrs[i].length, 3, "unexpected length for arrs[" + i + "]");
+    assertEq(arrs[i][0], 1, "bad element for arrs[" + i + "][0]");
+    assertEq(arrs[i][1], 2, "bad element for arrs[" + i + "][1]");
+    assertEq(arrs[i][2], 3, "bad element for arrs[" + i + "][2]");
+    assertEq(3 in arrs[i], false, "shouldn't be a third element");
+    assertEq(arrs[i][3], undefined);
+  }
+
+  a = arrs[100];
+  assertEq(a[0], 1, "bad element for a[" + i + "]");
+  assertEq(a[1], 2, "bad element for a[" + i + "]");
+  assertEq(a[2], 3, "bad element for a[" + i + "]");
+  assertEq(a.hasOwnProperty(3), false, "should have been deleted before throw");
+  assertEq(a[3], undefined);
+  assertEq(a.length, 4, "length shouldn't have been changed");
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/length-set-after-define-nonconfigurable.js
@@ -0,0 +1,7 @@
+var arr = [1];
+Object.defineProperty(arr, 1, { value: undefined, configurable: false });
+
+// no particular reason for 9 -- just enough to trigger property-cache code,
+// maybe start JITting a little
+for (var y = 0; y < 9; y++)
+  arr.length = 1;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/length-set-after-has-sparse.js
@@ -0,0 +1,9 @@
+var arr = [];
+Object.defineProperty(arr, 4, {
+  configurable: true,
+  enumerable: false,
+  writable: false,
+  value: undefined
+});
+for (var y = 0; y < 2; y++)
+  arr.length = 0;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/nonwritable-length-grow-capacity.js
@@ -0,0 +1,2 @@
+var arr = Object.defineProperty([], "length", { writable: false, value: 12 });
+arr[11] = true;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/pop-nonwritable-length-denseinitializedlength-below-length.js
@@ -0,0 +1,49 @@
+load(libdir + "asserts.js");
+
+function f(arr)
+{
+  assertEq(arr.pop(), undefined); // if it doesn't throw
+}
+
+var N = 100;
+
+function basic(out)
+{
+  // Create an array of arrays, to be iterated over for [].pop-calling.  We
+  // can't just loop on pop on a single array with non-writable length because
+  // pop throws when called on an array with non-writable length.
+  var arrs = out.arrs = [];
+  for (var i = 0; i < N; i++)
+  {
+    var arr = [0, 1, 2, 3, 4];
+    arr.length = 6;
+    arrs.push(arr);
+  }
+
+  var a = [0, 1, 2, 3, 4];
+  Object.defineProperty(a, "length", { writable: false, value: 6 });
+
+  arrs.push(a);
+
+  for (var i = 0, sz = arrs.length; i < sz; i++)
+    f(arrs[i]);
+}
+
+var obj = {};
+var arrs, a;
+
+assertThrowsInstanceOf(function() { basic(obj); }, TypeError);
+
+var arrs = obj.arrs;
+assertEq(arrs.length, N + 1);
+for (var i = 0; i < N; i++)
+{
+  assertEq(arrs[i].length, 5, "unexpected length for arrs[" + i + "]");
+  assertEq(arrs[i].hasOwnProperty(5), false,
+           "element not deleted for arrs[" + i + "]");
+}
+
+var a = arrs[N];
+assertEq(a.hasOwnProperty(5), false);
+assertEq(a[5], undefined);
+assertEq(a.length, 6);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/push-densely-loopy-nonwritable-length.js
@@ -0,0 +1,56 @@
+// Force recognition of a known-constant.
+var push = Array.prototype.push;
+
+function f(arr)
+{
+  // Push an actual constant to trigger JIT-inlining of the effect of the push.
+  push.call(arr, 99);
+}
+
+function basic(out)
+{
+  // Create an array of arrays, to be iterated over for [].push-calling.  We
+  // can't just loop on push on a single array with non-writable length because
+  // push throws when called on an array with non-writable length.
+  var arrs = out.arrs = [];
+  for (var i = 0; i < 100; i++)
+    arrs.push([]);
+
+  // Use a much-greater capacity than the eventual non-writable length.
+  var a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
+  Object.defineProperty(a, "length", { writable: false, value: 6 });
+
+  arrs.push(a);
+
+  for (var i = 0, sz = arrs.length; i < sz; i++)
+  {
+    var arr = arrs[i];
+    f(arr);
+  }
+}
+
+var obj = {};
+var arrs, a;
+
+try
+{
+  basic(obj);
+  throw new Error("didn't throw!");
+}
+catch (e)
+{
+  assertEq(e instanceof TypeError, true, "expected TypeError, got " + e);
+
+  arrs = obj.arrs;
+  assertEq(arrs.length, 101);
+  for (var i = 0; i < 100; i++)
+  {
+    assertEq(arrs[i].length, 1, "unexpected length for arrs[" + i + "]");
+    assertEq(arrs[i][0], 99, "bad element for arrs[" + i + "]");
+  }
+
+  a = arrs[100];
+  assertEq(a.hasOwnProperty(6), false);
+  assertEq(a[6], undefined);
+  assertEq(a.length, 6);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/push-densely-nonwritable-length.js
@@ -0,0 +1,31 @@
+function f(arr, v)
+{
+  arr.push(v);
+}
+
+function basic(out)
+{
+  // Use a much-greater capacity than the eventual non-writable length.
+  var a = out.a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
+  Object.defineProperty(a, "length", { writable: false, value: 6 });
+
+  f(a, 99);
+}
+
+var obj = {};
+var a;
+
+try
+{
+  basic(obj);
+  throw new Error("didn't throw!");
+}
+catch (e)
+{
+  assertEq(e instanceof TypeError, true, "expected TypeError, got " + e);
+
+  a = obj.a;
+  assertEq(a.hasOwnProperty(6), false);
+  assertEq(a[6], undefined);
+  assertEq(a.length, 6);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/push-slowly-loopy-nonwritable-length.js
@@ -0,0 +1,51 @@
+load(libdir + "asserts.js");
+
+function f(arr, v1, v2)
+{
+  // Ensure array_push_slowly is called by passing more than one argument.
+  arr.push(v1, v2);
+}
+
+var N = 100;
+
+function test(out)
+{
+  // Create an array of arrays, to be iterated over for [].push-calling.  We
+  // can't just loop on push on a single array with non-writable length because
+  // push throws when called on an array with non-writable length.
+  var arrs = out.arrs = [];
+  for (var i = 0; i < N; i++)
+    arrs.push([]);
+
+  // Use a much-greater capacity than the eventual non-writable length.
+  var a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
+  Object.defineProperty(a, "length", { writable: false, value: 6 });
+
+  arrs.push(a);
+
+  for (var i = 0, sz = arrs.length; i < sz; i++)
+  {
+    var arr = arrs[i];
+    f(arr, 8675309, 3141592);
+  }
+}
+
+var obj = {};
+
+assertThrowsInstanceOf(function() { test(obj); }, TypeError);
+
+var arrs = obj.arrs;
+assertEq(arrs.length, N + 1);
+for (var i = 0; i < N; i++)
+{
+  assertEq(arrs[i].length, 2, "unexpected length for arrs[" + i + "]");
+  assertEq(arrs[i][0], 8675309, "bad element for arrs[" + i + "][0]");
+  assertEq(arrs[i][1], 3141592, "bad element for arrs[" + i + "][1]");
+}
+
+var a = arrs[N];
+assertEq(a.hasOwnProperty(6), false);
+assertEq(a[6], undefined);
+assertEq(a.hasOwnProperty(7), false);
+assertEq(a[7], undefined);
+assertEq(a.length, 6);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/push-slowly-nonwritable-length.js
@@ -0,0 +1,24 @@
+load(libdir + "asserts.js");
+
+function f(arr, v1, v2)
+{
+  // Ensure array_push_slowly is called by passing more than one argument.
+  arr.push(v1, v2);
+}
+
+function basic()
+{
+  // Use a much-greater capacity than the eventual non-writable length.
+  var a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
+  Object.defineProperty(a, "length", { writable: false, value: 6 });
+
+  assertThrowsInstanceOf(() => f(a, 8675309, 3141592), TypeError);
+
+  assertEq(a.hasOwnProperty(6), false);
+  assertEq(a[6], undefined);
+  assertEq(a.hasOwnProperty(7), false);
+  assertEq(a[7], undefined);
+  assertEq(a.length, 6);
+}
+
+basic();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/setelem-one-past-nonwritable-length.js
@@ -0,0 +1,20 @@
+function f(arr, i, v)
+{
+  arr[i] = v;
+}
+
+function test()
+{
+  // Use a much-greater capacity than the eventual non-writable length.
+  var a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
+  Object.defineProperty(a, "length", { writable: false, value: 6 });
+
+  for (var i = 0; i < 100; i++)
+    f(a, a.length, i);
+
+  assertEq(a.hasOwnProperty(6), false);
+  assertEq(a[6], undefined);
+  assertEq(a.length, 6);
+}
+
+test();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/splice-nonwritable-length.js
@@ -0,0 +1,53 @@
+load(libdir + "asserts.js");
+
+function f(arr)
+{
+  assertEq(arr.splice(1, 2, 9, 8, 7, 6).length, 2); // if it doesn't throw :-)
+}
+
+var N = 100;
+
+function test(out)
+{
+  // Create an array of arrays, to be iterated over for [].splice-calling.
+  var arrs = out.arrs = [];
+  for (var i = 0; i < N; i++)
+    arrs.push([0, 1, 2, 3]);
+
+  // Use a much-greater capacity than the eventual non-writable length, just for
+  // variability.
+  var a = [0, 1, 2, 3, 4, 5, 6, 7];
+  Object.defineProperty(a, "length", { writable: false, value: 4 });
+
+  arrs.push(a);
+
+  for (var i = 0, sz = arrs.length; i < sz; i++)
+    f(arrs[i]);
+}
+
+var obj = {};
+assertThrowsInstanceOf(function() { test(obj); }, TypeError);
+
+var arrs = obj.arrs;
+assertEq(arrs.length, N + 1);
+for (var i = 0; i < N; i++)
+{
+  assertEq(arrs[i].length, 6, "unexpected length for arrs[" + i + "]");
+  assertEq(arrs[i][0], 0,  "bad element for arrs[" + i + "][0]");
+  assertEq(arrs[i][1], 9, "bad element for arrs[" + i + "][1]");
+  assertEq(arrs[i][2], 8, "bad element for arrs[" + i + "][2]");
+  assertEq(arrs[i][3], 7, "bad element for arrs[" + i + "][3]");
+  assertEq(arrs[i][4], 6, "bad element for arrs[" + i + "][4]");
+  assertEq(arrs[i][5], 3, "bad element for arrs[" + i + "][5]");
+}
+
+var a = arrs[N];
+assertEq(a[0], 0, "bad element for a[0]");
+assertEq(a[1], 1, "bad element for a[1]");
+assertEq(a[2], 2, "bad element for a[2]");
+assertEq(a[3], 3, "bad element for a[3]");
+assertEq(a.hasOwnProperty(4), false, "shouldn't have added any elements");
+assertEq(a[4], undefined);
+assertEq(a.hasOwnProperty(5), false, "shouldn't have added any elements");
+assertEq(a[5], undefined);
+assertEq(a.length, 4, "length shouldn't have been changed");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/unshift-nonwritable-length.js
@@ -0,0 +1,61 @@
+load(libdir + "asserts.js");
+
+function f(arr)
+{
+  assertEq(arr.unshift(3, 5, 7, 9), 8); // if it doesn't throw :-)
+}
+
+var N = 100;
+
+function test(out)
+{
+  // Create an array of arrays, to be iterated over for [].unshift-calling.  We
+  // can't just loop on unshift on a single array with non-writable length
+  // because unshift throws when called on an array with non-writable length.
+  var arrs = out.arrs = [];
+  for (var i = 0; i < N; i++)
+    arrs.push([0, 1, 2, 3]);
+
+  // Use a much-greater capacity than the eventual non-writable length, just for
+  // variability.
+  var a = [0, 1, 2, 3, 4, 5, 6, 7];
+  Object.defineProperty(a, "length", { writable: false, value: 4 });
+
+  arrs.push(a);
+
+  for (var i = 0, sz = arrs.length; i < sz; i++)
+    f(arrs[i]);
+}
+
+var obj = {};
+assertThrowsInstanceOf(function() { test(obj); }, TypeError);
+
+var arrs = obj.arrs;
+assertEq(arrs.length, N + 1);
+for (var i = 0; i < N; i++)
+{
+  assertEq(arrs[i].length, 8, "unexpected length for arrs[" + i + "]");
+  assertEq(arrs[i][0], 3, "bad element for arrs[" + i + "][0]");
+  assertEq(arrs[i][1], 5, "bad element for arrs[" + i + "][1]");
+  assertEq(arrs[i][2], 7, "bad element for arrs[" + i + "][2]");
+  assertEq(arrs[i][3], 9, "bad element for arrs[" + i + "][3]");
+  assertEq(arrs[i][4], 0, "bad element for arrs[" + i + "][4]");
+  assertEq(arrs[i][5], 1, "bad element for arrs[" + i + "][5]");
+  assertEq(arrs[i][6], 2, "bad element for arrs[" + i + "][6]");
+  assertEq(arrs[i][7], 3, "bad element for arrs[" + i + "][7]");
+}
+
+var a = arrs[N];
+assertEq(a[0], 0, "bad element for a[0]");
+assertEq(a[1], 1, "bad element for a[1]");
+assertEq(a[2], 2, "bad element for a[2]");
+assertEq(a[3], 3, "bad element for a[3]");
+assertEq(a.hasOwnProperty(4), false, "shouldn't have added any elements");
+assertEq(a[4], undefined);
+assertEq(a.hasOwnProperty(5), false, "shouldn't have added any elements");
+assertEq(a[5], undefined);
+assertEq(a.hasOwnProperty(6), false, "shouldn't have added any elements");
+assertEq(a[6], undefined);
+assertEq(a.hasOwnProperty(7), false, "shouldn't have added any elements");
+assertEq(a[7], undefined);
+assertEq(a.length, 4, "length shouldn't have been changed");
--- a/js/src/jit-test/tests/basic/splice-fail-step-16.js
+++ b/js/src/jit-test/tests/basic/splice-fail-step-16.js
@@ -1,29 +1,25 @@
-// |jit-test| error: InternalError
-// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Remove when [].length is redefinable!
-
 /* Test that arrays resize normally during splice, even if .length is non-writable. */
 
 var arr = [1, 2, 3, 4, 5, 6];
 
 Object.defineProperty(arr, "length", {writable: false});
 
 try
 {
   var removed = arr.splice(3, 3, 9, 9, 9, 9);
   throw new Error("splice didn't throw, returned [" + removed + "]");
 }
 catch (e)
 {
   assertEq(e instanceof TypeError, true,
-           "should have thrown a TypeError, instead threw " + e);
+           "should have thrown a TypeError, instead threw " + e + ", arr is " + arr);
 }
 
 // The exception should happen in step 16, which means we've already removed the array elements.
 assertEq(arr[0], 1);
 assertEq(arr[1], 2);
 assertEq(arr[2], 3);
 assertEq(arr[3], 9);
 assertEq(arr[4], 9);
 assertEq(arr[5], 9);
-assertEq(arr[6], 9);
 assertEq(arr.length, 6);
--- a/js/src/jit-test/tests/debug/Object-apply-02.js
+++ b/js/src/jit-test/tests/debug/Object-apply-02.js
@@ -33,19 +33,25 @@ function test(usingApply) {
         assertEq(cv.return, 3);
 
         cv = usingApply ? push.apply(arr, [arr]) : push.call(arr, arr);
         assertEq(cv.return, 4);
 
         cv = usingApply ? push.apply(arr) : push.call(arr);
         assertEq(cv.return, 4);
 
-        // you can apply Array.prototype.push to a string; it does ToObject on it.
-        cv = usingApply ? push.apply("hello", ["world"]) : push.call("hello", "world");
-        assertEq(cv.return, 6);
+        // You can apply Array.prototype.push to a string; it does ToObject on
+        // it.  But as the length property on String objects is non-writable,
+        // attempting to increase the length will throw a TypeError.
+        cv = usingApply
+             ? push.apply("hello", ["world"])
+             : push.call("hello", "world");
+        assertEq("throw" in cv, true);
+        var ex = cv.throw;
+        assertEq(frame.evalWithBindings("ex instanceof TypeError", { ex: ex }).return, true);
     };
     g.eval("var a = []; f(Array.prototype.push, a);");
     assertEq(g.a.length, 4);
     assertEq(g.a.slice(0, 3).join(","), "0,1,2");
     assertEq(g.a[3], g.a);
 }
 
 test(true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/parallelarray/alloc-array-nonwritable.js
@@ -0,0 +1,40 @@
+load(libdir + "parallelarray-helpers.js");
+
+function buildSimple()
+{
+  var subarr = [];
+  for (var i = 0; i < 100; i++)
+    subarr[i] = 3;
+  subarr[100] = 0;
+
+  var expected = [];
+  for (var i = 0; i < 256; i++)
+    expected[i] = subarr;
+
+  var pa = new ParallelArray([256], function(_) {
+    var arrs = [];
+    for (var i = 0; i < 100; i++)
+      arrs[i] = [0, 1, 2, 3, 4, 5, 6];
+
+    arrs[100] =
+      Object.defineProperty([0, 1, 2, 3, 4, 5, 6, 7],
+                            "length",
+                            { writable: false, value: 7 });
+
+    for (var i = 0; i < 101; i++)
+      arrs[i][7] = 7;
+
+    var x = [];
+    for (var i = 0; i < 101; i++) {
+      var a = arrs[i];
+      x[i] = +(a.length === 8) + 2 * +("7" in a);
+    }
+
+    return x;
+  });
+
+  assertEqParallelArrayArray(pa, expected);
+}
+
+if (getBuildConfiguration().parallelJS)
+  buildSimple();
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -287,18 +287,18 @@ MSG_DEF(JSMSG_STRICT_CODE_WITH,       23
 MSG_DEF(JSMSG_DUPLICATE_PROPERTY,     234, 1, JSEXN_SYNTAXERR, "property name {0} appears more than once in object literal")
 MSG_DEF(JSMSG_DEPRECATED_DELETE_OPERAND, 235, 0, JSEXN_SYNTAXERR, "applying the 'delete' operator to an unqualified name is deprecated")
 MSG_DEF(JSMSG_DEPRECATED_ASSIGN,      236, 1, JSEXN_SYNTAXERR, "assignment to {0} is deprecated")
 MSG_DEF(JSMSG_BAD_BINDING,            237, 1, JSEXN_SYNTAXERR, "redefining {0} is deprecated")
 MSG_DEF(JSMSG_INVALID_DESCRIPTOR,     238, 0, JSEXN_TYPEERR, "property descriptors must not specify a value or be writable when a getter or setter has been specified")
 MSG_DEF(JSMSG_OBJECT_NOT_EXTENSIBLE,  239, 1, JSEXN_TYPEERR, "{0} is not extensible")
 MSG_DEF(JSMSG_CANT_REDEFINE_PROP,     240, 1, JSEXN_TYPEERR, "can't redefine non-configurable property '{0}'")
 MSG_DEF(JSMSG_CANT_APPEND_TO_ARRAY,   241, 0, JSEXN_TYPEERR, "can't add elements past the end of an array if its length property is unwritable")
-MSG_DEF(JSMSG_CANT_DEFINE_ARRAY_LENGTH,242, 0, JSEXN_INTERNALERR, "defining the length property on an array is not currently supported")
-MSG_DEF(JSMSG_CANT_DEFINE_ARRAY_INDEX,243, 0, JSEXN_TYPEERR, "can't define array index property")
+MSG_DEF(JSMSG_CANT_REDEFINE_ARRAY_LENGTH,242, 0, JSEXN_TYPEERR, "can't redefine array length")
+MSG_DEF(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH,243, 0, JSEXN_TYPEERR, "can't define array index property past the end of an array with non-writable length")
 MSG_DEF(JSMSG_TYPED_ARRAY_BAD_INDEX,  244, 0, JSEXN_ERR, "invalid or out-of-range index")
 MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG, 245, 1, JSEXN_ERR, "argument {0} must be >= 0")
 MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS,   246, 0, JSEXN_ERR, "invalid arguments")
 MSG_DEF(JSMSG_CSP_BLOCKED_FUNCTION,   247, 0, JSEXN_ERR, "call to Function() blocked by CSP")
 MSG_DEF(JSMSG_BAD_GET_SET_FIELD,      248, 1, JSEXN_TYPEERR, "property descriptor's {0} field is neither undefined nor a function")
 MSG_DEF(JSMSG_BAD_PROXY_FIX,          249, 0, JSEXN_TYPEERR, "proxy was fixed while executing the handler")
 MSG_DEF(JSMSG_INVALID_EVAL_SCOPE_ARG, 250, 0, JSEXN_EVALERR, "invalid eval scope argument")
 MSG_DEF(JSMSG_ACCESSOR_WRONG_ARGS,    251, 3, JSEXN_SYNTAXERR, "{0} functions must have {1} argument{2}")
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -8,47 +8,46 @@
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/RangedPtr.h"
 #include "mozilla/Util.h"
 
 #include <limits.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include "jstypes.h"
-#include "jsutil.h"
-
 #include "jsapi.h"
 #include "jsarray.h"
 #include "jsatom.h"
 #include "jsbool.h"
 #include "jscntxt.h"
-#include "jsversion.h"
+#include "jsfriendapi.h"
 #include "jsfun.h"
 #include "jsgc.h"
 #include "jsinterp.h"
 #include "jsiter.h"
 #include "jslock.h"
 #include "jsnum.h"
 #include "jsobj.h"
+#include "jstypes.h"
+#include "jsutil.h"
+#include "jsversion.h"
 #include "jswrapper.h"
+
+#include "ds/Sort.h"
+#include "gc/Marking.h"
 #include "methodjit/MethodJIT.h"
 #include "methodjit/StubCalls.h"
 #include "methodjit/StubCalls-inl.h"
-
-#include "gc/Marking.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/ForkJoin.h"
 #include "vm/NumericConversions.h"
 #include "vm/Shape.h"
 #include "vm/StringBuffer.h"
 #include "vm/ThreadPool.h"
 
-#include "ds/Sort.h"
-
 #include "jsatominlines.h"
 #include "jscntxtinlines.h"
 #include "jsinterpinlines.h"
 #include "jsobjinlines.h"
 #include "jsstrinlines.h"
 
 #include "vm/ArgumentsObject-inl.h"
 #include "vm/ObjectImpl-inl.h"
@@ -79,21 +78,21 @@ js::GetLengthProperty(JSContext *cx, Han
         }
     }
 
     RootedValue value(cx);
     if (!JSObject::getProperty(cx, obj, obj, cx->names().length, &value))
         return false;
 
     if (value.isInt32()) {
-        *lengthp = uint32_t(value.toInt32()); /* uint32_t cast does ToUint32_t */
+        *lengthp = uint32_t(value.toInt32()); // uint32_t cast does ToUint32
         return true;
     }
 
-    return ToUint32(cx, value, (uint32_t *)lengthp);
+    return ToUint32(cx, value, lengthp);
 }
 
 /*
  * Determine if the id represents an array index.
  *
  * An id is an array index according to ECMA by (15.4):
  *
  * "Array objects give special treatment to a certain class of property names.
@@ -296,16 +295,21 @@ SetArrayElement(JSContext *cx, HandleObj
 
     if (obj->isArray() && !obj->isIndexed()) {
         /* Predicted/prefetched code should favor the remains-dense case. */
         JSObject::EnsureDenseResult result = JSObject::ED_SPARSE;
         do {
             if (index > uint32_t(-1))
                 break;
             uint32_t idx = uint32_t(index);
+            if (idx >= obj->getArrayLength() && !obj->arrayLengthIsWritable()) {
+                JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, NULL,
+                                             JSMSG_CANT_REDEFINE_ARRAY_LENGTH);
+                return false;
+            }
             result = obj->ensureDenseElements(cx, idx, 1);
             if (result != JSObject::ED_OK)
                 break;
             if (idx >= obj->getArrayLength())
                 obj->setArrayLengthInt32(idx + 1);
             JSObject::setDenseElementWithType(cx, obj, idx, v);
             return true;
         } while (false);
@@ -377,19 +381,17 @@ DeletePropertyOrThrow(JSContext *cx, Han
         return false;
     return obj->reportNotConfigurable(cx, id, JSREPORT_ERROR);
 }
 
 JSBool
 js::SetLengthProperty(JSContext *cx, HandleObject obj, double length)
 {
     RootedValue v(cx, NumberValue(length));
-
-    /* We don't support read-only array length yet. */
-    return JSObject::setProperty(cx, obj, obj, cx->names().length, &v, false);
+    return JSObject::setProperty(cx, obj, obj, cx->names().length, &v, true);
 }
 
 /*
  * Since SpiderMonkey supports cross-class prototype-based delegation, we have
  * to be careful about the length getter and setter being called on an object
  * not of Array class. For the getter, we search obj's prototype chain for the
  * array that caused this getter to be invoked. In the setter case to overcome
  * the JSPROP_SHARED attribute, we must define a shadowing length property.
@@ -412,137 +414,315 @@ array_length_getter(JSContext *cx, Handl
 static JSBool
 array_length_setter(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, MutableHandleValue vp)
 {
     if (!obj->isArray()) {
         return JSObject::defineProperty(cx, obj, cx->names().length, vp,
                                         NULL, NULL, JSPROP_ENUMERATE);
     }
 
-    uint32_t newlen;
-    if (!ToUint32(cx, vp, &newlen))
+    MOZ_ASSERT(obj->arrayLengthIsWritable(),
+               "setter shouldn't be called if property is non-writable");
+    return ArraySetLength(cx, obj, id, JSPROP_PERMANENT, vp, strict);
+}
+
+struct ReverseIndexComparator
+{
+    bool operator()(const uint32_t& a, const uint32_t& b, bool *lessOrEqualp) {
+        MOZ_ASSERT(a != b, "how'd we get duplicate indexes?");
+        *lessOrEqualp = b <= a;
+        return true;
+    }
+};
+
+/* ES6 20130308 draft 8.4.2.4 ArraySetLength */
+bool
+js::ArraySetLength(JSContext *cx, HandleObject obj, HandleId id, unsigned attrs,
+                   HandleValue value, bool setterIsStrict)
+{
+    MOZ_ASSERT(obj->isArray());
+    MOZ_ASSERT(id == NameToId(cx->names().length));
+    MOZ_ASSERT(attrs & JSPROP_PERMANENT);
+    MOZ_ASSERT(!(attrs & JSPROP_ENUMERATE));
+
+    /* Steps 1-2 are irrelevant in our implementation. */
+
+    /* Step 3. */
+    uint32_t newLen;
+    if (!ToUint32(cx, value, &newLen))
         return false;
 
+    /* Steps 4-5. */
     double d;
-    if (!ToNumber(cx, vp, &d))
+    if (!ToNumber(cx, value, &d))
         return false;
-
-    if (d != newlen) {
+    if (d != newLen) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_ARRAY_LENGTH);
         return false;
     }
 
-    uint32_t oldlen = obj->getArrayLength();
-    if (oldlen == newlen)
-        return true;
-
-    vp.setNumber(newlen);
-    if (oldlen < newlen) {
-        JSObject::setArrayLength(cx, obj, newlen);
-        return true;
+    /* Steps 6-7. */
+    bool lengthIsWritable = obj->arrayLengthIsWritable();
+#ifdef DEBUG
+    {
+        RootedShape lengthShape(cx, obj->nativeLookup(cx, id));
+        MOZ_ASSERT(lengthShape);
+        MOZ_ASSERT(lengthShape->writable() == lengthIsWritable);
+    }
+#endif
+
+    uint32_t oldLen = obj->getArrayLength();
+
+    /* Steps 8-9 for arrays with non-writable length. */
+    if (!lengthIsWritable) {
+        if (newLen == oldLen)
+            return true;
+
+        if (setterIsStrict) {
+            return JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, NULL,
+                                                JSMSG_CANT_REDEFINE_ARRAY_LENGTH);
+        }
+
+        return JSObject::reportReadOnly(cx, id, JSREPORT_STRICT | JSREPORT_WARNING);
     }
 
-    /*
-     * Don't reallocate if we're not actually shrinking our slots. If we do
-     * shrink slots here, shrink the initialized length too.  This permits us
-     * us to disregard length when reading from arrays as long we are within
-     * the initialized capacity.
-     */
-    uint32_t oldcap = obj->getDenseCapacity();
-    uint32_t oldinit = obj->getDenseInitializedLength();
-    if (oldinit > newlen)
-        obj->setDenseInitializedLength(newlen);
-    if (oldcap > newlen)
-        obj->shrinkElements(cx, newlen);
-
-    if (!obj->isIndexed()) {
-        /* No sparse indexed properties to remove. */
-        JSObject::setArrayLength(cx, obj, newlen);
+
+    /* Step 8. */
+    bool succeeded = true;
+    do {
+        // The initialized length and capacity of an array only need updating
+        // when non-hole elements are added or removed, which doesn't happen
+        // when array length stays the same or increases.
+        if (newLen >= oldLen)
+            break;
+
+        // Attempt to propagate dense-element optimization tricks, if possible,
+        // and avoid the generic (and accordingly slow) deletion code below.
+        // We can only do this if there are only densely-indexed elements.
+        // Once there's a sparse indexed element, there's no good way to know,
+        // save by enumerating all the properties to find it.  But we *have* to
+        // know in case that sparse indexed element is non-configurable, as
+        // that element must prevent any deletions below it.  Bug 586842 should
+        // fix this inefficiency by moving indexed storage to be entirely
+        // separate from non-indexed storage.
+        if (!obj->isIndexed()) {
+            uint32_t oldCapacity = obj->getDenseCapacity();
+            uint32_t oldInitializedLength = obj->getDenseInitializedLength();
+            MOZ_ASSERT(oldCapacity >= oldInitializedLength);
+            if (oldInitializedLength > newLen)
+                obj->setDenseInitializedLength(newLen);
+            if (oldCapacity > newLen)
+                obj->shrinkElements(cx, newLen);
+
+            // We've done the work of deleting any dense elements needing
+            // deletion, and there are no sparse elements.  Thus we can skip
+            // straight to defining the length.
+            break;
+        }
+
+        // Step 15.
+        //
+        // Attempt to delete all elements above the new length, from greatest
+        // to least.  If any of these deletions fails, we're supposed to define
+        // the length to one greater than the index that couldn't be deleted,
+        // *with the property attributes specified*.  This might convert the
+        // length to be not the value specified, yet non-writable.  (You may be
+        // forgiven for thinking these are interesting semantics.)  Example:
+        //
+        //   var arr =
+        //     Object.defineProperty([0, 1, 2, 3], 1, { writable: false });
+        //   Object.defineProperty(arr, "length",
+        //                         { value: 0, writable: false });
+        //
+        // will convert |arr| to an array of non-writable length two, then
+        // throw a TypeError.
+        //
+        // We implement this behavior, in the relevant lops below, by setting
+        // |succeeded| to false.  Then we exit the loop, define the length
+        // appropriately, and only then throw a TypeError, if necessary.
+        uint32_t gap = oldLen - newLen;
+        const uint32_t RemoveElementsFastLimit = 1 << 24;
+        if (gap < RemoveElementsFastLimit) {
+            // If we're removing a relatively small number of elements, just do
+            // it exactly by the spec.
+            while (newLen < oldLen) {
+                /* Step 15a. */
+                oldLen--;
+
+                /* Steps 15b-d. */
+                JSBool deleteSucceeded;
+                if (!JSObject::deleteElement(cx, obj, oldLen, &deleteSucceeded))
+                    return false;
+                if (!deleteSucceeded) {
+                    newLen = oldLen + 1;
+                    succeeded = false;
+                    break;
+                }
+            }
+        } else {
+            // If we're removing a large number of elements from an array
+            // that's probably sparse, try a different tack.  Get all the own
+            // property names, sift out the indexes in the deletion range into
+            // a vector, sort the vector greatest to least, then delete the
+            // indexes greatest to least using that vector.  See bug 322135.
+            //
+            // This heuristic's kind of a huge guess -- "large number of
+            // elements" and "probably sparse" are completely unprincipled
+            // predictions.  In the long run, bug 586842 will support the right
+            // fix: store sparse elements in a sorted data structure that
+            // permits fast in-reverse-order traversal and concurrent removals.
+
+            Vector<uint32_t> indexes(cx);
+            {
+                AutoIdVector props(cx);
+                if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &props))
+                    return false;
+
+                for (size_t i = 0; i < props.length(); i++) {
+                    if (!JS_CHECK_OPERATION_LIMIT(cx))
+                        return false;
+
+                    uint32_t index;
+                    if (!js_IdIsIndex(props[i], &index))
+                        continue;
+
+                    if (index >= newLen && index < oldLen) {
+                        if (!indexes.append(index))
+                            return false;
+                    }
+                }
+            }
+
+            uint32_t count = indexes.length();
+            {
+                // We should use radix sort to be O(n), but this is uncommon
+                // enough that we'll punt til someone complains.
+                Vector<uint32_t> scratch(cx);
+                if (!scratch.resize(count))
+                    return false;
+                MOZ_ALWAYS_TRUE(MergeSort(indexes.begin(), count, scratch.begin(),
+                                          ReverseIndexComparator()));
+            }
+
+            uint32_t index = UINT32_MAX;
+            for (uint32_t i = 0; i < count; i++) {
+                MOZ_ASSERT(indexes[i] < index, "indexes should never repeat");
+                index = indexes[i];
+
+                /* Steps 15b-d. */
+                JSBool deleteSucceeded;
+                if (!JSObject::deleteElement(cx, obj, index, &deleteSucceeded))
+                    return false;
+                if (!deleteSucceeded) {
+                    newLen = index + 1;
+                    succeeded = false;
+                    break;
+                }
+            }
+        }
+    } while (false);
+
+    /* Steps 12, 16. */
+
+    // Yes, we totally drop a non-stub getter/setter from a defineProperty
+    // API call on the floor here.  Given that getter/setter will go away in
+    // the long run, with accessors replacing them both internally and at the
+    // API level, just run with this.
+    RootedShape lengthShape(cx, obj->nativeLookup(cx, id));
+    if (!JSObject::changeProperty(cx, obj, lengthShape, attrs,
+                                  JSPROP_PERMANENT | JSPROP_READONLY | JSPROP_SHARED,
+                                  array_length_getter, array_length_setter))
+    {
+        return false;
+    }
+
+    RootedValue v(cx, NumberValue(newLen));
+    AddTypePropertyId(cx, obj, id, v);
+    JSObject::setArrayLength(cx, obj, newLen);
+
+    // All operations past here until the |!succeeded| code must be infallible,
+    // so that all element fields remain properly synchronized.
+
+    // Trim the initialized length, if needed, to preserve the <= length
+    // invariant.  (Capacity was already reduced during element deletion, if
+    // necessary.)
+    ObjectElements *header = obj->getElementsHeader();
+    header->initializedLength = Min(header->initializedLength, newLen);
+
+    if (attrs & JSPROP_READONLY) {
+        header->setNonwritableArrayLength();
+
+        // When an array's length becomes non-writable, writes to indexes
+        // greater than or equal to the length don't change the array.  We
+        // handle this with a check for non-writable length in most places.
+        // But in JIT code every check counts -- so we piggyback the check on
+        // the already-required range check for |index < capacity| by making
+        // capacity of arrays with non-writable length never exceed the length.
+        if (obj->getDenseCapacity() > newLen) {
+            obj->shrinkElements(cx, newLen);
+            obj->getElementsHeader()->capacity = newLen;
+        }
+    }
+
+    if (setterIsStrict && !succeeded) {
+        RootedId elementId(cx);
+        if (!IndexToId(cx, newLen - 1, &elementId))
+            return false;
+        return obj->reportNotConfigurable(cx, elementId);
+    }
+
+    return true;
+}
+
+bool
+js::WouldDefinePastNonwritableLength(JSContext *cx, HandleObject obj, uint32_t index, bool strict,
+                                     bool *definesPast)
+{
+    if (!obj->isArray()) {
+        *definesPast = false;
         return true;
     }
 
-    if (oldlen - newlen < (1 << 24)) {
-        /*
-         * We are removing a relatively small number of indexes in an array,
-         * so delete any property found for one of the deleted indexes.
-         */
-        do {
-            --oldlen;
-            if (!JS_CHECK_OPERATION_LIMIT(cx)) {
-                JSObject::setArrayLength(cx, obj, oldlen + 1);
-                return false;
-            }
-
-            JSBool succeeded;
-            if (!DeleteArrayElement(cx, obj, oldlen, &succeeded))
-                return false;
-            if (!succeeded) {
-                JSObject::setArrayLength(cx, obj, oldlen + 1);
-                if (!strict)
-                    return true;
-
-                RootedId id(cx);
-                if (!IndexToId(cx, oldlen, &id))
-                    return false;
-                obj->reportNotConfigurable(cx, id);
-                return false;
-            }
-        } while (oldlen != newlen);
-    } else {
-        /*
-         * We are going to remove a lot of indexes in a presumably sparse
-         * array. So instead of looping through indexes between newlen and
-         * oldlen, we iterate through all properties and remove those that
-         * correspond to indexes in the half-open range [newlen, oldlen).  See
-         * bug 322135.
-         */
-        RootedObject iter(cx, JS_NewPropertyIterator(cx, obj));
-        if (!iter)
-            return false;
-
-        uint32_t gap = oldlen - newlen;
-        for (;;) {
-            RootedId nid(cx);
-            if (!JS_CHECK_OPERATION_LIMIT(cx) || !JS_NextProperty(cx, iter, nid.address()))
-                return false;
-            if (JSID_IS_VOID(nid))
-                break;
-
-            // XXX Bug!  We should fail fast on the highest non-configurable
-            //     property we find, as we do in the simple-loop case above.
-            //     But since we're iterating in unknown order here, we don't
-            //     know if we're hitting the highest non-configurable property
-            //     when we hit a failure.  For now just drop unsuccessful
-            //     deletion on the floor, as the previous code here did.
-            uint32_t index;
-            JSBool succeeded;
-            if (js_IdIsIndex(nid, &index) && index - newlen < gap &&
-                !JSObject::deleteElement(cx, obj, index, &succeeded))
-            {
-                return false;
-            }
-        }
+    uint32_t length = obj->getArrayLength();
+    if (index < length) {
+        *definesPast = false;
+        return true;
+    }
+
+    if (obj->arrayLengthIsWritable()) {
+        *definesPast = false;
+        return true;
     }
 
-    JSObject::setArrayLength(cx, obj, newlen);
-    return true;
+    *definesPast = true;
+    if (!strict && !cx->hasStrictOption())
+        return true;
+
+    // Error in strict mode code or warn with strict option.
+    // XXX include the index and maybe array length in the error message
+    unsigned flags = strict ? JSREPORT_ERROR : (JSREPORT_STRICT | JSREPORT_WARNING);
+    return JS_ReportErrorFlagsAndNumber(cx, flags, js_GetErrorMessage, NULL,
+                                        JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
 }
 
 static JSBool
 array_addProperty(JSContext *cx, HandleObject obj, HandleId id,
                   MutableHandleValue vp)
 {
     uint32_t index, length;
 
     if (!js_IdIsIndex(id, &index))
-        return JS_TRUE;
+        return true;
     length = obj->getArrayLength();
-    if (index >= length)
+    if (index >= length) {
+        MOZ_ASSERT(obj->arrayLengthIsWritable(),
+                   "how'd this element get added if length is non-writable?");
         JSObject::setArrayLength(cx, obj, index + 1);
-    return JS_TRUE;
+    }
+    return true;
 }
 
 JSBool
 js::ObjectMayHaveExtraIndexedProperties(JSObject *obj)
 {
     /*
      * Whether obj may have indexed properties anywhere besides its dense
      * elements. This includes other indexed properties in its shape hierarchy,
@@ -609,17 +789,18 @@ AddLengthProperty(JSContext *cx, HandleO
      * The shared emptyObjectElements singleton cannot be used for slow arrays,
      * as accesses to 'length' will use the elements header.
      */
 
     RootedId lengthId(cx, NameToId(cx->names().length));
     JS_ASSERT(!obj->nativeLookup(cx, lengthId));
 
     return JSObject::addProperty(cx, obj, lengthId, array_length_getter, array_length_setter,
-                                 SHAPE_INVALID_SLOT, JSPROP_PERMANENT | JSPROP_SHARED, 0, 0);
+                                 SHAPE_INVALID_SLOT, JSPROP_PERMANENT | JSPROP_SHARED, 0, 0,
+                                 /* allowDictionary = */ false);
 }
 
 #if JS_HAS_TOSOURCE
 JS_ALWAYS_INLINE bool
 IsArray(const Value &v)
 {
     return v.isObject() && v.toObject().isArray();
 }
@@ -920,27 +1101,31 @@ InitArrayElements(JSContext *cx, HandleO
     types::TypeObject *type = obj->getType(cx);
     if (!type)
         return false;
     if (updateTypes && !InitArrayTypes(cx, type, vector, count))
         return false;
 
     /*
      * Optimize for dense arrays so long as adding the given set of elements
-     * wouldn't otherwise make the array slow.
+     * wouldn't otherwise make the array slow or exceed a non-writable array
+     * length.
      */
     do {
         if (!obj->isArray())
             break;
         if (ObjectMayHaveExtraIndexedProperties(obj))
             break;
 
         if (obj->shouldConvertDoubleElements())
             break;
 
+        if (!obj->arrayLengthIsWritable() && start + count > obj->getArrayLength())
+            break;
+
         JSObject::EnsureDenseResult result = obj->ensureDenseElements(cx, start, count);
         if (result != JSObject::ED_OK) {
             if (result == JSObject::ED_FAILED)
                 return false;
             JS_ASSERT(result == JSObject::ED_SPARSE);
             break;
         }
 
@@ -1690,59 +1875,21 @@ js::array_sort(JSContext *cx, unsigned a
     while (len > n) {
         if (!JS_CHECK_OPERATION_LIMIT(cx) || !DeletePropertyOrThrow(cx, obj, --len))
             return false;
     }
     args.rval().setObject(*obj);
     return true;
 }
 
-/*
- * Perl-inspired push, pop, shift, unshift, and splice methods.
- */
-static bool
-array_push_slowly(JSContext *cx, HandleObject obj, CallArgs &args)
-{
-    uint32_t length;
-
-    if (!GetLengthProperty(cx, obj, &length))
-        return false;
-    if (!InitArrayElements(cx, obj, length, args.length(), args.array(), UpdateTypes))
-        return false;
-
-    /* Per ECMA-262, return the new array length. */
-    double newlength = length + double(args.length());
-    args.rval().setNumber(newlength);
-    return SetLengthProperty(cx, obj, newlength);
-}
-
-static bool
-array_push1_dense(JSContext* cx, HandleObject obj, CallArgs &args)
-{
-    JS_ASSERT(args.length() == 1);
-
-    uint32_t length = obj->getArrayLength();
-    JSObject::EnsureDenseResult result = obj->ensureDenseElements(cx, length, 1);
-    if (result != JSObject::ED_OK) {
-        if (result == JSObject::ED_FAILED)
-            return false;
-        JS_ASSERT(result == JSObject::ED_SPARSE);
-        return array_push_slowly(cx, obj, args);
-    }
-
-    obj->setArrayLengthInt32(length + 1);
-    JSObject::setDenseElementWithType(cx, obj, length, args[0]);
-    args.rval().setNumber(obj->getArrayLength());
-    return true;
-}
-
 JS_ALWAYS_INLINE JSBool
 NewbornArrayPushImpl(JSContext *cx, HandleObject obj, const Value &v)
 {
     JS_ASSERT(!v.isMagic());
+    JS_ASSERT(obj->arrayLengthIsWritable());
 
     uint32_t length = obj->getArrayLength();
     JS_ASSERT(length <= obj->getDenseCapacity());
 
     if (!obj->ensureElements(cx, length + 1))
         return false;
 
     obj->setDenseInitializedLength(length + 1);
@@ -1752,103 +1899,113 @@ NewbornArrayPushImpl(JSContext *cx, Hand
 }
 
 JSBool
 js_NewbornArrayPush(JSContext *cx, HandleObject obj, const Value &vp)
 {
     return NewbornArrayPushImpl(cx, obj, vp);
 }
 
+/* ES5 15.4.4.7 */
 JSBool
 js::array_push(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
+
+    /* Step 1. */
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
-    /* Insist on one argument and obj of the expected class. */
-    if (args.length() != 1 || !obj->isArray())
-        return array_push_slowly(cx, obj, args);
-
-    return array_push1_dense(cx, obj, args);
-}
-
-static JSBool
-array_pop_slowly(JSContext *cx, HandleObject obj, CallArgs &args)
-{
-    uint32_t index;
-    if (!GetLengthProperty(cx, obj, &index))
+    /* Fast path for the fully-dense case. */
+    if (obj->isArray() &&
+        obj->arrayLengthIsWritable() &&
+        !ObjectMayHaveExtraIndexedProperties(obj))
+    {
+        uint32_t length = obj->getArrayLength();
+        uint32_t argCount = args.length();
+        JSObject::EnsureDenseResult result = obj->ensureDenseElements(cx, length, argCount);
+        if (result == JSObject::ED_FAILED)
+            return false;
+
+        if (result == JSObject::ED_OK) {
+            obj->setArrayLengthInt32(length + argCount);
+            for (uint32_t i = 0, index = length; i < argCount; index++, i++)
+                JSObject::setDenseElementWithType(cx, obj, index, args[i]);
+            args.rval().setNumber(obj->getArrayLength());
+            return true;
+        }
+
+        MOZ_ASSERT(result == JSObject::ED_SPARSE);
+    }
+
+    /* Steps 2-3. */
+    uint32_t length;
+    if (!GetLengthProperty(cx, obj, &length))
         return false;
 
-    if (index == 0) {
-        args.rval().setUndefined();
-        return SetLengthProperty(cx, obj, index);
-    }
-
-    index--;
-
-    JSBool hole;
-    RootedValue elt(cx);
-    if (!GetElement(cx, obj, index, &hole, &elt))
+    /* Steps 4-5. */
+    if (!InitArrayElements(cx, obj, length, args.length(), args.array(), UpdateTypes))
         return false;
 
-    if (!hole && !DeletePropertyOrThrow(cx, obj, index))
-        return false;
-
-    args.rval().set(elt);
-    return SetLengthProperty(cx, obj, index);
+    /* Steps 6-7. */
+    double newlength = length + double(args.length());
+    args.rval().setNumber(newlength);
+    return SetLengthProperty(cx, obj, newlength);
 }
 
-static JSBool
-array_pop_dense(JSContext *cx, HandleObject obj, CallArgs &args)
-{
-    uint32_t index = obj->getArrayLength();
-    if (index == 0) {
-        args.rval().setUndefined();
-        return true;
-    }
-
-    index--;
-
-    JSBool hole;
-    RootedValue elt(cx);
-    if (!GetElement(cx, obj, index, &hole, &elt))
-        return false;
-
-    if (!hole && !DeletePropertyOrThrow(cx, obj, index))
-        return false;
-
-    args.rval().set(elt);
-
-    // obj may not be a dense array any more, e.g. if the element was a missing
-    // and a getter supplied by the prototype modified the object.
-    if (obj->getDenseInitializedLength() > index)
-        obj->setDenseInitializedLength(index);
-
-    JSObject::setArrayLength(cx, obj, index);
-    return true;
-}
-
+/* ES6 20130308 draft 15.4.4.6. */
 JSBool
 js::array_pop(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
+
+    /* Step 1. */
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
-    if (obj->isArray())
-        return array_pop_dense(cx, obj, args);
-    return array_pop_slowly(cx, obj, args);
+
+    /* Steps 2-3. */
+    uint32_t index;
+    if (!GetLengthProperty(cx, obj, &index))
+        return false;
+
+    /* Steps 4-5. */
+    if (index == 0) {
+        /* Step 4b. */
+        args.rval().setUndefined();
+    } else {
+        /* Step 5a. */
+        index--;
+
+        /* Step 5b, 5e. */
+        JSBool hole;
+        if (!GetElement(cx, obj, index, &hole, args.rval()))
+            return false;
+
+        /* Step 5c. */
+        if (!hole && !DeletePropertyOrThrow(cx, obj, index))
+            return false;
+    }
+
+    // Keep dense initialized length optimal, if possible.  Note that this just
+    // reflects the possible deletion above: in particular, it's okay to do
+    // this even if the length is non-writable and SetLengthProperty throws.
+    if (obj->isNative() && obj->getDenseInitializedLength() > index)
+        obj->setDenseInitializedLength(index);
+
+    /* Steps 4a, 5d. */
+    return SetLengthProperty(cx, obj, index);
 }
 
 void
 js::ArrayShiftMoveElements(JSObject *obj)
 {
     JS_ASSERT(obj->isArray());
+    JS_ASSERT(obj->arrayLengthIsWritable());
 
     /*
      * At this point the length and initialized length have already been
      * decremented and the result fetched, so just shift the array elements
      * themselves.
      */
     uint32_t initlen = obj->getDenseInitializedLength();
     obj->moveDenseElementsUnbarriered(0, 1, initlen);
@@ -1858,73 +2015,91 @@ js::ArrayShiftMoveElements(JSObject *obj
 void JS_FASTCALL
 mjit::stubs::ArrayShift(VMFrame &f)
 {
     JSObject *obj = &f.regs.sp[-1].toObject();
     ArrayShiftMoveElements(obj);
 }
 #endif /* JS_METHODJIT */
 
+/* ES5 15.4.4.9 */
 JSBool
 js::array_shift(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
+
+    /* Step 1. */
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
-        return JS_FALSE;
-
-    uint32_t length;
-    if (!GetLengthProperty(cx, obj, &length))
-        return JS_FALSE;
-
-    if (length == 0) {
+        return false;
+
+    /* Steps 2-3. */
+    uint32_t len;
+    if (!GetLengthProperty(cx, obj, &len))
+        return false;
+
+    /* Step 4. */
+    if (len == 0) {
+        /* Step 4a. */
+        if (!SetLengthProperty(cx, obj, 0))
+            return false;
+
+        /* Step 4b. */
         args.rval().setUndefined();
-    } else {
-        length--;
-
-        if (obj->isArray() && !ObjectMayHaveExtraIndexedProperties(obj) &&
-            length < obj->getDenseCapacity() &&
-            0 < obj->getDenseInitializedLength())
-        {
-            args.rval().set(obj->getDenseElement(0));
-            if (args.rval().isMagic(JS_ELEMENTS_HOLE))
-                args.rval().setUndefined();
-            obj->moveDenseElements(0, 1, obj->getDenseInitializedLength() - 1);
-            obj->setDenseInitializedLength(obj->getDenseInitializedLength() - 1);
-            JSObject::setArrayLength(cx, obj, length);
-            if (!js_SuppressDeletedProperty(cx, obj, INT_TO_JSID(length)))
-                return JS_FALSE;
-            return JS_TRUE;
+        return true;
+    }
+
+    uint32_t newlen = len - 1;
+
+    /* Fast paths. */
+    if (obj->isArray() &&
+        obj->getDenseInitializedLength() > 0 &&
+        newlen < obj->getDenseCapacity() &&
+        !ObjectMayHaveExtraIndexedProperties(obj))
+    {
+        args.rval().set(obj->getDenseElement(0));
+        if (args.rval().isMagic(JS_ELEMENTS_HOLE))
+            args.rval().setUndefined();
+
+        obj->moveDenseElements(0, 1, obj->getDenseInitializedLength() - 1);
+        obj->setDenseInitializedLength(obj->getDenseInitializedLength() - 1);
+
+        if (!SetLengthProperty(cx, obj, newlen))
+            return false;
+
+        return js_SuppressDeletedProperty(cx, obj, INT_TO_JSID(newlen));
+    }
+
+    /* Steps 5, 10. */
+    JSBool hole;
+    if (!GetElement(cx, obj, uint32_t(0), &hole, args.rval()))
+        return false;
+
+    /* Steps 6-7. */
+    RootedValue value(cx);
+    for (uint32_t i = 0; i < newlen; i++) {
+        if (!JS_CHECK_OPERATION_LIMIT(cx))
+            return false;
+        if (!GetElement(cx, obj, i + 1, &hole, &value))
+            return false;
+        if (hole) {
+            if (!DeletePropertyOrThrow(cx, obj, i))
+                return false;
+        } else {
+            if (!SetArrayElement(cx, obj, i, value))
+                return false;
         }
-
-        JSBool hole;
-        if (!GetElement(cx, obj, 0u, &hole, args.rval()))
-            return JS_FALSE;
-
-        /* Slide down the array above the first element. */
-        RootedValue value(cx);
-        for (uint32_t i = 0; i < length; i++) {
-            if (!JS_CHECK_OPERATION_LIMIT(cx))
-                return false;
-            if (!GetElement(cx, obj, i + 1, &hole, &value))
-                return false;
-            if (hole) {
-                if (!DeletePropertyOrThrow(cx, obj, i))
-                    return false;
-            } else {
-                if (!SetArrayElement(cx, obj, i, value))
-                    return false;
-            }
-        }
-
-        /* Delete the only or last element when it exists. */
-        if (!hole && !DeletePropertyOrThrow(cx, obj, length))
-            return JS_FALSE;
     }
-    return SetLengthProperty(cx, obj, length);
+
+    /* Step 8. */
+    if (!DeletePropertyOrThrow(cx, obj, newlen))
+        return false;
+
+    /* Step 9. */
+    return SetLengthProperty(cx, obj, newlen);
 }
 
 static JSBool
 array_unshift(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
@@ -1939,16 +2114,18 @@ array_unshift(JSContext *cx, unsigned ar
         /* Slide up the array to make room for all args at the bottom. */
         if (length > 0) {
             bool optimized = false;
             do {
                 if (!obj->isArray())
                     break;
                 if (ObjectMayHaveExtraIndexedProperties(obj))
                     break;
+                if (!obj->arrayLengthIsWritable())
+                    break;
                 JSObject::EnsureDenseResult result = obj->ensureDenseElements(cx, length, args.length());
                 if (result != JSObject::ED_OK) {
                     if (result == JSObject::ED_FAILED)
                         return false;
                     JS_ASSERT(result == JSObject::ED_SPARSE);
                     break;
                 }
                 obj->moveDenseElements(args.length(), 0, length);
@@ -2039,18 +2216,19 @@ CanOptimizeForDenseStorage(HandleObject 
      * case can't happen, because any dense array used as the prototype of
      * another object is first slowified, for type inference's sake.
      */
     types::TypeObject *arrType = arr->getType(cx);
     if (JS_UNLIKELY(!arrType || arrType->hasAllFlags(OBJECT_FLAG_ITERATED)))
         return false;
 
     /*
-     * Now just watch out for getters and setters along the prototype chain or
-     * in other indexed properties on the object.
+     * Now watch out for getters and setters along the prototype chain or in
+     * other indexed properties on the object.  (Note that non-writable length
+     * is subsumed by the initializedLength comparison.)
      */
     return !ObjectMayHaveExtraIndexedProperties(arr) &&
            startingIndex + count <= arr->getDenseInitializedLength();
 }
 
 /* ES5 15.4.4.12. */
 static JSBool
 array_splice(JSContext *cx, unsigned argc, Value *vp)
@@ -2179,19 +2357,39 @@ array_splice(JSContext *cx, unsigned arg
                     return false;
             }
         }
     } else if (itemCount > actualDeleteCount) {
         /* Step 13. */
 
         /*
          * Optimize only if the array is already dense and we can extend it to
-         * its new length.
+         * its new length.  It would be wrong to extend the elements here for a
+         * number of reasons.
+         *
+         * First, this could cause us to fall into the fast-path below.  This
+         * would cause elements to be moved into places past the non-writable
+         * length.  And when the dense initialized length is updated, that'll
+         * cause the |in| operator to think that those elements actually exist,
+         * even though, properly, setting them must fail.
+         *
+         * Second, extending the elements here will trigger assertions inside
+         * ensureDenseElements that the elements aren't being extended past the
+         * length of a non-writable array.  This is because extending elements
+         * will extend capacity -- which might extend them past a non-writable
+         * length, violating the |capacity <= length| invariant for such
+         * arrays.  And that would make the various JITted fast-path method
+         * implementations of [].push, [].unshift, and so on wrong.
+         *
+         * If the array length is non-writable, this method *will* throw.  For
+         * simplicity, have the slow-path code do it.  (Also note that the slow
+         * path may validly *not* throw -- if all the elements being moved are
+         * holes.)
          */
-        if (obj->isArray()) {
+        if (obj->isArray() && obj->arrayLengthIsWritable()) {
             JSObject::EnsureDenseResult res =
                 obj->ensureDenseElements(cx, obj->getArrayLength(),
                                          itemCount - actualDeleteCount);
             if (res == JSObject::ED_FAILED)
                 return false;
         }
 
         if (CanOptimizeForDenseStorage(obj, len, itemCount - actualDeleteCount, cx)) {
--- a/js/src/jsarray.h
+++ b/js/src/jsarray.h
@@ -1,23 +1,23 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* JS Array interface. */
+
 #ifndef jsarray_h___
 #define jsarray_h___
-/*
- * JS Array interface.
- */
+
+#include "jsatom.h"
 #include "jscntxt.h"
 #include "jsprvtd.h"
 #include "jspubtd.h"
-#include "jsatom.h"
 #include "jsobj.h"
 
 namespace js {
 /* 2^32-2, inclusive */
 const uint32_t MAX_ARRAY_INDEX = 4294967294u;
 }
 
 inline JSBool
@@ -66,16 +66,25 @@ NewDenseUnallocatedArray(JSContext *cx, 
 extern JSObject *
 NewDenseCopiedArray(JSContext *cx, uint32_t length, HandleObject src, uint32_t elementOffset, RawObject proto = NULL);
 
 /* Create a dense array from the given array values, which must be rooted */
 extern JSObject *
 NewDenseCopiedArray(JSContext *cx, uint32_t length, const Value *values, RawObject proto = NULL,
                     NewObjectKind newKind = GenericObject);
 
+/*
+ * Determines whether a write to the given element on |obj| should fail because
+ * |obj| is an Array with a non-writable length, and writing that element would
+ * increase the length of the array.
+ */
+extern bool
+WouldDefinePastNonwritableLength(JSContext *cx, HandleObject obj, uint32_t index, bool strict,
+                                 bool *definesPast);
+
 /* Get the common shape used by all dense arrays with a prototype at globalObj. */
 extern RawShape
 GetDenseArrayShape(JSContext *cx, HandleObject globalObj);
 
 extern JSBool
 GetLengthProperty(JSContext *cx, HandleObject obj, uint32_t *lengthp);
 
 extern JSBool
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -69,16 +69,17 @@
 #include "jsautooplen.h"
 
 using namespace js;
 using namespace js::gc;
 using namespace js::types;
 
 using js::frontend::IsIdentifier;
 using mozilla::ArrayLength;
+using mozilla::DebugOnly;
 
 JS_STATIC_ASSERT(int32_t((JSObject::NELEMENTS_LIMIT - 1) * sizeof(Value)) == int64_t((JSObject::NELEMENTS_LIMIT - 1) * sizeof(Value)));
 
 Class js::ObjectClass = {
     js_Object_str,
     JSCLASS_HAS_CACHED_PROTO(JSProto_Object),
     JS_PropertyStub,         /* addProperty */
     JS_DeletePropertyStub,   /* delProperty */
@@ -548,16 +549,26 @@ Reject(JSContext *cx, JSObject *obj, uns
 {
     if (throwError)
         return Throw(cx, obj, errorNumber);
 
     *rval = false;
     return JS_TRUE;
 }
 
+static bool
+Reject(JSContext *cx, HandleId id, unsigned errorNumber, bool throwError, bool *rval)
+{
+    if (throwError)
+        return Throw(cx, id, errorNumber);
+
+    *rval = false;
+    return true;
+}
+
 // See comments on CheckDefineProperty in jsobj.h.
 //
 // DefinePropertyOnObject has its own implementation of these checks.
 //
 bool
 js::CheckDefineProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue value,
                         PropertyOp getter, StrictPropertyOp setter, unsigned attrs)
 {
@@ -910,51 +921,67 @@ DefinePropertyOnObject(JSContext *cx, Ha
         JSBool succeeded;
         if (!CallJSDeletePropertyOp(cx, obj2->getClass()->delProperty, obj2, id, &succeeded))
             return false;
     }
 
     return baseops::DefineGeneric(cx, obj, id, v, getter, setter, attrs);
 }
 
+/* ES6 20130308 draft 8.4.2.1 [[DefineOwnProperty]] */
 static JSBool
 DefinePropertyOnArray(JSContext *cx, HandleObject obj, HandleId id, const PropDesc &desc,
                       bool throwError, bool *rval)
 {
-    // Disabled until we support defining "length":
-    //uint32_t oldLen = obj->getArrayLength();
-
-    if (JSID_IS_ATOM(id, cx->names().length)) {
-        /*
-         * Our optimization of storage of the length property of arrays makes
-         * it very difficult to properly implement defining the property.  For
-         * now simply throw an exception (NB: not merely Reject) on any attempt
-         * to define the "length" property, rather than attempting to implement
-         * some difficult-for-authors-to-grasp subset of that functionality.
-         */
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_DEFINE_ARRAY_LENGTH);
-        return JS_FALSE;
+    JS_ASSERT(obj->isArray());
+
+    /* Step 2. */
+    if (id == NameToId(cx->names().length)) {
+        if (desc.hasConfigurable() && desc.configurable())
+            return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval);
+        if (desc.hasEnumerable() && desc.enumerable())
+            return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval);
+
+        if (desc.isAccessorDescriptor())
+            return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval);
+
+        unsigned attrs = obj->nativeLookup(cx, id)->attributes();
+
+        RootedValue v(cx, desc.hasValue() ? desc.value() : NumberValue(obj->getArrayLength()));
+        if (!obj->arrayLengthIsWritable()) {
+            if (desc.hasWritable() && desc.writable())
+                return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval);
+
+            if (desc.hasValue()) {
+                if (obj->getArrayLength() != desc.value().toNumber())
+                    return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval);
+            }
+        } else {
+            if (desc.hasWritable() && !desc.writable())
+                attrs = attrs | JSPROP_READONLY;
+        }
+
+        return ArraySetLength(cx, obj, id, attrs, v, throwError);
     }
 
+    /* Step 3. */
     uint32_t index;
     if (js_IdIsIndex(id, &index)) {
-        /*
-        // Disabled until we support defining "length":
-        if (index >= oldLen && lengthPropertyNotWritable())
-            return ThrowTypeError(cx, JSMSG_CANT_APPEND_TO_ARRAY);
-         */
-        if (!DefinePropertyOnObject(cx, obj, id, desc, false, rval))
-            return JS_FALSE;
-        if (!*rval)
-            return Reject(cx, obj, JSMSG_CANT_DEFINE_ARRAY_INDEX, throwError, rval);
-
-        *rval = true;
-        return JS_TRUE;
+        /* Step 3b. */
+        uint32_t oldLen = obj->getArrayLength();
+
+        /* Steps 3a, 3e. */
+        if (index >= oldLen && !obj->arrayLengthIsWritable())
+            return Reject(cx, obj, JSMSG_CANT_APPEND_TO_ARRAY, throwError, rval);
+
+        /* Steps 3f-j. */
+        return DefinePropertyOnObject(cx, obj, id, desc, throwError, rval);
     }
 
+    /* Step 4. */
     return DefinePropertyOnObject(cx, obj, id, desc, throwError, rval);
 }
 
 bool
 js::DefineProperty(JSContext *cx, HandleObject obj, HandleId id, const PropDesc &desc,
                    bool throwError, bool *rval)
 {
     if (obj->isArray())
@@ -1944,18 +1971,18 @@ JSObject::TradeGuts(JSContext *cx, JSObj
 #endif
     } else {
         /*
          * If the objects are of differing sizes, use the space we reserved
          * earlier to save the slots from each object and then copy them into
          * the new layout for the other object.
          */
 
-        unsigned acap = a->slotSpan();
-        unsigned bcap = b->slotSpan();
+        uint32_t acap = a->slotSpan();
+        uint32_t bcap = b->slotSpan();
 
         for (size_t i = 0; i < acap; i++)
             reserved.avals.infallibleAppend(a->getSlot(i));
 
         for (size_t i = 0; i < bcap; i++)
             reserved.bvals.infallibleAppend(b->getSlot(i));
 
         /* Done with the dynamic slots. */
@@ -2414,17 +2441,17 @@ JSObject::growSlots(JSContext *cx, Handl
     /*
      * If we are allocating slots for an object whose type is always created
      * by calling 'new' on a particular script, bump the GC kind for that
      * type to give these objects a larger number of fixed slots when future
      * objects are constructed.
      */
     if (!obj->hasLazyType() && !oldCount && obj->type()->newScript) {
         gc::AllocKind kind = obj->type()->newScript->allocKind;
-        unsigned newScriptSlots = gc::GetGCKindSlots(kind);
+        uint32_t newScriptSlots = gc::GetGCKindSlots(kind);
         if (newScriptSlots == obj->numFixedSlots() && gc::TryIncrementAllocKind(&kind)) {
             AutoEnterAnalysis enter(cx);
 
             Rooted<TypeObject*> typeObj(cx, obj->type());
             RootedShape shape(cx, typeObj->newScript->shape);
             JSObject *reshapedObj = NewReshapedObject(cx, typeObj, obj->getParent(), kind, shape);
             if (!reshapedObj)
                 return false;
@@ -2498,17 +2525,17 @@ JSObject::shrinkSlots(JSContext *cx, Han
     obj->slots = newslots;
 
     /* Watch for changes in global object slots, as for growSlots. */
     if (changed && obj->isGlobal())
         types::MarkObjectStateChange(cx, obj);
 }
 
 /* static */ bool
-JSObject::sparsifyDenseElement(JSContext *cx, HandleObject obj, unsigned index)
+JSObject::sparsifyDenseElement(JSContext *cx, HandleObject obj, uint32_t index)
 {
     RootedValue value(cx, obj->getDenseElement(index));
     JS_ASSERT(!value.isMagic(JS_ELEMENTS_HOLE));
 
     JSObject::removeDenseElementForSparseIndex(cx, obj, index);
 
     uint32_t slot = obj->slotSpan();
     if (!obj->addDataProperty(cx, INT_TO_JSID(index), slot, JSPROP_ENUMERATE)) {
@@ -2548,38 +2575,38 @@ JSObject::sparsifyDenseElements(JSContex
         obj->shrinkElements(cx, 0);
         obj->getElementsHeader()->capacity = 0;
     }
 
     return true;
 }
 
 bool
-JSObject::willBeSparseElements(unsigned requiredCapacity, unsigned newElementsHint)
+JSObject::willBeSparseElements(uint32_t requiredCapacity, uint32_t newElementsHint)
 {
     JS_ASSERT(isNative());
     JS_ASSERT(requiredCapacity > MIN_SPARSE_INDEX);
 
-    unsigned cap = getDenseCapacity();
+    uint32_t cap = getDenseCapacity();
     JS_ASSERT(requiredCapacity >= cap);
 
     if (requiredCapacity >= NELEMENTS_LIMIT)
         return true;
 
-    unsigned minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO;
+    uint32_t minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO;
     if (newElementsHint >= minimalDenseCount)
         return false;
     minimalDenseCount -= newElementsHint;
 
     if (minimalDenseCount > cap)
         return true;
 
-    unsigned len = getDenseInitializedLength();
+    uint32_t len = getDenseInitializedLength();
     const Value *elems = getDenseElements();
-    for (unsigned i = 0; i < len; i++) {
+    for (uint32_t i = 0; i < len; i++) {
         if (!elems[i].isMagic(JS_ELEMENTS_HOLE) && !--minimalDenseCount)
             return false;
     }
     return true;
 }
 
 /* static */ JSObject::EnsureDenseResult
 JSObject::maybeDensifySparseElements(JSContext *cx, HandleObject obj)
@@ -2723,19 +2750,21 @@ ReallocateElements(JSObject::MaybeContex
     }
 
     Allocator *alloc = maybecx.allocator;
     return static_cast<js::ObjectElements *>(alloc->realloc_(oldHeader, oldCount * sizeof(HeapSlot),
                                                              newCount * sizeof(HeapSlot)));
 }
 
 bool
-JSObject::growElements(MaybeContext cx, unsigned newcap)
+JSObject::growElements(MaybeContext cx, uint32_t newcap)
 {
     JS_ASSERT(isExtensible());
+    JS_ASSERT_IF(isArray() && !arrayLengthIsWritable(),
+                 newcap <= getArrayLength());
 
     /*
      * When an object with CAPACITY_DOUBLING_MAX or fewer elements needs to
      * grow, double its capacity, to add N elements in amortized O(N) time.
      *
      * Above this limit, grow by 12.5% each time. Speed is still amortized
      * O(N), with a higher constant factor, and we waste less space.
      */
@@ -2744,25 +2773,32 @@ JSObject::growElements(MaybeContext cx, 
 
     uint32_t oldcap = getDenseCapacity();
     JS_ASSERT(oldcap <= newcap);
 
     uint32_t nextsize = (oldcap <= CAPACITY_DOUBLING_MAX)
                       ? oldcap * 2
                       : oldcap + (oldcap >> 3);
 
-    uint32_t actualCapacity = Max(newcap, nextsize);
-    if (actualCapacity >= CAPACITY_CHUNK)
-        actualCapacity = JS_ROUNDUP(actualCapacity, CAPACITY_CHUNK);
-    else if (actualCapacity < SLOT_CAPACITY_MIN)
-        actualCapacity = SLOT_CAPACITY_MIN;
-
-    /* Don't let nelements get close to wrapping around uint32_t. */
-    if (actualCapacity >= NELEMENTS_LIMIT || actualCapacity < oldcap || actualCapacity < newcap) {
-        return false;
+    uint32_t actualCapacity;
+    if (isArray() && !arrayLengthIsWritable()) {
+        // Preserve the |capacity <= length| invariant for arrays with
+        // non-writable length.  See also js::ArraySetLength which initially
+        // enforces this requirement.
+        actualCapacity = newcap;
+    } else {
+        actualCapacity = Max(newcap, nextsize);
+        if (actualCapacity >= CAPACITY_CHUNK)
+            actualCapacity = JS_ROUNDUP(actualCapacity, CAPACITY_CHUNK);
+        else if (actualCapacity < SLOT_CAPACITY_MIN)
+            actualCapacity = SLOT_CAPACITY_MIN;
+
+        /* Don't let nelements get close to wrapping around uint32_t. */
+        if (actualCapacity >= NELEMENTS_LIMIT || actualCapacity < oldcap || actualCapacity < newcap)
+            return false;
     }
 
     uint32_t initlen = getDenseInitializedLength();
     uint32_t oldAllocated = oldcap + ObjectElements::VALUES_PER_HEADER;
     uint32_t newAllocated = actualCapacity + ObjectElements::VALUES_PER_HEADER;
 
     ObjectElements *newheader;
     if (hasDynamicElements()) {
@@ -2781,17 +2817,17 @@ JSObject::growElements(MaybeContext cx, 
     elements = newheader->elements();
 
     Debug_SetSlotRangeToCrashOnTouch(elements + initlen, actualCapacity - initlen);
 
     return true;
 }
 
 void
-JSObject::shrinkElements(JSContext *cx, unsigned newcap)
+JSObject::shrinkElements(JSContext *cx, uint32_t newcap)
 {
     uint32_t oldcap = getDenseCapacity();
     JS_ASSERT(newcap <= oldcap);
 
     /* Don't shrink elements below the minimum capacity. */
     if (oldcap <= SLOT_CAPACITY_MIN || !hasDynamicElements())
         return;
 
@@ -3249,25 +3285,46 @@ DefinePropertyOrElement(JSContext *cx, H
     /* Use dense storage for new indexed properties where possible. */
     if (JSID_IS_INT(id) &&
         getter == JS_PropertyStub &&
         setter == JS_StrictPropertyStub &&
         attrs == JSPROP_ENUMERATE &&
         (!obj->isIndexed() || !obj->nativeContains(cx, id)))
     {
         uint32_t index = JSID_TO_INT(id);
+        bool definesPast;
+        if (!WouldDefinePastNonwritableLength(cx, obj, index, setterIsStrict, &definesPast))
+            return false;
+        if (definesPast)
+            return true;
+
         JSObject::EnsureDenseResult result = obj->ensureDenseElements(cx, index, 1);
         if (result == JSObject::ED_FAILED)
             return false;
         if (result == JSObject::ED_OK) {
             obj->setDenseElementMaybeConvertDouble(index, value);
             return CallAddPropertyHookDense(cx, obj->getClass(), obj, index, value);
         }
     }
 
+    if (obj->isArray()) {
+        if (id == NameToId(cx->names().length))
+            return ArraySetLength(cx, obj, id, attrs, value, setterIsStrict);
+
+        uint32_t index;
+        if (js_IdIsIndex(id, &index)) {
+            bool definesPast;
+            if (!WouldDefinePastNonwritableLength(cx, obj, index, setterIsStrict, &definesPast))
+                return false;
+            if (definesPast)
+                return true;
+        }
+    }
+
+
     AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter);
 
     RootedShape shape(cx, JSObject::putProperty(cx, obj, id, getter, setter, SHAPE_INVALID_SLOT,
                                                 attrs, flags, shortid));
     if (!shape)
         return false;
 
     if (shape->hasSlot())
@@ -3720,16 +3777,17 @@ NativeGetInline(JSContext *cx,
 
 JSBool
 js_NativeGet(JSContext *cx, Handle<JSObject*> obj, Handle<JSObject*> pobj, Handle<Shape*> shape,
              unsigned getHow, MutableHandle<Value> vp)
 {
     return NativeGetInline<CanGC>(cx, obj, obj, pobj, shape, getHow, vp);
 }
 
+
 JSBool
 js_NativeSet(JSContext *cx, Handle<JSObject*> obj, Handle<JSObject*> receiver,
              HandleShape shape, bool strict, MutableHandleValue vp)
 {
     JS_ASSERT(obj->isNative());
 
     if (shape->hasSlot()) {
         uint32_t slot = shape->slot();
@@ -4159,22 +4217,16 @@ JSObject::callMethod(JSContext *cx, Hand
     return GetMethod(cx, obj, id, 0, &fval) &&
            Invoke(cx, ObjectValue(*obj), fval, argc, argv, vp.address());
 }
 
 JSBool
 baseops::SetPropertyHelper(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
                            unsigned defineHow, MutableHandleValue vp, JSBool strict)
 {
-    unsigned attrs, flags;
-    int shortid;
-    Class *clasp;
-    PropertyOp getter;
-    StrictPropertyOp setter;
-
     JS_ASSERT((defineHow & ~(DNP_CACHE_RESULT | DNP_UNQUALIFIED)) == 0);
 
     if (JS_UNLIKELY(obj->watched())) {
         /* Fire watchpoints, if any. */
         WatchpointMap *wpmap = cx->compartment->watchpointMap;
         if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, vp))
             return false;
     }
@@ -4218,22 +4270,22 @@ baseops::SetPropertyHelper(JSContext *cx
             return false;
         }
     }
 
     /*
      * Now either shape is null, meaning id was not found in obj or one of its
      * prototypes; or shape is non-null, meaning id was found directly in pobj.
      */
-    attrs = JSPROP_ENUMERATE;
-    flags = 0;
-    shortid = 0;
-    clasp = obj->getClass();
-    getter = clasp->getProperty;
-    setter = clasp->setProperty;
+    unsigned attrs = JSPROP_ENUMERATE;
+    unsigned flags = 0;
+    int shortid = 0;
+    Class *clasp = obj->getClass();
+    PropertyOp getter = clasp->getProperty;
+    StrictPropertyOp setter = clasp->setProperty;
 
     if (IsImplicitDenseElement(shape)) {
         /* ES5 8.12.4 [[Put]] step 2, for a dense data property on pobj. */
         if (pobj != obj)
             shape = NULL;
     } else if (shape) {
         /* ES5 8.12.4 [[Put]] step 2. */
         if (shape->isAccessorDescriptor()) {
@@ -4299,20 +4351,30 @@ baseops::SetPropertyHelper(JSContext *cx
              * Forget we found the proto-property now that we've copied any
              * needed member values.
              */
             shape = NULL;
         }
     }
 
     if (IsImplicitDenseElement(shape)) {
-        JSObject::setDenseElementWithType(cx, obj, JSID_TO_INT(id), vp);
+        uint32_t index = JSID_TO_INT(id);
+        bool definesPast;
+        if (!WouldDefinePastNonwritableLength(cx, obj, index, strict, &definesPast))
+            return false;
+        if (definesPast)
+            return true;
+
+        JSObject::setDenseElementWithType(cx, obj, index, vp);
         return true;
     }
 
+    if (obj->isArray() && id == NameToId(cx->names().length))
+        return ArraySetLength(cx, obj, id, attrs, vp, strict);
+
     if (!shape) {
         if (!obj->isExtensible()) {
             /* Error in strict mode code, warn with strict option, otherwise do nothing. */
             if (strict)
                 return obj->reportNotExtensible(cx);
             if (cx->hasStrictOption())
                 return obj->reportNotExtensible(cx, JSREPORT_STRICT | JSREPORT_WARNING);
             return true;
@@ -5038,20 +5100,20 @@ JSObject::dump()
         if (obj->inDictionaryMode())
             fprintf(stderr, " inDictionaryMode");
         if (obj->hasShapeTable())
             fprintf(stderr, " hasShapeTable");
     }
     fprintf(stderr, "\n");
 
     if (obj->isNative()) {
-        unsigned slots = obj->getDenseInitializedLength();
+        uint32_t slots = obj->getDenseInitializedLength();
         if (slots) {
             fprintf(stderr, "elements\n");
-            for (unsigned i = 0; i < slots; i++) {
+            for (uint32_t i = 0; i < slots; i++) {
                 fprintf(stderr, " %3d: ", i);
                 dumpValue(obj->getDenseElement(i));
                 fprintf(stderr, "\n");
                 fflush(stderr);
             }
         }
     }
 
@@ -5068,22 +5130,22 @@ JSObject::dump()
     fputc('\n', stderr);
 
     if (clasp->flags & JSCLASS_HAS_PRIVATE)
         fprintf(stderr, "private %p\n", obj->getPrivate());
 
     if (!obj->isNative())
         fprintf(stderr, "not native\n");
 
-    unsigned reservedEnd = JSCLASS_RESERVED_SLOTS(clasp);
-    unsigned slots = obj->slotSpan();
-    unsigned stop = obj->isNative() ? reservedEnd : slots;
+    uint32_t reservedEnd = JSCLASS_RESERVED_SLOTS(clasp);
+    uint32_t slots = obj->slotSpan();
+    uint32_t stop = obj->isNative() ? reservedEnd : slots;
     if (stop > 0)
         fprintf(stderr, obj->isNative() ? "reserved slots:\n" : "slots:\n");
-    for (unsigned i = 0; i < stop; i++) {
+    for (uint32_t i = 0; i < stop; i++) {
         fprintf(stderr, " %3d ", i);
         if (i < reservedEnd)
             fprintf(stderr, "(reserved) ");
         fprintf(stderr, "= ");
         dumpValue(obj->getSlot(i));
         fputc('\n', stderr);
     }
 
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -411,24 +411,24 @@ class JSObject : public js::ObjectImpl
      * Trigger the write barrier on a range of slots that will no longer be
      * reachable.
      */
     inline void prepareSlotRangeForOverwrite(size_t start, size_t end);
     inline void prepareElementRangeForOverwrite(size_t start, size_t end);
 
     void rollbackProperties(JSContext *cx, uint32_t slotSpan);
 
-    inline void nativeSetSlot(unsigned slot, const js::Value &value);
+    inline void nativeSetSlot(uint32_t slot, const js::Value &value);
     static inline void nativeSetSlotWithType(JSContext *cx, js::HandleObject, js::Shape *shape,
                                              const js::Value &value);
 
-    inline const js::Value &getReservedSlot(unsigned index) const;
-    inline js::HeapSlot &getReservedSlotRef(unsigned index);
-    inline void initReservedSlot(unsigned index, const js::Value &v);
-    inline void setReservedSlot(unsigned index, const js::Value &v);
+    inline const js::Value &getReservedSlot(uint32_t index) const;
+    inline js::HeapSlot &getReservedSlotRef(uint32_t index);
+    inline void initReservedSlot(uint32_t index, const js::Value &v);
+    inline void setReservedSlot(uint32_t index, const js::Value &v);
 
     /*
      * Marks this object as having a singleton type, and leave the type lazy.
      * Constructs a new, unique shape for the object.
      */
     static inline bool setSingletonType(JSContext *cx, js::HandleObject obj);
 
     inline js::types::TypeObject* getType(JSContext *cx);
@@ -566,87 +566,88 @@ class JSObject : public js::ObjectImpl
     struct MaybeContext {
         js::Allocator *allocator;
         JSContext *context;
 
         MaybeContext(JSContext *cx) : allocator(NULL), context(cx) {}
         MaybeContext(js::Allocator *alloc) : allocator(alloc), context(NULL) {}
     };
 
-    inline bool ensureElements(JSContext *cx, unsigned cap);
-    bool growElements(MaybeContext cx, unsigned newcap);
-    void shrinkElements(JSContext *cx, unsigned cap);
+    inline bool ensureElements(JSContext *cx, uint32_t cap);
+    bool growElements(MaybeContext cx, uint32_t newcap);
+    void shrinkElements(JSContext *cx, uint32_t cap);
     inline void setDynamicElements(js::ObjectElements *header);
 
     inline uint32_t getDenseCapacity();
     inline void setDenseInitializedLength(uint32_t length);
-    inline void ensureDenseInitializedLength(JSContext *cx, unsigned index, unsigned extra);
-    inline void setDenseElement(unsigned idx, const js::Value &val);
-    inline void initDenseElement(unsigned idx, const js::Value &val);
-    inline void setDenseElementMaybeConvertDouble(unsigned idx, const js::Value &val);
+    inline void ensureDenseInitializedLength(JSContext *cx, uint32_t index, uint32_t extra);
+    inline void setDenseElement(uint32_t index, const js::Value &val);
+    inline void initDenseElement(uint32_t index, const js::Value &val);
+    inline void setDenseElementMaybeConvertDouble(uint32_t index, const js::Value &val);
     static inline void setDenseElementWithType(JSContext *cx, js::HandleObject obj,
-                                               unsigned idx, const js::Value &val);
+                                               uint32_t index, const js::Value &val);
     static inline void initDenseElementWithType(JSContext *cx, js::HandleObject obj,
-                                                unsigned idx, const js::Value &val);
-    static inline void setDenseElementHole(JSContext *cx, js::HandleObject obj, unsigned idx);
+                                                uint32_t index, const js::Value &val);
+    static inline void setDenseElementHole(JSContext *cx, js::HandleObject obj, uint32_t index);
     static inline void removeDenseElementForSparseIndex(JSContext *cx, js::HandleObject obj,
-                                                        unsigned idx);
-    inline void copyDenseElements(unsigned dstStart, const js::Value *src, unsigned count);
-    inline void initDenseElements(unsigned dstStart, const js::Value *src, unsigned count);
-    inline void moveDenseElements(unsigned dstStart, unsigned srcStart, unsigned count);
-    inline void moveDenseElementsUnbarriered(unsigned dstStart, unsigned srcStart, unsigned count);
+                                                        uint32_t index);
+    inline void copyDenseElements(uint32_t dstStart, const js::Value *src, uint32_t count);
+    inline void initDenseElements(uint32_t dstStart, const js::Value *src, uint32_t count);
+    inline void moveDenseElements(uint32_t dstStart, uint32_t srcStart, uint32_t count);
+    inline void moveDenseElementsUnbarriered(uint32_t dstStart, uint32_t srcStart, uint32_t count);
     inline bool shouldConvertDoubleElements();
     inline void setShouldConvertDoubleElements();
 
     /* Packed information for this object's elements. */
     inline void markDenseElementsNotPacked(JSContext *cx);
 
     /*
      * ensureDenseElements ensures that the object can hold at least
      * index + extra elements. It returns ED_OK on success, ED_FAILED on
      * failure to grow the array, ED_SPARSE when the object is too sparse to
      * grow (this includes the case of index + extra overflow). In the last
      * two cases the object is kept intact.
      */
     enum EnsureDenseResult { ED_OK, ED_FAILED, ED_SPARSE };
-    inline EnsureDenseResult ensureDenseElements(JSContext *cx, unsigned index, unsigned extra);
+    inline EnsureDenseResult ensureDenseElements(JSContext *cx, uint32_t index, uint32_t extra);
     inline EnsureDenseResult parExtendDenseElements(js::Allocator *alloc, js::Value *v,
                                                     uint32_t extra);
     template<typename MallocProviderType>
     inline EnsureDenseResult extendDenseElements(MallocProviderType *cx,
-                                                 unsigned requiredCapacity, unsigned extra);
+                                                 uint32_t requiredCapacity, uint32_t extra);
 
     /* Convert a single dense element to a sparse property. */
-    static bool sparsifyDenseElement(JSContext *cx, js::HandleObject obj, unsigned index);
+    static bool sparsifyDenseElement(JSContext *cx, js::HandleObject obj, uint32_t index);
 
     /* Convert all dense elements to sparse properties. */
     static bool sparsifyDenseElements(JSContext *cx, js::HandleObject obj);
 
     /* Small objects are dense, no matter what. */
-    static const unsigned MIN_SPARSE_INDEX = 1000;
+    static const uint32_t MIN_SPARSE_INDEX = 1000;
 
     /*
      * Element storage for an object will be sparse if fewer than 1/8 indexes
      * are filled in.
      */
     static const unsigned SPARSE_DENSITY_RATIO = 8;
 
     /*
      * Check if after growing the object's elements will be too sparse.
      * newElementsHint is an estimated number of elements to be added.
      */
-    bool willBeSparseElements(unsigned requiredCapacity, unsigned newElementsHint);
+    bool willBeSparseElements(uint32_t requiredCapacity, uint32_t newElementsHint);
 
     /*
      * After adding a sparse index to obj, see if it should be converted to use
      * dense elements.
      */
     static EnsureDenseResult maybeDensifySparseElements(JSContext *cx, js::HandleObject obj);
 
     /* Array specific accessors. */
+    inline bool arrayLengthIsWritable() const;
     inline uint32_t getArrayLength() const;
     static inline void setArrayLength(JSContext *cx, js::HandleObject obj, uint32_t length);
     inline void setArrayLengthInt32(uint32_t length);
 
   public:
     /*
      * Date-specific getters and setters.
      */
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -314,38 +314,38 @@ JSObject::canRemoveLastProperty()
 inline const js::HeapSlot *
 JSObject::getRawSlots()
 {
     JS_ASSERT(isGlobal());
     return slots;
 }
 
 inline const js::Value &
-JSObject::getReservedSlot(unsigned index) const
+JSObject::getReservedSlot(uint32_t index) const
 {
     JS_ASSERT(index < JSSLOT_FREE(getClass()));
     return getSlot(index);
 }
 
 inline js::HeapSlot &
-JSObject::getReservedSlotRef(unsigned index)
+JSObject::getReservedSlotRef(uint32_t index)
 {
     JS_ASSERT(index < JSSLOT_FREE(getClass()));
     return getSlotRef(index);
 }
 
 inline void
-JSObject::setReservedSlot(unsigned index, const js::Value &v)
+JSObject::setReservedSlot(uint32_t index, const js::Value &v)
 {
     JS_ASSERT(index < JSSLOT_FREE(getClass()));
     setSlot(index, v);
 }
 
 inline void
-JSObject::initReservedSlot(unsigned index, const js::Value &v)
+JSObject::initReservedSlot(uint32_t index, const js::Value &v)
 {
     JS_ASSERT(index < JSSLOT_FREE(getClass()));
     initSlot(index, v);
 }
 
 inline void
 JSObject::prepareSlotRangeForOverwrite(size_t start, size_t end)
 {
@@ -363,20 +363,28 @@ JSObject::prepareElementRangeForOverwrit
 
 inline uint32_t
 JSObject::getArrayLength() const
 {
     JS_ASSERT(isArray());
     return getElementsHeader()->length;
 }
 
+inline bool
+JSObject::arrayLengthIsWritable() const
+{
+    JS_ASSERT(isArray());
+    return !getElementsHeader()->hasNonwritableArrayLength();
+}
+
 /* static */ inline void
 JSObject::setArrayLength(JSContext *cx, js::HandleObject obj, uint32_t length)
 {
     JS_ASSERT(obj->isArray());
+    JS_ASSERT(obj->arrayLengthIsWritable());
 
     if (length > INT32_MAX) {
         /* Track objects with overflowing lengths in type information. */
         js::types::MarkTypeObjectFlags(cx, obj,
                                        js::types::OBJECT_FLAG_LENGTH_OVERFLOW);
         jsid lengthId = js::NameToId(cx->names().length);
         js::types::AddTypePropertyId(cx, obj, lengthId,
                                      js::types::Type::DoubleType());
@@ -385,16 +393,17 @@ JSObject::setArrayLength(JSContext *cx, 
     obj->getElementsHeader()->length = length;
 }
 
 inline void
 JSObject::setArrayLengthInt32(uint32_t length)
 {
     /* Variant of setArrayLength for use on arrays where the length cannot overflow int32_t. */
     JS_ASSERT(isArray());
+    JS_ASSERT(arrayLengthIsWritable());
     JS_ASSERT(length <= INT32_MAX);
     getElementsHeader()->length = length;
 }
 
 inline void
 JSObject::setDenseInitializedLength(uint32_t length)
 {
     JS_ASSERT(isNative());
@@ -402,16 +411,17 @@ JSObject::setDenseInitializedLength(uint
     prepareElementRangeForOverwrite(length, getElementsHeader()->initializedLength);
     getElementsHeader()->initializedLength = length;
 }
 
 inline uint32_t
 JSObject::getDenseCapacity()
 {
     JS_ASSERT(isNative());
+    JS_ASSERT(getElementsHeader()->capacity >= getElementsHeader()->initializedLength);
     return getElementsHeader()->capacity;
 }
 
 inline bool
 JSObject::shouldConvertDoubleElements()
 {
     JS_ASSERT(isNative());
     return getElementsHeader()->shouldConvertDoubleElements();
@@ -436,92 +446,92 @@ inline void
 JSObject::setDynamicElements(js::ObjectElements *header)
 {
     JS_ASSERT(!hasDynamicElements());
     elements = header->elements();
     JS_ASSERT(hasDynamicElements());
 }
 
 inline void
-JSObject::setDenseElement(unsigned idx, const js::Value &val)
+JSObject::setDenseElement(uint32_t index, const js::Value &val)
 {
-    JS_ASSERT(isNative() && idx < getDenseInitializedLength());
-    elements[idx].set(this, js::HeapSlot::Element, idx, val);
+    JS_ASSERT(isNative() && index < getDenseInitializedLength());
+    elements[index].set(this, js::HeapSlot::Element, index, val);
 }
 
 inline void
-JSObject::setDenseElementMaybeConvertDouble(unsigned idx, const js::Value &val)
+JSObject::setDenseElementMaybeConvertDouble(uint32_t index, const js::Value &val)
 {
     if (val.isInt32() && shouldConvertDoubleElements())
-        setDenseElement(idx, js::DoubleValue(val.toInt32()));
+        setDenseElement(index, js::DoubleValue(val.toInt32()));
     else
-        setDenseElement(idx, val);
+        setDenseElement(index, val);
 }
 
 inline void
-JSObject::initDenseElement(unsigned idx, const js::Value &val)
+JSObject::initDenseElement(uint32_t index, const js::Value &val)
 {
-    JS_ASSERT(isNative() && idx < getDenseInitializedLength());
-    elements[idx].init(this, js::HeapSlot::Element, idx, val);
+    JS_ASSERT(isNative() && index < getDenseInitializedLength());
+    elements[index].init(this, js::HeapSlot::Element, index, val);
 }
 
 /* static */ inline void
-JSObject::setDenseElementWithType(JSContext *cx, js::HandleObject obj, unsigned idx,
+JSObject::setDenseElementWithType(JSContext *cx, js::HandleObject obj, uint32_t index,
                                   const js::Value &val)
 {
     js::types::AddTypePropertyId(cx, obj, JSID_VOID, val);
-    obj->setDenseElementMaybeConvertDouble(idx, val);
+    obj->setDenseElementMaybeConvertDouble(index, val);
 }
 
 /* static */ inline void
-JSObject::initDenseElementWithType(JSContext *cx, js::HandleObject obj, unsigned idx,
+JSObject::initDenseElementWithType(JSContext *cx, js::HandleObject obj, uint32_t index,
                                    const js::Value &val)
 {
     JS_ASSERT(!obj->shouldConvertDoubleElements());
     js::types::AddTypePropertyId(cx, obj, JSID_VOID, val);
-    obj->initDenseElement(idx, val);
+    obj->initDenseElement(index, val);
 }
 
 /* static */ inline void
-JSObject::setDenseElementHole(JSContext *cx, js::HandleObject obj, unsigned idx)
+JSObject::setDenseElementHole(JSContext *cx, js::HandleObject obj, uint32_t index)
 {
     js::types::MarkTypeObjectFlags(cx, obj, js::types::OBJECT_FLAG_NON_PACKED);
-    obj->setDenseElement(idx, js::MagicValue(JS_ELEMENTS_HOLE));
+    obj->setDenseElement(index, js::MagicValue(JS_ELEMENTS_HOLE));
 }
 
 /* static */ inline void
-JSObject::removeDenseElementForSparseIndex(JSContext *cx, js::HandleObject obj, unsigned idx)
+JSObject::removeDenseElementForSparseIndex(JSContext *cx, js::HandleObject obj, uint32_t index)
 {
     js::types::MarkTypeObjectFlags(cx, obj,
                                    js::types::OBJECT_FLAG_NON_PACKED |
                                    js::types::OBJECT_FLAG_SPARSE_INDEXES);
-    if (obj->containsDenseElement(idx))
-        obj->setDenseElement(idx, js::MagicValue(JS_ELEMENTS_HOLE));
+    if (obj->containsDenseElement(index))
+        obj->setDenseElement(index, js::MagicValue(JS_ELEMENTS_HOLE));
 }
 
 inline void
-JSObject::copyDenseElements(unsigned dstStart, const js::Value *src, unsigned count)
+JSObject::copyDenseElements(uint32_t dstStart, const js::Value *src, uint32_t count)
 {
     JS_ASSERT(dstStart + count <= getDenseCapacity());
     JS::Zone *zone = this->zone();
-    for (unsigned i = 0; i < count; ++i)
+    for (uint32_t i = 0; i < count; ++i)
         elements[dstStart + i].set(zone, this, js::HeapSlot::Element, dstStart + i, src[i]);
 }
 
 inline void
-JSObject::initDenseElements(unsigned dstStart, const js::Value *src, unsigned count)
+JSObject::initDenseElements(uint32_t dstStart, const js::Value *src, uint32_t count)
 {
     JS_ASSERT(dstStart + count <= getDenseCapacity());
     JSRuntime *rt = runtime();
-    for (unsigned i = 0; i < count; ++i)
+    for (uint32_t i = 0; i < count; ++i)
         elements[dstStart + i].init(rt, this, js::HeapSlot::Element, dstStart + i, src[i]);
 }
 
 inline void
-JSObject::moveDenseElements(unsigned dstStart, unsigned srcStart, unsigned count)
+JSObject::moveDenseElements(uint32_t dstStart, uint32_t srcStart, uint32_t count)
 {
     JS_ASSERT(dstStart + count <= getDenseCapacity());
     JS_ASSERT(srcStart + count <= getDenseInitializedLength());
 
     /*
      * Using memmove here would skip write barriers. Also, we need to consider
      * an array containing [A, B, C], in the following situation:
      *
@@ -533,32 +543,32 @@ JSObject::moveDenseElements(unsigned dst
      * write barrier is invoked here on B, despite the fact that it exists in
      * the array before and after the move.
     */
     JS::Zone *zone = this->zone();
     if (zone->needsBarrier()) {
         if (dstStart < srcStart) {
             js::HeapSlot *dst = elements + dstStart;
             js::HeapSlot *src = elements + srcStart;
-            for (unsigned i = 0; i < count; i++, dst++, src++)
+            for (uint32_t i = 0; i < count; i++, dst++, src++)
                 dst->set(zone, this, js::HeapSlot::Element, dst - elements, *src);
         } else {
             js::HeapSlot *dst = elements + dstStart + count - 1;
             js::HeapSlot *src = elements + srcStart + count - 1;
-            for (unsigned i = 0; i < count; i++, dst--, src--)
+            for (uint32_t i = 0; i < count; i++, dst--, src--)
                 dst->set(zone, this, js::HeapSlot::Element, dst - elements, *src);
         }
     } else {
         memmove(elements + dstStart, elements + srcStart, count * sizeof(js::HeapSlot));
         DenseRangeWriteBarrierPost(runtime(), this, dstStart, count);
     }
 }
 
 inline void
-JSObject::moveDenseElementsUnbarriered(unsigned dstStart, unsigned srcStart, unsigned count)
+JSObject::moveDenseElementsUnbarriered(uint32_t dstStart, uint32_t srcStart, uint32_t count)
 {
     JS_ASSERT(!zone()->needsBarrier());
 
     JS_ASSERT(dstStart + count <= getDenseCapacity());
     JS_ASSERT(srcStart + count <= getDenseCapacity());
 
     memmove(elements + dstStart, elements + srcStart, count * sizeof(js::Value));
 }
@@ -592,17 +602,17 @@ JSObject::ensureDenseInitializedLength(J
             sp->init(rt, this, js::HeapSlot::Element, offset, js::MagicValue(JS_ELEMENTS_HOLE));
         initlen = index + extra;
     }
 }
 
 template<typename MallocProviderType>
 JSObject::EnsureDenseResult
 JSObject::extendDenseElements(MallocProviderType *cx,
-                              unsigned requiredCapacity, unsigned extra)
+                              uint32_t requiredCapacity, uint32_t extra)
 {
     /*
      * Don't grow elements for non-extensible objects or watched objects. Dense
      * elements can be added/written with no extensible or watchpoint checks as
      * long as there is capacity for them.
      */
     if (!isExtensible() || watched()) {
         JS_ASSERT(getDenseCapacity() == 0);
@@ -631,20 +641,21 @@ JSObject::extendDenseElements(MallocProv
 
     return ED_OK;
 }
 
 inline JSObject::EnsureDenseResult
 JSObject::parExtendDenseElements(js::Allocator *alloc, js::Value *v, uint32_t extra)
 {
     JS_ASSERT(isNative());
+    JS_ASSERT_IF(isArray(), arrayLengthIsWritable());
 
     js::ObjectElements *header = getElementsHeader();
-    unsigned initializedLength = header->initializedLength;
-    unsigned requiredCapacity = initializedLength + extra;
+    uint32_t initializedLength = header->initializedLength;
+    uint32_t requiredCapacity = initializedLength + extra;
     if (requiredCapacity < initializedLength)
         return ED_SPARSE; /* Overflow. */
 
     if (requiredCapacity > header->capacity) {
         EnsureDenseResult edr = extendDenseElements(alloc, requiredCapacity, extra);
         if (edr != ED_OK)
             return edr;
     }
@@ -665,23 +676,23 @@ JSObject::parExtendDenseElements(js::All
     }
     header->initializedLength = requiredCapacity;
     if (header->length < requiredCapacity)
         header->length = requiredCapacity;
     return ED_OK;
 }
 
 inline JSObject::EnsureDenseResult
-JSObject::ensureDenseElements(JSContext *cx, unsigned index, unsigned extra)
+JSObject::ensureDenseElements(JSContext *cx, uint32_t index, uint32_t extra)
 {
     JS_ASSERT(isNative());
 
-    unsigned currentCapacity = getDenseCapacity();
+    uint32_t currentCapacity = getDenseCapacity();
 
-    unsigned requiredCapacity;
+    uint32_t requiredCapacity;
     if (extra == 1) {
         /* Optimize for the common case. */
         if (index < currentCapacity) {
             ensureDenseInitializedLength(cx, index, 1);
             return ED_OK;
         }
         requiredCapacity = index + 1;
         if (requiredCapacity == 0) {
@@ -1041,17 +1052,17 @@ JSObject::hasProperty(JSContext *cx, js:
 
 inline bool
 JSObject::isCallable()
 {
     return isFunction() || getClass()->call;
 }
 
 inline void
-JSObject::nativeSetSlot(unsigned slot, const js::Value &value)
+JSObject::nativeSetSlot(uint32_t slot, const js::Value &value)
 {
     JS_ASSERT(isNative());
     JS_ASSERT(slot < slotSpan());
     return setSlot(slot, value);
 }
 
 /* static */ inline void
 JSObject::nativeSetSlotWithType(JSContext *cx, js::HandleObject obj, js::Shape *shape,
--- a/js/src/methodjit/FastBuiltins.cpp
+++ b/js/src/methodjit/FastBuiltins.cpp
@@ -948,34 +948,16 @@ mjit::Compiler::inlineNativeFunction(uin
 
         if ((argType == JSVAL_TYPE_DOUBLE || argType == JSVAL_TYPE_INT32) &&
             type == JSVAL_TYPE_INT32) {
             return compileParseInt(argType, argc);
         }
     }
 
     if (argc == 0) {
-        if ((native == js::array_pop || native == js::array_shift) && thisType == JSVAL_TYPE_OBJECT) {
-            /*
-             * Only handle pop/shift on dense arrays which have never been used
-             * in an iterator --- when popping elements we don't account for
-             * suppressing deleted properties in active iterators.
-             *
-             * Constraints propagating properties directly into the result
-             * type set are generated by TypeConstraintCall during inference.
-             */
-            if (thisTypes->getKnownClass() == &ArrayClass &&
-                !thisTypes->hasObjectFlags(cx, types::OBJECT_FLAG_SPARSE_INDEXES |
-                                           types::OBJECT_FLAG_LENGTH_OVERFLOW |
-                                           types::OBJECT_FLAG_ITERATED) &&
-                !types::ArrayPrototypeHasIndexedProperty(cx, outerScript)) {
-                bool packed = !thisTypes->hasObjectFlags(cx, types::OBJECT_FLAG_NON_PACKED);
-                return compileArrayPopShift(thisValue, packed, native == js::array_pop);
-            }
-        }
     } else if (argc == 1) {
         FrameEntry *arg = frame.peek(-1);
         types::StackTypeSet *argTypes = frame.extra(arg).types;
         if (!argTypes)
             return Compile_InlineAbort;
         JSValueType argType = arg->isTypeKnown() ? arg->getKnownType() : JSVAL_TYPE_UNKNOWN;
 
         if (native == js_math_abs) {
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_5/Array/length-nonwritable-redefine-nop.js
@@ -0,0 +1,70 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ * Contributor:
+ *   Jeff Walden <jwalden+code@mit.edu>
+ */
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 858381;
+var summary = "No-op array length redefinition";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+var arr;
+
+// initializedLength == capacity == length
+// 6 == 6 == 6
+arr = Object.defineProperty([0, 1, 2, 3, 4, 5], "length", { writable: false });
+Object.defineProperty(arr, "length", { value: 6 });
+Object.defineProperty(arr, "length", { writable: false });
+Object.defineProperty(arr, "length", { configurable: false });
+Object.defineProperty(arr, "length", { writable: false, configurable: false });
+Object.defineProperty(arr, "length", { writable: false, value: 6 });
+Object.defineProperty(arr, "length", { configurable: false, value: 6 });
+Object.defineProperty(arr, "length", { writable: false, configurable: false, value: 6 });
+
+// initializedLength == capacity < length
+// 6 == 6 < 8
+arr = Object.defineProperty([0, 1, 2, 3, 4, 5], "length", { value: 8, writable: false });
+Object.defineProperty(arr, "length", { value: 8 });
+Object.defineProperty(arr, "length", { writable: false });
+Object.defineProperty(arr, "length", { configurable: false });
+Object.defineProperty(arr, "length", { writable: false, configurable: false });
+Object.defineProperty(arr, "length", { writable: false, value: 8 });
+Object.defineProperty(arr, "length", { configurable: false, value: 8 });
+Object.defineProperty(arr, "length", { writable: false, configurable: false, value: 8 });
+
+// initializedLength < capacity == length
+// 7 < 8 == 8
+arr = Object.defineProperty([0, 1, 2, 3, 4, 5, 6, /* hole */, ], "length",
+                            { value: 8, writable: false });
+Object.defineProperty(arr, "length", { value: 8 });
+Object.defineProperty(arr, "length", { writable: false });
+Object.defineProperty(arr, "length", { configurable: false });
+Object.defineProperty(arr, "length", { writable: false, configurable: false });
+Object.defineProperty(arr, "length", { writable: false, value: 8 });
+Object.defineProperty(arr, "length", { configurable: false, value: 8 });
+Object.defineProperty(arr, "length", { writable: false, configurable: false, value: 8 });
+
+// initializedLength < capacity < length
+// 3 < 6 < 8
+arr = Object.defineProperty([0, 1, 2], "length", { value: 8, writable: false });
+Object.defineProperty(arr, "length", { value: 8 });
+Object.defineProperty(arr, "length", { writable: false });
+Object.defineProperty(arr, "length", { configurable: false });
+Object.defineProperty(arr, "length", { writable: false, configurable: false });
+Object.defineProperty(arr, "length", { writable: false, value: 8 });
+Object.defineProperty(arr, "length", { configurable: false, value: 8 });
+Object.defineProperty(arr, "length", { writable: false, configurable: false, value: 8 });
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_5/Array/length-truncate-nonconfigurable-sparse.js
@@ -0,0 +1,110 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ * Contributor:
+ *   Jeff Walden <jwalden+code@mit.edu>
+ */
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 858381;
+var summary =
+  "Array length redefinition behavior with non-configurable elements";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+function addDataProperty(obj, prop, value, enumerable, configurable, writable)
+{
+  var desc =
+    { enumerable: enumerable,
+      configurable: configurable,
+      writable: writable,
+      value: value };
+  Object.defineProperty(obj, prop, desc);
+}
+
+function nonstrict()
+{
+  var arr = [0, , 2, , , 5];
+
+  addDataProperty(arr,  31415926, "foo", true,  true,  true);
+  addDataProperty(arr, 123456789, "bar", true,  true,  false);
+  addDataProperty(arr,   8675309, "qux", false, true,  true);
+  addDataProperty(arr,   1735039, "eit", false, true,  false);
+  addDataProperty(arr, 987654321, "fun", false, true,  false);
+
+  // non-array indexes to spice things up
+  addDataProperty(arr, "foopy", "sdfsd", false, false, false);
+  addDataProperty(arr, 4294967296, "psych", true, false, false);
+  addDataProperty(arr, 4294967295, "psych", true, false, false);
+
+  addDataProperty(arr,  27182818, "eep", false, false, false);
+
+  // Truncate...but only as far as possible.
+  arr.length = 1;
+
+  assertEq(arr.length, 27182819);
+
+  var props = Object.getOwnPropertyNames(arr).sort();
+  var expected =
+    ["0", "2", "5", "1735039", "8675309", "27182818",
+     "foopy", "4294967296", "4294967295", "length"].sort();
+
+  assertEq(props.length, expected.length);
+  for (var i = 0; i < props.length; i++)
+    assertEq(props[i], expected[i], "unexpected property: " + props[i]);
+}
+nonstrict();
+
+function strict()
+{
+  "use strict";
+
+  var arr = [0, , 2, , , 5];
+
+  addDataProperty(arr,  31415926, "foo", true,  true,  true);
+  addDataProperty(arr, 123456789, "bar", true,  true,  false);
+  addDataProperty(arr,   8675309, "qux", false, true,  true);
+  addDataProperty(arr,   1735039, "eit", false, true,  false);
+  addDataProperty(arr, 987654321, "fun", false, true,  false);
+
+  // non-array indexes to spice things up
+  addDataProperty(arr, "foopy", "sdfsd", false, false, false);
+  addDataProperty(arr, 4294967296, "psych", true, false, false);
+  addDataProperty(arr, 4294967295, "psych", true, false, false);
+
+  addDataProperty(arr,  27182818, "eep", false, false, false);
+
+  try
+  {
+    arr.length = 1;
+    throw new Error("didn't throw?!");
+  }
+  catch (e)
+  {
+    assertEq(e instanceof TypeError, true,
+             "non-configurable property should trigger TypeError, got " + e);
+  }
+
+  assertEq(arr.length, 27182819);
+
+  var props = Object.getOwnPropertyNames(arr).sort();
+  var expected =
+    ["0", "2", "5", "1735039", "8675309", "27182818",
+     "foopy", "4294967296", "4294967295", "length"].sort();
+
+  assertEq(props.length, expected.length);
+  for (var i = 0; i < props.length; i++)
+    assertEq(props[i], expected[i], "unexpected property: " + props[i]);
+}
+strict();
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_5/Array/length-truncate-nonconfigurable.js
@@ -0,0 +1,48 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ * Contributor:
+ *   Jeff Walden <jwalden+code@mit.edu>
+ */
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 858381;
+var summary =
+  "Array length redefinition behavior with non-configurable elements";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+var arr = [0, 1, 2];
+Object.defineProperty(arr, 1, { configurable: false });
+
+try
+{
+  Object.defineProperty(arr, "length", { value: 0, writable: false });
+}
+catch (e)
+{
+  assertEq(e instanceof TypeError, true,
+           "must throw TypeError when array truncation would have to remove " +
+           "non-configurable elements");
+}
+
+assertEq(arr.length, 2, "length is highest remaining index plus one");
+
+var desc = Object.getOwnPropertyDescriptor(arr, "length");
+assertEq(desc !== undefined, true);
+
+assertEq(desc.value, 2);
+assertEq(desc.writable, false);
+assertEq(desc.enumerable, false);
+assertEq(desc.configurable, false);
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_5/Array/length-truncate-with-indexed.js
@@ -0,0 +1,101 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ * Contributor:
+ *   Jeff Walden <jwalden+code@mit.edu>
+ */
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 858381;
+var summary =
+  "Array length setting/truncating with non-dense, indexed elements";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+function testTruncateDenseAndSparse()
+{
+  var arr;
+
+  // initialized length 16, capacity same
+  arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
+
+  // plus a sparse element
+  arr[987654321] = 987654321;
+
+  // lop off the sparse element and half the dense elements, shrink capacity
+  arr.length = 8;
+
+  assertEq(987654321 in arr, false);
+  assertEq(arr[987654321], undefined);
+  assertEq(arr.length, 8);
+}
+testTruncateDenseAndSparse();
+
+function testTruncateSparse()
+{
+  // initialized length 8, capacity same
+  var arr = [0, 1, 2, 3, 4, 5, 6, 7];
+
+  // plus a sparse element
+  arr[987654321] = 987654321;
+
+  // lop off the sparse element, leave initialized length/capacity unchanged
+  arr.length = 8;
+
+  assertEq(987654321 in arr, false);
+  assertEq(arr[987654321], undefined);
+  assertEq(arr.length, 8);
+}
+testTruncateSparse();
+
+function testTruncateDenseAndSparseShrinkCapacity()
+{
+  // initialized length 11, capacity...somewhat larger, likely 16
+  var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+
+  // plus a sparse element
+  arr[987654321] = 987654321;
+
+  // lop off the sparse element, reduce initialized length, reduce capacity
+  arr.length = 8;
+
+  assertEq(987654321 in arr, false);
+  assertEq(arr[987654321], undefined);
+  assertEq(arr.length, 8);
+}
+testTruncateDenseAndSparseShrinkCapacity();
+
+function testTruncateSparseShrinkCapacity()
+{
+  // initialized length 8, capacity same
+  var arr = [0, 1, 2, 3, 4, 5, 6, 7];
+
+  // capacity expands to accommodate, initialized length remains same (not equal
+  // to capacity or length)
+  arr[15] = 15;
+
+  // now no elements past initialized length
+  delete arr[15];
+
+  // ...except a sparse element
+  arr[987654321] = 987654321;
+
+  // trims sparse element, doesn't change initialized length, shrinks capacity
+  arr.length = 8;
+
+  assertEq(987654321 in arr, false);
+  assertEq(arr[987654321], undefined);
+  assertEq(arr.length, 8);
+}
+testTruncateSparseShrinkCapacity();
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_5/Array/pop-empty-nonwritable.js
@@ -0,0 +1,32 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 858381;
+var summary = 'Object.freeze([]).pop() must throw a TypeError';
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+try
+{
+  Object.freeze([]).pop();
+  throw new Error("didn't throw");
+}
+catch (e)
+{
+  assertEq(e instanceof TypeError, true,
+           "should have thrown TypeError, instead got: " + e);
+}
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_5/extensions/array-pop-proxy.js
@@ -0,0 +1,24 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+var gTestfile = 'array-pop-proxy.js';
+var BUGNUMBER = 858381;
+var summary = "Behavior of [].pop on proxies";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+var p = new Proxy([0, 1, 2], {});
+Array.prototype.pop.call(p);
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
--- a/js/src/tests/ecma_5/strict/15.4.5.1.js
+++ b/js/src/tests/ecma_5/strict/15.4.5.1.js
@@ -1,30 +1,84 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/
  */
 
+var out = {};
+
 function arr() {
   return Object.defineProperty([1, 2, 3, 4], 2, {configurable: false});
 }
 
-assertEq(testLenientAndStrict('var a = arr(); a.length = 2; a',
-                              returnsCopyOf([1, 2, 3]),
-                              raisesException(TypeError)),
-         true);
+function nonStrict1(out)
+{
+  var a = out.array = arr();
+  a.length = 2;
+}
+
+function strict1(out)
+{
+  "use strict";
+  var a = out.array = arr();
+  a.length = 2;
+  return a;
+}
+
+out.array = null;
+nonStrict1(out);
+assertEq(deepEqual(out.array, [1, 2, 3]), true);
+
+out.array = null;
+try
+{
+  strict1(out);
+  throw "no error";
+}
+catch (e)
+{
+  assertEq(e instanceof TypeError, true, "expected TypeError, got " + e);
+}
+assertEq(deepEqual(out.array, [1, 2, 3]), true);
 
 // Internally, SpiderMonkey has two representations for arrays:
 // fast-but-inflexible, and slow-but-flexible. Adding a non-index property
 // to an array turns it into the latter. We should test on both kinds.
 function addx(obj) {
   obj.x = 5;
   return obj;
 }
 
-assertEq(testLenientAndStrict('var a = addx(arr()); a.length = 2; a',
-                              returnsCopyOf(addx([1, 2, 3])),
-                              raisesException(TypeError)),
-         true);
+function nonStrict2(out)
+{
+  var a = out.array = addx(arr());
+  a.length = 2;
+}
+
+function strict2(out)
+{
+  "use strict";
+  var a = out.array = addx(arr());
+  a.length = 2;
+}
+
+out.array = null;
+nonStrict2(out);
+assertEq(deepEqual(out.array, addx([1, 2, 3])), true);
 
-reportCompare(true, true);
+out.array = null;
+try
+{
+  strict2(out);
+  throw "no error";
+}
+catch (e)
+{
+  assertEq(e instanceof TypeError, true, "expected TypeError, got " + e);
+}
+assertEq(deepEqual(out.array, addx([1, 2, 3])), true);
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
--- a/js/src/tests/js1_6/Array/regress-304828.js
+++ b/js/src/tests/js1_6/Array/regress-304828.js
@@ -53,24 +53,41 @@ catch(e)
 }
 reportCompare(expect, actual, summary + ': sort');
 
 // push
 value  = 'abc';
 expect = 6;
 try
 {
-  actual = Array.prototype.push.call(value, 'd', 'e', 'f');
+  Array.prototype.push.call(value, 'd', 'e', 'f');
+  throw new Error("didn't throw");
 }
 catch(e)
 {
-  actual = e + '';
+  reportCompare(true, e instanceof TypeError,
+                "push on a string primitive should throw TypeError");
 }
-reportCompare(expect, actual, summary + ': push');
-reportCompare('abc', value, summary + ': push');
+reportCompare('abc', value, summary + ': push string primitive');
+
+value  = new String("abc");
+expect = 6;
+try
+{
+  Array.prototype.push.call(value, 'd', 'e', 'f');
+  throw new Error("didn't throw");
+}
+catch(e)
+{
+  reportCompare(true, e instanceof TypeError,
+                "push on a String object should throw TypeError");
+}
+reportCompare("d", value[3], summary + ': push String object index 3');
+reportCompare("e", value[4], summary + ': push String object index 4');
+reportCompare("f", value[5], summary + ': push String object index 5');
 
 // pop
 value  = 'abc';
 expect = "TypeError: property Array.prototype.pop.call(...) is non-configurable and can't be deleted";
 try
 {
   actual = Array.prototype.pop.call(value);
 }
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -854,16 +854,28 @@ ElementsHeader::asArrayBufferElements()
 {
     MOZ_ASSERT(isArrayBufferElements());
     return *static_cast<ArrayBufferElementsHeader *>(this);
 }
 
 class ArrayBufferObject;
 
 /*
+ * ES6 20130308 draft 8.4.2.4 ArraySetLength.
+ *
+ * |id| must be "length", |attrs| are the attributes to be used for the newly-
+ * changed length property, |value| is the value for the new length, and
+ * |setterIsStrict| indicates whether invalid changes will cause a TypeError
+ * to be thrown.
+ */
+extern bool
+ArraySetLength(JSContext *cx, HandleObject obj, HandleId id, unsigned attrs, HandleValue value,
+               bool setterIsStrict);
+
+/*
  * Elements header used for all native objects. The elements component of such
  * objects offers an efficient representation for all or some of the indexed
  * properties of the object, using a flat array of Values rather than a shape
  * hierarchy stored in the object's slots. This structure is immediately
  * followed by an array of elements, with the elements member in an object
  * pointing to the beginning of that array (the end of this structure).
  * See below for usage of this structure.
  *
@@ -886,22 +898,30 @@ class ArrayBufferObject;
  *    getDenseElementsCapacity().
  *  - The array's initialized length, accessible with
  *    getDenseElementsInitializedLength().
  *
  * Holes in the array are represented by MagicValue(JS_ELEMENTS_HOLE) values.
  * These indicate indexes which are not dense properties of the array. The
  * property may, however, be held by the object's properties.
  *
- * NB: the capacity and length of an object are entirely unrelated!  The
- * length may be greater than, less than, or equal to the capacity. The first
- * case may occur when the user writes "new Array(100)", in which case the
- * length is 100 while the capacity remains 0 (indices below length and above
- * capacity must be treated as holes). See array_length_setter for another
- * explanation of how the first case may occur.
+ * The capacity and length of an object's elements are almost entirely
+ * unrelated!  In general the length may be greater than, less than, or equal
+ * to the capacity.  The first case occurs with |new Array(100)|.  The length
+ * is 100, but the capacity remains 0 (indices below length and above capacity
+ * must be treated as holes) until elements between capacity and length are
+ * set.  The other two cases are common, depending upon the number of elements
+ * in an array and the underlying allocator used for element storage.
+ *
+ * The only case in which the capacity and length of an object's elements are
+ * related is when the object is an array with non-writable length.  In this
+ * case the capacity is always less than or equal to the length.  This permits
+ * JIT code to optimize away the check for non-writable length when assigning
+ * to possibly out-of-range elements: such code already has to check for
+ * |index < capacity|, and fallback code checks for non-writable length.
  *
  * The initialized length of an object specifies the number of elements that
  * have been initialized. All elements above the initialized length are
  * holes in the object, and the memory for all elements between the initialized
  * length and capacity is left uninitialized. When type inference is disabled,
  * the initialized length always equals the capacity. When inference is
  * enabled, the initialized length is some value less than or equal to both the
  * object's length and the object's capacity.
@@ -919,25 +939,33 @@ class ArrayBufferObject;
  * of an object does not necessarily visit indexes in the order they were
  * created.
  */
 class ObjectElements
 {
   public:
     enum Flags {
         CONVERT_DOUBLE_ELEMENTS = 0x1,
-        ASMJS_ARRAY_BUFFER = 0x2
+        ASMJS_ARRAY_BUFFER = 0x2,
+
+        // Present only if these elements correspond to an array with
+        // non-writable length; never present for non-arrays.
+        NONWRITABLE_ARRAY_LENGTH = 0x4
     };
 
   private:
     friend class ::JSObject;
     friend class ObjectImpl;
     friend class ArrayBufferObject;
     friend class Nursery;
 
+    friend bool
+    ArraySetLength(JSContext *cx, HandleObject obj, HandleId id, unsigned attrs, HandleValue value,
+                   bool setterIsStrict);
+
     /* See Flags enum above. */
     uint32_t flags;
 
     /*
      * Number of initialized elements. This is <= the capacity, and for arrays
      * is <= the length. Memory for elements above the initialized length is
      * uninitialized, but values between the initialized length and the proper
      * length are conceptually holes.
@@ -969,16 +997,22 @@ class ObjectElements
         flags |= CONVERT_DOUBLE_ELEMENTS;
     }
     bool isAsmJSArrayBuffer() const {
         return flags & ASMJS_ARRAY_BUFFER;
     }
     void setIsAsmJSArrayBuffer() {
         flags |= ASMJS_ARRAY_BUFFER;
     }
+    bool hasNonwritableArrayLength() const {
+        return flags & NONWRITABLE_ARRAY_LENGTH;
+    }
+    void setNonwritableArrayLength() {
+        flags |= NONWRITABLE_ARRAY_LENGTH;
+    }
 
   public:
     ObjectElements(uint32_t capacity, uint32_t length)
       : flags(0), initializedLength(0), capacity(capacity), length(length)
     {}
 
     HeapSlot *elements() { return (HeapSlot *)(uintptr_t(this) + sizeof(ObjectElements)); }
     static ObjectElements * fromElements(HeapSlot *elems) {
@@ -1078,16 +1112,20 @@ class ObjectImpl : public gc::Cell
      * set, this is the prototype's default 'new' type and can only be used
      * to get that prototype.
      */
     HeapPtrTypeObject type_;
 
     HeapSlot *slots;     /* Slots for object properties. */
     HeapSlot *elements;  /* Slots for object elements. */
 
+    friend bool
+    ArraySetLength(JSContext *cx, HandleObject obj, HandleId id, unsigned attrs, HandleValue value,
+                   bool setterIsStrict);
+
   private:
     static void staticAsserts() {
         MOZ_STATIC_ASSERT(sizeof(ObjectImpl) == sizeof(shadow::Object),
                           "shadow interface must match actual implementation");
         MOZ_STATIC_ASSERT(sizeof(ObjectImpl) % sizeof(Value) == 0,
                           "fixed slots after an object must be aligned");
 
         MOZ_STATIC_ASSERT(offsetof(ObjectImpl, shape_) == offsetof(shadow::Object, shape),
@@ -1407,17 +1445,17 @@ class ObjectImpl : public gc::Cell
     inline bool hasDynamicElements() const {
         /*
          * Note: for objects with zero fixed slots this could potentially give
          * a spurious 'true' result, if the end of this object is exactly
          * aligned with the end of its arena and dynamic slots are allocated
          * immediately afterwards. Such cases cannot occur for dense arrays
          * (which have at least two fixed slots) and can only result in a leak.
          */
-        return elements != emptyObjectElements && elements != fixedElements();
+        return !hasEmptyElements() && elements != fixedElements();
     }
 
     inline bool hasFixedElements() const {
         return elements == fixedElements();
     }
 
     inline bool hasEmptyElements() const {
         return elements == emptyObjectElements;
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -583,16 +583,24 @@ CheckCanChangeAttrs(JSContext *cx, JSObj
 /* static */ RawShape
 JSObject::putProperty(JSContext *cx, HandleObject obj, HandleId id,
                       PropertyOp getter, StrictPropertyOp setter,
                       uint32_t slot, unsigned attrs,
                       unsigned flags, int shortid)
 {
     JS_ASSERT(!JSID_IS_VOID(id));
 
+#ifdef DEBUG
+    if (obj->isArray()) {
+        uint32_t index;
+        if (js_IdIsIndex(id, &index))
+            JS_ASSERT(index < obj->getArrayLength() || obj->arrayLengthIsWritable());
+    }
+#endif
+
     NormalizeGetterAndSetter(obj, id, attrs, flags, getter, setter);
 
     AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter);
 
     /* Search for id in order to claim its entry if table has been allocated. */
     Shape **spp;
     RootedShape shape(cx, Shape::search(cx, obj->lastProperty(), id, &spp, true));
     if (!shape) {