blob: 37e4454f98c9a6ed5d6ecbf0b785567f567189b5 [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 "builtin/Reflect.h"
#include "jsarray.h"
#include "jscntxt.h"
#include "vm/ArgumentsObject.h"
#include "vm/Stack.h"
#include "vm/Interpreter-inl.h"
using namespace js;
/*** Reflect methods *****************************************************************************/
/*
* ES draft rev 32 (2015 Feb 2) 7.3.17 CreateListFromArrayLike.
* The elementTypes argument is not supported. The result list is
* pushed to *args.
*/
template <class InvokeArgs>
static bool
InitArgsFromArrayLike(JSContext* cx, HandleValue v, InvokeArgs* args)
{
// Step 3.
RootedObject obj(cx, NonNullObject(cx, v));
if (!obj)
return false;
// Steps 4-5.
uint32_t len;
if (!GetLengthProperty(cx, obj, &len))
return false;
// Allocate space for the arguments.
if (len > ARGS_LENGTH_MAX) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TOO_MANY_FUN_APPLY_ARGS);
return false;
}
if (!args->init(len))
return false;
// Steps 6-8.
for (uint32_t index = 0; index < len; index++) {
if (!GetElement(cx, obj, obj, index, (*args)[index]))
return false;
}
// Step 9.
return true;
}
/* ES6 26.1.1 Reflect.apply(target, thisArgument, argumentsList) */
static bool
Reflect_apply(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
if (!IsCallable(args.get(0))) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION,
"Reflect.apply argument");
return false;
}
// Steps 2-3.
FastInvokeGuard fig(cx, args.get(0));
InvokeArgs& invokeArgs = fig.args();
if (!InitArgsFromArrayLike(cx, args.get(2), &invokeArgs))
return false;
invokeArgs.setCallee(args.get(0));
invokeArgs.setThis(args.get(1));
// Steps 4-5. This is specified to be a tail call, but isn't.
if (!fig.invoke(cx))
return false;
args.rval().set(invokeArgs.rval());
return true;
}
/* ES6 26.1.2 Reflect.construct(target, argumentsList [, newTarget]) */
static bool
Reflect_construct(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
if (!IsConstructor(args.get(0))) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_CONSTRUCTOR,
"Reflect.construct argument");
return false;
}
// Steps 2-3.
RootedValue newTarget(cx, args.get(0));
if (argc > 2) {
newTarget = args[2];
if (!IsConstructor(newTarget)) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_CONSTRUCTOR,
"Reflect.construct argument 3");
return false;
}
}
// Step 4-5.
ConstructArgs constructArgs(cx);
if (!InitArgsFromArrayLike(cx, args.get(1), &constructArgs))
return false;
// Step 6.
return Construct(cx, args.get(0), constructArgs, newTarget, args.rval());
}
/* ES6 26.1.3 Reflect.defineProperty(target, propertyKey, attributes) */
static bool
Reflect_defineProperty(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
RootedObject obj(cx, NonNullObject(cx, args.get(0)));
if (!obj)
return false;
// Steps 2-3.
RootedValue propertyKey(cx, args.get(1));
RootedId key(cx);
if (!ToPropertyKey(cx, propertyKey, &key))
return false;
// Steps 4-5.
Rooted<JSPropertyDescriptor> desc(cx);
if (!ToPropertyDescriptor(cx, args.get(2), true, &desc))
return false;
// Step 6.
ObjectOpResult result;
if (!DefineProperty(cx, obj, key, desc, result))
return false;
args.rval().setBoolean(bool(result));
return true;
}
/* ES6 26.1.4 Reflect.deleteProperty (target, propertyKey) */
static bool
Reflect_deleteProperty(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
RootedObject target(cx, NonNullObject(cx, args.get(0)));
if (!target)
return false;
// Steps 2-3.
RootedValue propertyKey(cx, args.get(1));
RootedId key(cx);
if (!ToPropertyKey(cx, propertyKey, &key))
return false;
// Step 4.
ObjectOpResult result;
if (!DeleteProperty(cx, target, key, result))
return false;
args.rval().setBoolean(bool(result));
return true;
}
#if 0
/*
* ES6 26.1.5 Reflect.enumerate(target)
*
* TODO:
* - redefine enumeration in terms of iterators without losing performance
* - support iterators in Proxies
*/
static bool
Reflect_enumerate(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
RootedObject obj(cx, NonNullObject(cx, args.get(0)));
if (!obj)
return false;
// Step 2.
RootedObject iterator(cx);
if (!Enumerate(cx, obj, &iterator))
return false;
args.rval().setObject(*iterator);
return true;
}
#endif
/*
* ES6 26.1.6 Reflect.get(target, propertyKey [, receiver])
*
* Primitive receivers are not supported yet (see bug 603201).
*/
static bool
Reflect_get(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
RootedObject obj(cx, NonNullObject(cx, args.get(0)));
if (!obj)
return false;
// Steps 2-3.
RootedValue propertyKey(cx, args.get(1));
RootedId key(cx);
if (!ToPropertyKey(cx, propertyKey, &key))
return false;
// Step 4.
RootedValue receiver(cx, argc > 2 ? args[2] : args.get(0));
// Non-standard hack: Throw a TypeError if the receiver isn't an object.
// See bug 603201.
RootedObject receiverObj(cx, NonNullObject(cx, receiver));
if (!receiverObj)
return false;
// Step 5.
return GetProperty(cx, obj, receiverObj, key, args.rval());
}
/* ES6 26.1.7 Reflect.getOwnPropertyDescriptor(target, propertyKey) */
static bool
Reflect_getOwnPropertyDescriptor(JSContext* cx, unsigned argc, Value* vp)
{
// Step 1.
CallArgs args = CallArgsFromVp(argc, vp);
if (!NonNullObject(cx, args.get(0)))
return false;
// The other steps are identical to ES6 draft rev 32 (2015 Feb 2) 19.1.2.6
// Object.getOwnPropertyDescriptor.
return js::obj_getOwnPropertyDescriptor(cx, argc, vp);
}
/* ES6 26.1.8 Reflect.getPrototypeOf(target) */
bool
js::Reflect_getPrototypeOf(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
RootedObject target(cx, NonNullObject(cx, args.get(0)));
if (!target)
return false;
// Step 2.
RootedObject proto(cx);
if (!GetPrototype(cx, target, &proto))
return false;
args.rval().setObjectOrNull(proto);
return true;
}
/* ES6 draft 26.1.10 Reflect.isExtensible(target) */
bool
js::Reflect_isExtensible(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
RootedObject target(cx, NonNullObject(cx, args.get(0)));
if (!target)
return false;
// Step 2.
bool extensible;
if (!IsExtensible(cx, target, &extensible))
return false;
args.rval().setBoolean(extensible);
return true;
}
/* ES6 26.1.11 Reflect.ownKeys(target) */
static bool
Reflect_ownKeys(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
if (!NonNullObject(cx, args.get(0)))
return false;
// Steps 2-4.
return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS);
}
/* ES6 26.1.12 Reflect.preventExtensions(target) */
static bool
Reflect_preventExtensions(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
RootedObject target(cx, NonNullObject(cx, args.get(0)));
if (!target)
return false;
// Step 2.
ObjectOpResult result;
if (!PreventExtensions(cx, target, result))
return false;
args.rval().setBoolean(bool(result));
return true;
}
/* ES6 26.1.13 Reflect.set(target, propertyKey, V [, receiver]) */
static bool
Reflect_set(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
RootedObject target(cx, NonNullObject(cx, args.get(0)));
if (!target)
return false;
// Steps 2-3.
RootedValue propertyKey(cx, args.get(1));
RootedId key(cx);
if (!ToPropertyKey(cx, propertyKey, &key))
return false;
// Step 4.
RootedValue receiver(cx, argc > 3 ? args[3] : args.get(0));
// Step 5.
ObjectOpResult result;
RootedValue value(cx, args.get(2));
if (!SetProperty(cx, target, key, value, receiver, result))
return false;
args.rval().setBoolean(bool(result));
return true;
}
/*
* ES6 26.1.3 Reflect.setPrototypeOf(target, proto)
*
* The specification is not quite similar enough to Object.setPrototypeOf to
* share code.
*/
static bool
Reflect_setPrototypeOf(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
RootedObject obj(cx, NonNullObject(cx, args.get(0)));
if (!obj)
return false;
// Step 2.
if (!args.get(1).isObjectOrNull()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
"Reflect.setPrototypeOf", "an object or null",
InformalValueTypeName(args.get(1)));
return false;
}
RootedObject proto(cx, args.get(1).toObjectOrNull());
// Step 4.
ObjectOpResult result;
if (!SetPrototype(cx, obj, proto, result))
return false;
args.rval().setBoolean(bool(result));
return true;
}
static const JSFunctionSpec methods[] = {
JS_FN("apply", Reflect_apply, 3, 0),
JS_FN("construct", Reflect_construct, 2, 0),
JS_FN("defineProperty", Reflect_defineProperty, 3, 0),
JS_FN("deleteProperty", Reflect_deleteProperty, 2, 0),
// JS_FN("enumerate", Reflect_enumerate, 1, 0),
JS_FN("get", Reflect_get, 2, 0),
JS_FN("getOwnPropertyDescriptor", Reflect_getOwnPropertyDescriptor, 2, 0),
JS_FN("getPrototypeOf", Reflect_getPrototypeOf, 1, 0),
JS_SELF_HOSTED_FN("has", "Reflect_has", 2, 0),
JS_FN("isExtensible", Reflect_isExtensible, 1, 0),
JS_FN("ownKeys", Reflect_ownKeys, 1, 0),
JS_FN("preventExtensions", Reflect_preventExtensions, 1, 0),
JS_FN("set", Reflect_set, 3, 0),
JS_FN("setPrototypeOf", Reflect_setPrototypeOf, 2, 0),
JS_FS_END
};
/*** Setup **************************************************************************************/
JSObject*
js::InitReflect(JSContext* cx, HandleObject obj)
{
RootedObject proto(cx, obj->as<GlobalObject>().getOrCreateObjectPrototype(cx));
if (!proto)
return nullptr;
RootedObject reflect(cx, NewObjectWithGivenProto<PlainObject>(cx, proto, SingletonObject));
if (!reflect)
return nullptr;
if (!JS_DefineFunctions(cx, reflect, methods))
return nullptr;
RootedValue value(cx, ObjectValue(*reflect));
if (!DefineProperty(cx, obj, cx->names().Reflect, value, nullptr, nullptr, JSPROP_RESOLVING))
return nullptr;
obj->as<GlobalObject>().setConstructor(JSProto_Reflect, value);
return reflect;
}