/* 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 "NamespaceImports.h"

#include "gc/GC.h"
#include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
#include "js/Proxy.h"
#include "proxy/DeadObjectProxy.h"
#include "vm/Compartment.h"
#include "vm/Interpreter.h"
#include "vm/ProxyObject.h"
#include "vm/WrapperObject.h"

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

using namespace js;

using JS::IsArrayAnswer;

bool BaseProxyHandler::enter(JSContext* cx, HandleObject wrapper, HandleId id,
                             Action act, bool mayThrow, bool* bp) const {
  *bp = true;
  return true;
}

bool BaseProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id,
                           bool* bp) const {
  assertEnteredPolicy(cx, proxy, id, GET);

  // This method is not covered by any spec, but we follow ES 2016
  // (February 11, 2016) 9.1.7.1 fairly closely.

  // Step 2. (Step 1 is a superfluous assertion.)
  // Non-standard: Use our faster hasOwn trap.
  if (!hasOwn(cx, proxy, id, bp)) {
    return false;
  }

  // Step 3.
  if (*bp) {
    return true;
  }

  // The spec calls this variable "parent", but that word has weird
  // connotations in SpiderMonkey, so let's go with "proto".
  // Step 4.
  RootedObject proto(cx);
  if (!GetPrototype(cx, proxy, &proto)) {
    return false;
  }

  // Step 5.,5.a.
  if (proto) {
    return HasProperty(cx, proto, id, bp);
  }

  // Step 6.
  *bp = false;
  return true;
}

bool BaseProxyHandler::hasOwn(JSContext* cx, HandleObject proxy, HandleId id,
                              bool* bp) const {
  assertEnteredPolicy(cx, proxy, id, GET);
  Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
  if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) {
    return false;
  }
  *bp = desc.isSome();
  return true;
}

bool BaseProxyHandler::get(JSContext* cx, HandleObject proxy,
                           HandleValue receiver, HandleId id,
                           MutableHandleValue vp) const {
  assertEnteredPolicy(cx, proxy, id, GET);

  // This method is not covered by any spec, but we follow ES 2016
  // (January 21, 2016) 9.1.8 fairly closely.

  // Step 2. (Step 1 is a superfluous assertion.)
  Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
  if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) {
    return false;
  }
  if (desc.isSome()) {
    desc->assertComplete();
  }

  // Step 3.
  if (desc.isNothing()) {
    // The spec calls this variable "parent", but that word has weird
    // connotations in SpiderMonkey, so let's go with "proto".
    // Step 3.a.
    RootedObject proto(cx);
    if (!GetPrototype(cx, proxy, &proto)) {
      return false;
    }

    // Step 3.b.
    if (!proto) {
      vp.setUndefined();
      return true;
    }

    // Step 3.c.
    return GetProperty(cx, proto, receiver, id, vp);
  }

  // Step 4.
  if (desc->isDataDescriptor()) {
    vp.set(desc->value());
    return true;
  }

  // Step 5.
  MOZ_ASSERT(desc->isAccessorDescriptor());
  RootedObject getter(cx, desc->getter());

  // Step 6.
  if (!getter) {
    vp.setUndefined();
    return true;
  }

  // Step 7.
  RootedValue getterFunc(cx, ObjectValue(*getter));
  return CallGetter(cx, receiver, getterFunc, vp);
}

bool BaseProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id,
                           HandleValue v, HandleValue receiver,
                           ObjectOpResult& result) const {
  assertEnteredPolicy(cx, proxy, id, SET);

  // This method is not covered by any spec, but we follow ES6 draft rev 28
  // (2014 Oct 14) 9.1.9 fairly closely, adapting it slightly for
  // SpiderMonkey's particular foibles.

  // Steps 2-3.  (Step 1 is a superfluous assertion.)
  Rooted<mozilla::Maybe<PropertyDescriptor>> ownDesc(cx);
  if (!getOwnPropertyDescriptor(cx, proxy, id, &ownDesc)) {
    return false;
  }
  if (ownDesc.isSome()) {
    ownDesc->assertComplete();
  }

  // The rest is factored out into a separate function with a weird name.
  // This algorithm continues just below.
  return SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, ownDesc,
                                        result);
}

bool js::SetPropertyIgnoringNamedGetter(
    JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
    HandleValue receiver, Handle<mozilla::Maybe<PropertyDescriptor>> ownDesc_,
    ObjectOpResult& result) {
  Rooted<PropertyDescriptor> ownDesc(cx);

  // Step 4.
  if (ownDesc_.isNothing()) {
    // The spec calls this variable "parent", but that word has weird
    // connotations in SpiderMonkey, so let's go with "proto".
    RootedObject proto(cx);
    if (!GetPrototype(cx, obj, &proto)) {
      return false;
    }
    if (proto) {
      return SetProperty(cx, proto, id, v, receiver, result);
    }

    // Step 4.d.
    ownDesc.set(PropertyDescriptor::Data(
        UndefinedValue(),
        {JS::PropertyAttribute::Configurable, JS::PropertyAttribute::Enumerable,
         JS::PropertyAttribute::Writable}));
  } else {
    ownDesc.set(*ownDesc_);
  }

  // Step 5.
  if (ownDesc.isDataDescriptor()) {
    // Steps 5.a-b.
    if (!ownDesc.writable()) {
      return result.fail(JSMSG_READ_ONLY);
    }
    if (!receiver.isObject()) {
      return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER);
    }
    RootedObject receiverObj(cx, &receiver.toObject());

    // Steps 5.c-d.
    Rooted<mozilla::Maybe<PropertyDescriptor>> existingDescriptor(cx);
    if (!GetOwnPropertyDescriptor(cx, receiverObj, id, &existingDescriptor)) {
      return false;
    }

    // Step 5.e.
    if (existingDescriptor.isSome()) {
      // Step 5.e.i.
      if (existingDescriptor->isAccessorDescriptor()) {
        return result.fail(JSMSG_OVERWRITING_ACCESSOR);
      }

      // Step 5.e.ii.
      if (!existingDescriptor->writable()) {
        return result.fail(JSMSG_READ_ONLY);
      }
    }

    // Steps 5.e.iii-iv. and 5.f.i.
    Rooted<PropertyDescriptor> desc(cx);
    if (existingDescriptor.isSome()) {
      desc = PropertyDescriptor::Empty();
      desc.setValue(v);
    } else {
      desc = PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
                                          JS::PropertyAttribute::Enumerable,
                                          JS::PropertyAttribute::Writable});
    }
    return DefineProperty(cx, receiverObj, id, desc, result);
  }

  // Step 6.
  MOZ_ASSERT(ownDesc.isAccessorDescriptor());
  RootedObject setter(cx);
  if (ownDesc.hasSetter()) {
    setter = ownDesc.setter();
  }
  if (!setter) {
    return result.fail(JSMSG_GETTER_ONLY);
  }
  RootedValue setterValue(cx, ObjectValue(*setter));
  if (!CallSetter(cx, receiver, setterValue, v)) {
    return false;
  }
  return result.succeed();
}

bool BaseProxyHandler::getOwnEnumerablePropertyKeys(
    JSContext* cx, HandleObject proxy, MutableHandleIdVector props) const {
  assertEnteredPolicy(cx, proxy, JS::PropertyKey::Void(), ENUMERATE);
  MOZ_ASSERT(props.length() == 0);

  if (!ownPropertyKeys(cx, proxy, props)) {
    return false;
  }

  /* Select only the enumerable properties through in-place iteration. */
  RootedId id(cx);
  Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
  size_t i = 0;
  for (size_t j = 0, len = props.length(); j < len; j++) {
    MOZ_ASSERT(i <= j);
    id = props[j];
    if (id.isSymbol()) {
      continue;
    }

    AutoWaivePolicy policy(cx, proxy, id, BaseProxyHandler::GET);
    if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) {
      desc.reset();
      return false;
    }
    if (desc.isSome()) {
      desc->assertComplete();
    }

    if (desc.isSome() && desc->enumerable()) {
      props[i++].set(id);
    }
  }

  MOZ_ASSERT(i <= props.length());
  if (!props.resize(i)) {
    return false;
  }

  return true;
}

