mozjs_sys 0.67.1

System crate for the Mozilla SpiderMonkey JavaScript engine.
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * 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/Symbol.h"

#include "js/Symbol.h"
#include "js/PropertySpec.h"
#include "util/StringBuffer.h"
#include "vm/SymbolType.h"

#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"

using JS::Symbol;
using namespace js;

const Class SymbolObject::class_ = {
    "Symbol", JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
                  JSCLASS_HAS_CACHED_PROTO(JSProto_Symbol)};

SymbolObject* SymbolObject::create(JSContext* cx, JS::HandleSymbol symbol) {
  SymbolObject* obj = NewBuiltinClassInstance<SymbolObject>(cx);
  if (!obj) {
    return nullptr;
  }
  obj->setPrimitiveValue(symbol);
  return obj;
}

const JSPropertySpec SymbolObject::properties[] = {
    JS_PSG("description", descriptionGetter, 0), JS_PS_END};

const JSFunctionSpec SymbolObject::methods[] = {
    JS_FN(js_toString_str, toString, 0, 0),
    JS_FN(js_valueOf_str, valueOf, 0, 0),
    JS_SYM_FN(toPrimitive, toPrimitive, 1, JSPROP_READONLY), JS_FS_END};

const JSFunctionSpec SymbolObject::staticMethods[] = {
    JS_FN("for", for_, 1, 0), JS_FN("keyFor", keyFor, 1, 0), JS_FS_END};

JSObject* SymbolObject::initClass(JSContext* cx, Handle<GlobalObject*> global,
                                  bool defineMembers) {
  // This uses &JSObject::class_ because: "The Symbol prototype object is an
  // ordinary object. It is not a Symbol instance and does not have a
  // [[SymbolData]] internal slot." (ES6 rev 24, 19.4.3)
  RootedObject proto(
      cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
  if (!proto) {
    return nullptr;
  }

  RootedFunction ctor(cx, GlobalObject::createConstructor(
                              cx, construct, ClassName(JSProto_Symbol, cx), 0));
  if (!ctor) {
    return nullptr;
  }

  if (defineMembers) {
    // Define the well-known symbol properties, such as Symbol.iterator.
    ImmutablePropertyNamePtr* names = cx->names().wellKnownSymbolNames();
    RootedValue value(cx);
    unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT;
    WellKnownSymbols* wks = cx->runtime()->wellKnownSymbols;
    for (size_t i = 0; i < JS::WellKnownSymbolLimit; i++) {
      value.setSymbol(wks->get(i));
      if (!NativeDefineDataProperty(cx, ctor, names[i], value, attrs)) {
        return nullptr;
      }
    }
  }

  if (!LinkConstructorAndPrototype(cx, ctor, proto)) {
    return nullptr;
  }

  if (defineMembers) {
    if (!DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
        !DefineToStringTag(cx, proto, cx->names().Symbol) ||
        !DefinePropertiesAndFunctions(cx, ctor, nullptr, staticMethods)) {
      return nullptr;
    }
  }

  if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_Symbol, ctor,
                                            proto)) {
    return nullptr;
  }
  return proto;
}

// ES6 rev 24 (2014 Apr 27) 19.4.1.1 and 19.4.1.2
bool SymbolObject::construct(JSContext* cx, unsigned argc, Value* vp) {
  // According to a note in the draft standard, "Symbol has ordinary
  // [[Construct]] behaviour but the definition of its @@create method causes
  // `new Symbol` to throw a TypeError exception." We do not support @@create
  // yet, so just throw a TypeError.
  CallArgs args = CallArgsFromVp(argc, vp);
  if (args.isConstructing()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_NOT_CONSTRUCTOR, "Symbol");
    return false;
  }

  // steps 1-3
  RootedString desc(cx);
  if (!args.get(0).isUndefined()) {
    desc = ToString(cx, args.get(0));
    if (!desc) {
      return false;
    }
  }

  // step 4
  RootedSymbol symbol(cx,
                      JS::Symbol::new_(cx, JS::SymbolCode::UniqueSymbol, desc));
  if (!symbol) {
    return false;
  }
  args.rval().setSymbol(symbol);
  return true;
}

