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 }