bool BaseProxyHandler::enumerate(JSContext* cx, HandleObject proxy,
                                 MutableHandleIdVector props) const {
  assertEnteredPolicy(cx, proxy, JS::PropertyKey::Void(), ENUMERATE);

  // GetPropertyKeys will invoke getOwnEnumerablePropertyKeys along the proto
  // chain for us.
  MOZ_ASSERT(props.empty());
  return GetPropertyKeys(cx, proxy, 0, props);
}

bool BaseProxyHandler::call(JSContext* cx, HandleObject proxy,
                            const CallArgs& args) const {
  MOZ_CRASH("callable proxies should implement call trap");
}

bool BaseProxyHandler::construct(JSContext* cx, HandleObject proxy,
                                 const CallArgs& args) const {
  MOZ_CRASH("callable proxies should implement construct trap");
}

const char* BaseProxyHandler::className(JSContext* cx,
                                        HandleObject proxy) const {
  return proxy->isCallable() ? "Function" : "Object";
}

JSString* BaseProxyHandler::fun_toString(JSContext* cx, HandleObject proxy,
                                         bool isToSource) const {
  if (proxy->isCallable()) {
    return JS_NewStringCopyZ(cx, "function () {\n    [native code]\n}");
  }

  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            JSMSG_INCOMPATIBLE_PROTO, "Function", "toString",
                            "object");
  return nullptr;
}

