tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

DisposableStackObjectBase.cpp (11569B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "builtin/DisposableStackObjectBase.h"
      8 
      9 #include "builtin/Array.h"
     10 #include "vm/ArrayObject.h"
     11 #include "vm/Interpreter.h"
     12 
     13 #include "vm/DisposableRecord-inl.h"
     14 #include "vm/JSObject-inl.h"
     15 
     16 using namespace js;
     17 
     18 /**
     19 * Explicit Resource Management Proposal
     20 *
     21 * 27.4.3.1 AsyncDisposableStack.prototype.adopt ( value, onDisposeAsync )
     22 * https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-asyncdisposablestack.prototype.adopt
     23 * Step 5.a
     24 * 27.3.3.1 DisposableStack.prototype.adopt ( value, onDispose )
     25 * https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-disposablestack.prototype.adopt
     26 * Step 5.a
     27 */
     28 bool js::AdoptClosure(JSContext* cx, unsigned argc, JS::Value* vp) {
     29  JS::CallArgs args = CallArgsFromVp(argc, vp);
     30 
     31  JS::Rooted<JSFunction*> callee(cx, &args.callee().as<JSFunction>());
     32  JS::Rooted<JS::Value> value(
     33      cx, callee->getExtendedSlot(AdoptClosureSlot_ValueSlot));
     34  JS::Rooted<JS::Value> onDispose(
     35      cx, callee->getExtendedSlot(AdoptClosureSlot_OnDisposeSlot));
     36 
     37  // Step 5.a. Return ? Call(onDispose, undefined, « value »).
     38  return Call(cx, onDispose, JS::UndefinedHandleValue, value, args.rval());
     39 }
     40 
     41 bool js::ThrowIfOnDisposeNotCallable(JSContext* cx,
     42                                     JS::Handle<JS::Value> onDispose) {
     43  if (IsCallable(onDispose)) {
     44    return true;
     45  }
     46 
     47  JS::UniqueChars bytes =
     48      DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, onDispose, nullptr);
     49  if (!bytes) {
     50    return false;
     51  }
     52 
     53  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION,
     54                           bytes.get());
     55 
     56  return false;
     57 }
     58 
     59 // Explicit Resource Management Proposal
     60 // CreateDisposableResource ( V, hint [ , method ] )
     61 // https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-createdisposableresource
     62 // Steps 1, 3.
     63 bool js::CreateDisposableResource(JSContext* cx, JS::Handle<JS::Value> objVal,
     64                                  UsingHint hint,
     65                                  JS::MutableHandle<JS::Value> result) {
     66  // Step 1. If method is not present, then
     67  // (implicit)
     68  JS::Rooted<JS::Value> method(cx);
     69  JS::Rooted<JS::Value> object(cx);
     70  // Step 1.a. If V is either null or undefined, then
     71  if (objVal.isNullOrUndefined()) {
     72    // Step 1.a.i. Set V to undefined.
     73    // Step 1.a.ii. Set method to undefined.
     74    object.setUndefined();
     75    method.setUndefined();
     76  } else {
     77    // Step 1.b. Else,
     78    // Step 1.b.i. If V is not an Object, throw a TypeError exception.
     79    if (!objVal.isObject()) {
     80      return ThrowCheckIsObject(cx, CheckIsObjectKind::Disposable);
     81    }
     82 
     83    // Step 1.b.ii. Set method to ? GetDisposeMethod(V, hint).
     84    // Step 1.b.iii. If method is undefined, throw a TypeError exception.
     85    object.set(objVal);
     86    if (!GetDisposeMethod(cx, object, hint, &method)) {
     87      return false;
     88    }
     89  }
     90 
     91  // Step 3. Return the
     92  //         DisposableResource Record { [[ResourceValue]]: V, [[Hint]]: hint,
     93  //         [[DisposeMethod]]: method }.
     94  DisposableRecordObject* disposableRecord =
     95      DisposableRecordObject::create(cx, object, method, hint);
     96  if (!disposableRecord) {
     97    return false;
     98  }
     99  result.set(ObjectValue(*disposableRecord));
    100 
    101  return true;
    102 }
    103 
    104 // Explicit Resource Management Proposal
    105 // CreateDisposableResource ( V, hint [ , method ] )
    106 // https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-createdisposableresource
    107 // Steps 2, 3.
    108 bool js::CreateDisposableResource(JSContext* cx, JS::Handle<JS::Value> obj,
    109                                  UsingHint hint,
    110                                  JS::Handle<JS::Value> methodVal,
    111                                  JS::MutableHandle<JS::Value> result) {
    112  JS::Rooted<JS::Value> method(cx);
    113  JS::Rooted<JS::Value> object(cx);
    114 
    115  // Step 2. Else,
    116  // Step 2.a. If IsCallable(method) is false, throw a TypeError exception.
    117  if (!IsCallable(methodVal)) {
    118    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    119                              JSMSG_DISPOSE_NOT_CALLABLE);
    120    return false;
    121  }
    122  object.set(obj);
    123  method.set(methodVal);
    124 
    125  // Step 3. Return the
    126  //         DisposableResource Record { [[ResourceValue]]: V, [[Hint]]: hint,
    127  //         [[DisposeMethod]]: method }.
    128  DisposableRecordObject* disposableRecord =
    129      DisposableRecordObject::create(cx, object, method, hint);
    130  if (!disposableRecord) {
    131    return false;
    132  }
    133  result.set(ObjectValue(*disposableRecord));
    134 
    135  return true;
    136 }
    137 
    138 // Explicit Resource Management Proposal
    139 // 7.5.4 AddDisposableResource ( disposeCapability, V, hint [ , method ] )
    140 // https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-adddisposableresource
    141 // Steps 1, 3.
    142 bool js::AddDisposableResource(JSContext* cx,
    143                               JS::Handle<ArrayObject*> disposeCapability,
    144                               JS::Handle<JS::Value> val, UsingHint hint) {
    145  JS::Rooted<JS::Value> resource(cx);
    146 
    147  // Step 1. If method is not present, then
    148  // (implicit)
    149  // Step 1.a. If V is either null or undefined and hint is sync-dispose,
    150  // return unused.
    151  if (val.isNullOrUndefined() && hint == UsingHint::Sync) {
    152    return true;
    153  }
    154 
    155  // Step 1.c. Let resource be ? CreateDisposableResource(V, hint).
    156  if (!CreateDisposableResource(cx, val, hint, &resource)) {
    157    return false;
    158  }
    159 
    160  // Step 3. Append resource to disposeCapability.[[DisposableResourceStack]].
    161  return NewbornArrayPush(cx, disposeCapability, resource);
    162 }
    163 
    164 // Explicit Resource Management Proposal
    165 // 7.5.4 AddDisposableResource ( disposeCapability, V, hint [ , method ] )
    166 // https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-adddisposableresource
    167 bool js::AddDisposableResource(JSContext* cx,
    168                               JS::Handle<ArrayObject*> disposeCapability,
    169                               JS::Handle<JS::Value> val, UsingHint hint,
    170                               JS::Handle<JS::Value> methodVal) {
    171  JS::Rooted<JS::Value> resource(cx);
    172  // Step 2. Else,
    173  // Step 2.a. Assert: V is undefined.
    174  MOZ_ASSERT(val.isUndefined());
    175 
    176  // Step 2.b. Let resource be ? CreateDisposableResource(undefined, hint,
    177  // method).
    178  if (!CreateDisposableResource(cx, val, hint, methodVal, &resource)) {
    179    return false;
    180  }
    181  // Step 3. Append resource to disposeCapability.[[DisposableResourceStack]].
    182  return NewbornArrayPush(cx, disposeCapability, resource);
    183 }
    184 
    185 // Explicit Resource Management Proposal
    186 // GetDisposeMethod ( V, hint )
    187 // https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-getdisposemethod
    188 bool js::GetDisposeMethod(JSContext* cx, JS::Handle<JS::Value> objVal,
    189                          UsingHint hint,
    190                          JS::MutableHandle<JS::Value> disposeMethod) {
    191  switch (hint) {
    192    case UsingHint::Async: {
    193      // Step 1. If hint is async-dispose, then
    194      // Step 1.a. Let method be ? GetMethod(V, @@asyncDispose).
    195      // GetMethod throws TypeError if method is not callable
    196      // this is handled below at the end of the function.
    197      JS::Rooted<JS::PropertyKey> idAsync(
    198          cx, PropertyKey::Symbol(cx->wellKnownSymbols().asyncDispose));
    199      JS::Rooted<JSObject*> obj(cx, &objVal.toObject());
    200 
    201      if (!GetProperty(cx, obj, obj, idAsync, disposeMethod)) {
    202        return false;
    203      }
    204 
    205      // Step 1.b. If method is undefined, then
    206      // GetMethod returns undefined if the function is null but
    207      // since we do not do the conversion here we check for
    208      // null or undefined here.
    209      if (disposeMethod.isNullOrUndefined()) {
    210        // Step 1.b.i. Set method to ? GetMethod(V, @@dispose).
    211        JS::Rooted<JS::PropertyKey> idSync(
    212            cx, PropertyKey::Symbol(cx->wellKnownSymbols().dispose));
    213        JS::Rooted<JS::Value> syncDisposeMethod(cx);
    214        if (!GetProperty(cx, obj, obj, idSync, &syncDisposeMethod)) {
    215          return false;
    216        }
    217 
    218        if (!syncDisposeMethod.isNullOrUndefined()) {
    219          // Step 1.b.ii. If method is not undefined, then
    220          if (!IsCallable(syncDisposeMethod)) {
    221            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    222                                      JSMSG_DISPOSE_NOT_CALLABLE);
    223            return false;
    224          }
    225 
    226          // Step 1.b.ii.1. Let closure be a new Abstract Closure with no
    227          // parameters that captures method and performs the following steps
    228          // when called:
    229          // Steps 1.b.ii.1.a-f: See SyncDisposalClosure
    230          // Step 1.b.ii.3. Return CreateBuiltinFunction(closure, 0, "", « »).
    231          JS::Handle<PropertyName*> funName = cx->names().empty_;
    232          JSFunction* asyncWrapper = NewNativeFunction(
    233              cx, SyncDisposalClosure, 0, funName,
    234              gc::AllocKind::FUNCTION_EXTENDED, GenericObject);
    235          if (!asyncWrapper) {
    236            return false;
    237          }
    238          asyncWrapper->initExtendedSlot(
    239              uint8_t(SyncDisposalClosureSlots::Method), syncDisposeMethod);
    240          disposeMethod.set(JS::ObjectValue(*asyncWrapper));
    241        }
    242      }
    243 
    244      break;
    245    }
    246 
    247    case UsingHint::Sync: {
    248      // Step 2. Else,
    249      // Step 2.a. Let method be ? GetMethod(V, @@dispose).
    250      JS::Rooted<JS::PropertyKey> id(
    251          cx, PropertyKey::Symbol(cx->wellKnownSymbols().dispose));
    252      JS::Rooted<JSObject*> obj(cx, &objVal.toObject());
    253 
    254      if (!GetProperty(cx, obj, obj, id, disposeMethod)) {
    255        return false;
    256      }
    257 
    258      break;
    259    }
    260    default:
    261      MOZ_CRASH("Invalid UsingHint");
    262  }
    263 
    264  // CreateDisposableResource ( V, hint [ , method ] )
    265  // https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-createdisposableresource
    266  //
    267  // Step 1.b.iii. If method is undefined, throw a TypeError exception.
    268  if (disposeMethod.isNullOrUndefined() || !IsCallable(disposeMethod)) {
    269    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    270                              JSMSG_DISPOSE_NOT_CALLABLE);
    271    return false;
    272  }
    273 
    274  return true;
    275 }
    276 
    277 /* static */ ArrayObject*
    278 DisposableStackObjectBase::GetOrCreateDisposeCapability(
    279    JSContext* cx, JS::Handle<DisposableStackObjectBase*> obj) {
    280  ArrayObject* disposablesList = nullptr;
    281 
    282  if (obj->isDisposableResourceStackEmpty()) {
    283    disposablesList = NewDenseEmptyArray(cx);
    284    if (!disposablesList) {
    285      return nullptr;
    286    }
    287    obj->setReservedSlot(DISPOSABLE_RESOURCE_STACK_SLOT,
    288                         ObjectValue(*disposablesList));
    289  } else {
    290    disposablesList = obj->nonEmptyDisposableResourceStack();
    291  }
    292 
    293  return disposablesList;
    294 }
    295 
    296 bool DisposableStackObjectBase::isDisposableResourceStackEmpty() const {
    297  return getReservedSlot(DISPOSABLE_RESOURCE_STACK_SLOT).isUndefined();
    298 }
    299 
    300 void DisposableStackObjectBase::clearDisposableResourceStack() {
    301  setReservedSlot(DISPOSABLE_RESOURCE_STACK_SLOT, JS::UndefinedValue());
    302 }
    303 
    304 ArrayObject* DisposableStackObjectBase::nonEmptyDisposableResourceStack()
    305    const {
    306  MOZ_ASSERT(!isDisposableResourceStackEmpty());
    307  return &getReservedSlot(DISPOSABLE_RESOURCE_STACK_SLOT)
    308              .toObject()
    309              .as<ArrayObject>();
    310 }
    311 
    312 DisposableStackObjectBase::DisposableState DisposableStackObjectBase::state()
    313    const {
    314  return DisposableState(uint8_t(getReservedSlot(STATE_SLOT).toInt32()));
    315 }
    316 
    317 void DisposableStackObjectBase::setState(DisposableState state) {
    318  setReservedSlot(STATE_SLOT, JS::Int32Value(int32_t(state)));
    319 }