// ES6 rev 24 (2014 Apr 27) 19.4.2.2
bool SymbolObject::for_(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  // steps 1-2
  RootedString stringKey(cx, ToString(cx, args.get(0)));
  if (!stringKey) {
    return false;
  }

  // steps 3-7
  JS::Symbol* symbol = JS::Symbol::for_(cx, stringKey);
  if (!symbol) {
    return false;
  }
  args.rval().setSymbol(symbol);
  return true;
}

// ES6 rev 25 (2014 May 22) 19.4.2.7
bool SymbolObject::keyFor(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  // step 1
  HandleValue arg = args.get(0);
  if (!arg.isSymbol()) {
    ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, arg,
                     nullptr, "not a symbol");
    return false;
  }

  // step 2
  if (arg.toSymbol()->code() == JS::SymbolCode::InSymbolRegistry) {
#ifdef DEBUG
    RootedString desc(cx, arg.toSymbol()->description());
    MOZ_ASSERT(Symbol::for_(cx, desc) == arg.toSymbol());
#endif
    args.rval().setString(arg.toSymbol()->description());
    return true;
  }

  // step 3: omitted
  // step 4
  args.rval().setUndefined();
  return true;
}

MOZ_ALWAYS_INLINE bool IsSymbol(HandleValue v) {
  return v.isSymbol() || (v.isObject() && v.toObject().is<SymbolObject>());
}

// ES6 rev 27 (2014 Aug 24) 19.4.3.2
bool SymbolObject::toString_impl(JSContext* cx, const CallArgs& args) {
  // steps 1-3
  HandleValue thisv = args.thisv();
  MOZ_ASSERT(IsSymbol(thisv));
  Rooted<Symbol*> sym(cx, thisv.isSymbol()
                              ? thisv.toSymbol()
                              : thisv.toObject().as<SymbolObject>().unbox());

  // step 4
  return SymbolDescriptiveString(cx, sym, args.rval());
}

bool SymbolObject::toString(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsSymbol, toString_impl>(cx, args);
}

// ES6 rev 24 (2014 Apr 27) 19.4.3.3
bool SymbolObject::valueOf_impl(JSContext* cx, const CallArgs& args) {
  // Step 3, the error case, is handled by CallNonGenericMethod.
  HandleValue thisv = args.thisv();
  MOZ_ASSERT(IsSymbol(thisv));
  if (thisv.isSymbol()) {
    args.rval().set(thisv);
  } else {
    args.rval().setSymbol(thisv.toObject().as<SymbolObject>().unbox());
  }
  return true;
}

bool SymbolObject::valueOf(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsSymbol, valueOf_impl>(cx, args);
}

// ES6 19.4.3.4
bool SymbolObject::toPrimitive(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  // The specification gives exactly the same algorithm for @@toPrimitive as
  // for valueOf, so reuse the valueOf implementation.
  return CallNonGenericMethod<IsSymbol, valueOf_impl>(cx, args);
}

bool SymbolObject::descriptionGetter_impl(JSContext* cx, const CallArgs& args) {
  // Get symbol object pointer.
  HandleValue thisv = args.thisv();
  MOZ_ASSERT(IsSymbol(thisv));
  Rooted<Symbol*> sym(cx, thisv.isSymbol()
                              ? thisv.toSymbol()
                              : thisv.toObject().as<SymbolObject>().unbox());

  // Return the symbol's description if present, otherwise return undefined.
  if (JSString* str = sym->description()) {
    args.rval().setString(str);
  } else {
    args.rval().setUndefined();
  }
  return true;
}

bool SymbolObject::descriptionGetter(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  return CallNonGenericMethod<IsSymbol, descriptionGetter_impl>(cx, args);
}

JSObject* js::InitSymbolClass(JSContext* cx, Handle<GlobalObject*> global) {
  return SymbolObject::initClass(cx, global, true);
}

JSObject* js::InitBareSymbolCtor(JSContext* cx, Handle<GlobalObject*> global) {
  return SymbolObject::initClass(cx, global, false);
}