RegExpShared* BaseProxyHandler::regexp_toShared(JSContext* cx,
                                                HandleObject proxy) const {
  MOZ_CRASH("This should have been a wrapped regexp");
}

bool BaseProxyHandler::boxedValue_unbox(JSContext* cx, HandleObject proxy,
                                        MutableHandleValue vp) const {
  vp.setUndefined();
  return true;
}

bool BaseProxyHandler::nativeCall(JSContext* cx, IsAcceptableThis test,
                                  NativeImpl impl, const CallArgs& args) const {
  ReportIncompatible(cx, args);
  return false;
}

bool BaseProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy,
                                       ESClass* cls) const {
  *cls = ESClass::Other;
  return true;
}

bool BaseProxyHandler::isArray(JSContext* cx, HandleObject proxy,
                               IsArrayAnswer* answer) const {
  *answer = IsArrayAnswer::NotArray;
  return true;
}

void BaseProxyHandler::trace(JSTracer* trc, JSObject* proxy) const {}

void BaseProxyHandler::finalize(JS::GCContext* gcx, JSObject* proxy) const {}

size_t BaseProxyHandler::objectMoved(JSObject* proxy, JSObject* old) const {
  return 0;
}

bool BaseProxyHandler::getPrototype(JSContext* cx, HandleObject proxy,
                                    MutableHandleObject protop) const {
  MOZ_CRASH("must override getPrototype with dynamic prototype");
}

bool BaseProxyHandler::setPrototype(JSContext* cx, HandleObject proxy,
                                    HandleObject proto,
                                    ObjectOpResult& result) const {
  // Disallow sets of protos on proxies with dynamic prototypes but no hook.
  // This keeps us away from the footgun of having the first proto set opt
  // you out of having dynamic protos altogether.
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            JSMSG_CANT_SET_PROTO_OF, "incompatible Proxy");
  return false;
}

bool BaseProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy,
                                             bool* succeeded) const {
  *succeeded = false;
  return true;
}

bool BaseProxyHandler::getElements(JSContext* cx, HandleObject proxy,
                                   uint32_t begin, uint32_t end,
                                   ElementAdder* adder) const {
  assertEnteredPolicy(cx, proxy, JS::PropertyKey::Void(), GET);

  return js::GetElementsWithAdder(cx, proxy, proxy, begin, end, adder);
}

bool BaseProxyHandler::isCallable(JSObject* obj) const { return false; }

bool BaseProxyHandler::isConstructor(JSObject* obj) const { return false; }

JS_PUBLIC_API void js::NukeNonCCWProxy(JSContext* cx, HandleObject proxy) {
  MOZ_ASSERT(proxy->is<ProxyObject>());
  MOZ_ASSERT(!proxy->is<CrossCompartmentWrapperObject>());

  // (NotifyGCNukeWrapper() only needs to be called on CCWs.)

  // The proxy is about to be replaced, so we need to do any necessary
  // cleanup first.
  proxy->as<ProxyObject>().handler()->finalize(cx->gcContext(), proxy);

  proxy->as<ProxyObject>().nuke();

  MOZ_ASSERT(IsDeadProxyObject(proxy));
}

void js::NukeRemovedCrossCompartmentWrapper(JSContext* cx, JSObject* wrapper) {
#ifdef DEBUG
  MOZ_ASSERT(wrapper->is<CrossCompartmentWrapperObject>());
  JSObject* target = UncheckedUnwrapWithoutExpose(wrapper);
  auto ptr = wrapper->compartment()->lookupWrapper(target);
  MOZ_ASSERT_IF(ptr, ptr->value() != wrapper);
#endif

  NotifyGCNukeWrapper(cx, wrapper);

  // We don't need to call finalize here because the CCW finalizer doesn't do
  // anything. Skipping finalize means that |wrapper| doesn't need to be rooted
  // to pass the hazard analysis, which is needed because this method is called
  // from some tricky places inside transplanting where rooting can be
  // difficult.

  wrapper->as<ProxyObject>().nuke();

  MOZ_ASSERT(IsDeadProxyObject(wrapper));
}
