blob: 590327306aeeecff0af7b984cce3894bf7fc6dfe [file] [log] [blame]
/* -*- 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/. */
#include "jsapi.h"
#include "jscntxt.h"
#include "jscompartment.h"
#include "jsobj.h"
#include "vm/Interpreter.h"
#include "vm/PIC.h"
#include "jsobjinlines.h"
using namespace js;
using JS::ForOfIterator;
using mozilla::UniquePtr;
bool
ForOfIterator::init(HandleValue iterable, NonIterableBehavior nonIterableBehavior)
{
JSContext* cx = cx_;
RootedObject iterableObj(cx, ToObject(cx, iterable));
if (!iterableObj)
return false;
MOZ_ASSERT(index == NOT_ARRAY);
// Check the PIC first for a match.
if (iterableObj->is<ArrayObject>()) {
ForOfPIC::Chain* stubChain = ForOfPIC::getOrCreate(cx);
if (!stubChain)
return false;
bool optimized;
if (!stubChain->tryOptimizeArray(cx, iterableObj.as<ArrayObject>(), &optimized))
return false;
if (optimized) {
// Got optimized stub. Array is optimizable.
index = 0;
iterator = iterableObj;
return true;
}
}
MOZ_ASSERT(index == NOT_ARRAY);
// The iterator is the result of calling obj[@@iterator]().
InvokeArgs args(cx);
if (!args.init(0))
return false;
args.setThis(iterable);
RootedValue callee(cx);
RootedId iteratorId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator));
if (!GetProperty(cx, iterableObj, iterableObj, iteratorId, &callee))
return false;
// If obj[@@iterator] is undefined and we were asked to allow non-iterables,
// bail out now without setting iterator. This will make valueIsIterable(),
// which our caller should check, return false.
if (nonIterableBehavior == AllowNonIterable && callee.isUndefined())
return true;
// Throw if obj[@@iterator] isn't callable.
// js::Invoke is about to check for this kind of error anyway, but it would
// throw an inscrutable error message about |method| rather than this nice
// one about |obj|.
if (!callee.isObject() || !callee.toObject().isCallable()) {
UniquePtr<char[], JS::FreePolicy> bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK,
iterable, nullptr);
if (!bytes)
return false;
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, bytes.get());
return false;
}
args.setCallee(callee);
if (!Invoke(cx, args))
return false;
iterator = ToObject(cx, args.rval());
if (!iterator)
return false;
return true;
}
inline bool
ForOfIterator::nextFromOptimizedArray(MutableHandleValue vp, bool* done)
{
MOZ_ASSERT(index != NOT_ARRAY);
if (!CheckForInterrupt(cx_))
return false;
ArrayObject* arr = &iterator->as<ArrayObject>();
if (index >= arr->length()) {
vp.setUndefined();
*done = true;
return true;
}
*done = false;
// Try to get array element via direct access.
if (index < arr->getDenseInitializedLength()) {
vp.set(arr->getDenseElement(index));
if (!vp.isMagic(JS_ELEMENTS_HOLE)) {
++index;
return true;
}
}
return GetElement(cx_, iterator, iterator, index++, vp);
}
bool
ForOfIterator::next(MutableHandleValue vp, bool* done)
{
MOZ_ASSERT(iterator);
if (index != NOT_ARRAY) {
ForOfPIC::Chain* stubChain = ForOfPIC::getOrCreate(cx_);
if (!stubChain)
return false;
if (stubChain->isArrayNextStillSane())
return nextFromOptimizedArray(vp, done);
// ArrayIterator.prototype.next changed, materialize a proper
// ArrayIterator instance and fall through to slowpath case.
if (!materializeArrayIterator())
return false;
}
RootedValue method(cx_);
if (!GetProperty(cx_, iterator, iterator, cx_->names().next, &method))
return false;
InvokeArgs args(cx_);
if (!args.init(0))
return false;
args.setCallee(method);
args.setThis(ObjectValue(*iterator));
if (!Invoke(cx_, args))
return false;
RootedObject resultObj(cx_, ToObject(cx_, args.rval()));
if (!resultObj)
return false;
RootedValue doneVal(cx_);
if (!GetProperty(cx_, resultObj, resultObj, cx_->names().done, &doneVal))
return false;
*done = ToBoolean(doneVal);
if (*done) {
vp.setUndefined();
return true;
}
return GetProperty(cx_, resultObj, resultObj, cx_->names().value, vp);
}
bool
ForOfIterator::materializeArrayIterator()
{
MOZ_ASSERT(index != NOT_ARRAY);
HandlePropertyName name = cx_->names().ArrayValuesAt;
RootedValue val(cx_);
if (!GlobalObject::getSelfHostedFunction(cx_, cx_->global(), name, name, 1, &val))
return false;
InvokeArgs args(cx_);
if (!args.init(1))
return false;
args.setCallee(val);
args.setThis(ObjectValue(*iterator));
args[0].set(Int32Value(index));
if (!Invoke(cx_, args))
return false;
index = NOT_ARRAY;
// Result of call to ArrayValuesAt must be an object.
iterator = &args.rval().toObject();
return true;
}