Step 25 reads:
> Let T be a String value equal to the substring of S consisting of the
> elements at indices p (inclusive) through size (exclusive).
In the case where there is a successful match against a non-empty `string`
argument, the value of `p` can be traced through the following steps:
> 19. Let size be the number of elements in S.
> 20. Let p be 0.
> [...]
> 23. Let q be p.
> 24. Repeat, while q < size
> [...]
> c. Let z be RegExpExec(splitter, S).
> [...]
> f. Else z is not null,
> i. Let e be ToLength(Get(splitter, "lastIndex")).
> ii. ReturnIfAbrupt(e).
> [...]
> iv. Else e ≠ p,
> [...]
> 6. Let p be e.
> [...]
> 12. Let q be p.
> 25. (see above)
Because RegExpExec defers to the user-defined `exec` method where present, `e`
may take any value at 24.f.i (as suggested by the ReturnIfAbrupt in the
following step). This will also be the value of `p` as the algorithm approaches
completion. The ToLength abstract operation ensures that `p` is greater than or
equal to zero, but there is no guarantee that it will be less than the value of
`size`. The specification does not include any detail about how the host should
behave if the value is out-of-bounds here. Maybe a RangeError is appropriate?
Example code:
var obj = {
constructor: function() {}
};
var fakeRe = {
exec: function() {
fakeRe.lastIndex = 999;
return [];
}
};
obj.constructor[Symbol.species] = function() {
return fakeRe;
};
RegExp.prototype[Symbol.split].call(obj, 'a');
Fixed in ES2016 Draft (08b4756).