1// Copyright 2019 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include 'src/builtins/builtins-promise.h' 6#include 'src/builtins/builtins-promise-gen.h' 7 8namespace runtime { 9extern transitioning runtime 10AllowDynamicFunction(implicit context: Context)(JSAny): JSAny; 11 12extern transitioning runtime ThrowNoAccess(implicit context: Context)(): never; 13 14extern transitioning runtime 15ReportMessageFromMicrotask(implicit context: Context)(JSAny): JSAny; 16} 17 18// Unsafe functions that should be used very carefully. 19namespace promise_internal { 20extern macro PromiseBuiltinsAssembler::ZeroOutEmbedderOffsets(JSPromise): void; 21 22extern macro PromiseBuiltinsAssembler::AllocateJSPromise(Context): HeapObject; 23} 24 25extern macro 26PromiseBuiltinsAssembler::IsContextPromiseHookEnabled(uint32): bool; 27 28extern macro 29PromiseBuiltinsAssembler::IsIsolatePromiseHookEnabled(uint32): bool; 30 31extern macro 32PromiseBuiltinsAssembler::PromiseHookFlags(): uint32; 33 34namespace promise { 35extern macro IsFunctionWithPrototypeSlotMap(Map): bool; 36 37@export 38macro PromiseHasHandler(promise: JSPromise): bool { 39 return promise.HasHandler(); 40} 41 42@export 43macro PromiseInit(promise: JSPromise): void { 44 promise.reactions_or_result = kZero; 45 promise.flags = SmiTag(JSPromiseFlags{ 46 status: PromiseState::kPending, 47 has_handler: false, 48 handled_hint: false, 49 is_silent: false, 50 async_task_id: 0 51 }); 52 promise_internal::ZeroOutEmbedderOffsets(promise); 53} 54 55macro InnerNewJSPromise(implicit context: Context)(): JSPromise { 56 const promiseFun = *NativeContextSlot(ContextSlot::PROMISE_FUNCTION_INDEX); 57 dcheck(IsFunctionWithPrototypeSlotMap(promiseFun.map)); 58 const promiseMap = UnsafeCast<Map>(promiseFun.prototype_or_initial_map); 59 const promiseHeapObject = promise_internal::AllocateJSPromise(context); 60 *UnsafeConstCast(&promiseHeapObject.map) = promiseMap; 61 const promise = UnsafeCast<JSPromise>(promiseHeapObject); 62 promise.properties_or_hash = kEmptyFixedArray; 63 promise.elements = kEmptyFixedArray; 64 promise.reactions_or_result = kZero; 65 promise.flags = SmiTag(JSPromiseFlags{ 66 status: PromiseState::kPending, 67 has_handler: false, 68 handled_hint: false, 69 is_silent: false, 70 async_task_id: 0 71 }); 72 return promise; 73} 74 75macro NewPromiseFulfillReactionJobTask(implicit context: Context)( 76 handlerContext: Context, argument: Object, handler: Callable|Undefined, 77 promiseOrCapability: JSPromise|PromiseCapability| 78 Undefined): PromiseFulfillReactionJobTask { 79 const nativeContext = LoadNativeContext(handlerContext); 80 return new PromiseFulfillReactionJobTask{ 81 map: PromiseFulfillReactionJobTaskMapConstant(), 82 argument, 83 context: handlerContext, 84 handler, 85 promise_or_capability: promiseOrCapability, 86 continuation_preserved_embedder_data: 87 *ContextSlot( 88 nativeContext, ContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX) 89 }; 90} 91 92macro NewPromiseRejectReactionJobTask(implicit context: Context)( 93 handlerContext: Context, argument: Object, handler: Callable|Undefined, 94 promiseOrCapability: JSPromise|PromiseCapability| 95 Undefined): PromiseRejectReactionJobTask { 96 const nativeContext = LoadNativeContext(handlerContext); 97 return new PromiseRejectReactionJobTask{ 98 map: PromiseRejectReactionJobTaskMapConstant(), 99 argument, 100 context: handlerContext, 101 handler, 102 promise_or_capability: promiseOrCapability, 103 continuation_preserved_embedder_data: 104 *ContextSlot( 105 nativeContext, ContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX) 106 }; 107} 108 109@export 110transitioning macro RunContextPromiseHookInit(implicit context: Context)( 111 promise: JSPromise, parent: Object): void { 112 const maybeHook = *NativeContextSlot( 113 ContextSlot::PROMISE_HOOK_INIT_FUNCTION_INDEX); 114 const hook = Cast<Callable>(maybeHook) otherwise return; 115 const parentObject = Is<JSPromise>(parent) ? Cast<JSPromise>(parent) 116 otherwise unreachable: Undefined; 117 118 try { 119 Call(context, hook, Undefined, promise, parentObject); 120 } catch (e, _message) { 121 runtime::ReportMessageFromMicrotask(e); 122 } 123} 124 125@export 126transitioning macro RunContextPromiseHookResolve(implicit context: Context)( 127 promise: JSPromise): void { 128 // Use potentially unused variables. 129 const _unusedPromise = promise; 130 @if(V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS) { 131 RunContextPromiseHook( 132 ContextSlot::PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, promise, 133 PromiseHookFlags()); 134 } 135} 136 137@export 138transitioning macro RunContextPromiseHookResolve(implicit context: Context)( 139 promise: JSPromise, flags: uint32): void { 140 RunContextPromiseHook( 141 ContextSlot::PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, promise, flags); 142} 143 144@export 145transitioning macro RunContextPromiseHookBefore(implicit context: Context)( 146 promiseOrCapability: JSPromise|PromiseCapability|Undefined): void { 147 // Use potentially unused variables. 148 const _unusedPromiseOrCapability = promiseOrCapability; 149 @if(V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS) { 150 RunContextPromiseHook( 151 ContextSlot::PROMISE_HOOK_BEFORE_FUNCTION_INDEX, promiseOrCapability, 152 PromiseHookFlags()); 153 } 154} 155 156@export 157transitioning macro RunContextPromiseHookBefore(implicit context: Context)( 158 promiseOrCapability: JSPromise|PromiseCapability|Undefined, flags: uint32): 159 void { 160 RunContextPromiseHook( 161 ContextSlot::PROMISE_HOOK_BEFORE_FUNCTION_INDEX, promiseOrCapability, 162 flags); 163} 164 165@export 166transitioning macro RunContextPromiseHookAfter(implicit context: Context)( 167 promiseOrCapability: JSPromise|PromiseCapability|Undefined): void { 168 // Use potentially unused variables. 169 const _unusedPromiseOrCapability = promiseOrCapability; 170 @if(V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS) { 171 RunContextPromiseHook( 172 ContextSlot::PROMISE_HOOK_AFTER_FUNCTION_INDEX, promiseOrCapability, 173 PromiseHookFlags()); 174 } 175} 176 177@export 178transitioning macro RunContextPromiseHookAfter(implicit context: Context)( 179 promiseOrCapability: JSPromise|PromiseCapability|Undefined, flags: uint32): 180 void { 181 RunContextPromiseHook( 182 ContextSlot::PROMISE_HOOK_AFTER_FUNCTION_INDEX, promiseOrCapability, 183 flags); 184} 185 186transitioning macro RunContextPromiseHook(implicit context: Context)( 187 slot: Slot<NativeContext, Undefined|Callable>, 188 promiseOrCapability: JSPromise|PromiseCapability|Undefined, 189 flags: uint32): void { 190 // Use potentially unused variables. 191 const _unusedSlot = slot; 192 const _unusedPromiseOrCapability = promiseOrCapability; 193 const _unusedFlags = flags; 194 @if(V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS) { 195 if (!IsContextPromiseHookEnabled(flags)) return; 196 const maybeHook = *NativeContextSlot(slot); 197 const hook = Cast<Callable>(maybeHook) otherwise return; 198 199 let promise: JSPromise; 200 typeswitch (promiseOrCapability) { 201 case (jspromise: JSPromise): { 202 promise = jspromise; 203 } 204 case (capability: PromiseCapability): { 205 promise = Cast<JSPromise>(capability.promise) otherwise return; 206 } 207 case (Undefined): { 208 return; 209 } 210 } 211 212 try { 213 Call(context, hook, Undefined, promise); 214 } catch (e, _message) { 215 runtime::ReportMessageFromMicrotask(e); 216 } 217 } 218} 219 220transitioning macro RunAnyPromiseHookInit(implicit context: Context)( 221 promise: JSPromise, parent: Object): void { 222 const promiseHookFlags = PromiseHookFlags(); 223 // Fast return if no hooks are set. 224 if (promiseHookFlags == 0) return; 225 @if(V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS) { 226 if (IsContextPromiseHookEnabled(promiseHookFlags)) { 227 RunContextPromiseHookInit(promise, parent); 228 } 229 } 230 if (IsIsolatePromiseHookEnabled(promiseHookFlags)) { 231 runtime::PromiseHookInit(promise, parent); 232 } 233} 234 235// These allocate and initialize a promise with pending state and 236// undefined fields. 237// 238// This uses the given parent as the parent promise for the promise 239// init hook. 240@export 241transitioning macro NewJSPromise(implicit context: Context)(parent: Object): 242 JSPromise { 243 const instance = InnerNewJSPromise(); 244 PromiseInit(instance); 245 RunAnyPromiseHookInit(instance, parent); 246 return instance; 247} 248 249// This uses undefined as the parent promise for the promise init 250// hook. 251@export 252transitioning macro NewJSPromise(implicit context: Context)(): JSPromise { 253 return NewJSPromise(Undefined); 254} 255 256// This allocates and initializes a promise with the given state and 257// fields. 258@export 259transitioning macro NewJSPromise(implicit context: Context)( 260 status: constexpr PromiseState, result: JSAny): JSPromise { 261 dcheck(status != PromiseState::kPending); 262 263 const instance = InnerNewJSPromise(); 264 instance.reactions_or_result = result; 265 instance.SetStatus(status); 266 promise_internal::ZeroOutEmbedderOffsets(instance); 267 RunAnyPromiseHookInit(instance, Undefined); 268 return instance; 269} 270 271macro NewPromiseReaction(implicit context: Context)( 272 handlerContext: Context, next: Zero|PromiseReaction, 273 promiseOrCapability: JSPromise|PromiseCapability|Undefined, 274 fulfillHandler: Callable|Undefined, 275 rejectHandler: Callable|Undefined): PromiseReaction { 276 const nativeContext = LoadNativeContext(handlerContext); 277 return new PromiseReaction{ 278 map: PromiseReactionMapConstant(), 279 next: next, 280 reject_handler: rejectHandler, 281 fulfill_handler: fulfillHandler, 282 promise_or_capability: promiseOrCapability, 283 continuation_preserved_embedder_data: 284 *ContextSlot( 285 nativeContext, ContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX) 286 }; 287} 288 289extern macro PromiseResolveThenableJobTaskMapConstant(): Map; 290 291// https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob 292macro NewPromiseResolveThenableJobTask(implicit context: Context)( 293 promiseToResolve: JSPromise, thenable: JSReceiver, 294 then: Callable): PromiseResolveThenableJobTask { 295 // 2. Let getThenRealmResult be GetFunctionRealm(then). 296 // 3. If getThenRealmResult is a normal completion, then let thenRealm be 297 // getThenRealmResult.[[Value]]. 298 // 4. Otherwise, let thenRealm be null. 299 // 300 // The only cases where |thenRealm| can be null is when |then| is a revoked 301 // Proxy object, which would throw when it is called anyway. So instead of 302 // setting the context to null as the spec does, we just use the current 303 // realm. 304 const thenContext: Context = ExtractHandlerContext(then); 305 const nativeContext = LoadNativeContext(thenContext); 306 307 // 1. Let job be a new Job abstract closure with no parameters that 308 // captures promiseToResolve, thenable, and then... 309 // 5. Return { [[Job]]: job, [[Realm]]: thenRealm }. 310 return new PromiseResolveThenableJobTask{ 311 map: PromiseResolveThenableJobTaskMapConstant(), 312 context: nativeContext, 313 promise_to_resolve: promiseToResolve, 314 thenable, 315 then 316 }; 317} 318 319struct InvokeThenOneArgFunctor { 320 transitioning 321 macro Call( 322 nativeContext: NativeContext, then: JSAny, receiver: JSAny, arg1: JSAny, 323 _arg2: JSAny): JSAny { 324 return Call(nativeContext, then, receiver, arg1); 325 } 326} 327 328struct InvokeThenTwoArgFunctor { 329 transitioning 330 macro Call( 331 nativeContext: NativeContext, then: JSAny, receiver: JSAny, arg1: JSAny, 332 arg2: JSAny): JSAny { 333 return Call(nativeContext, then, receiver, arg1, arg2); 334 } 335} 336 337transitioning 338macro InvokeThen<F: type>(implicit context: Context)( 339 nativeContext: NativeContext, receiver: JSAny, arg1: JSAny, arg2: JSAny, 340 callFunctor: F): JSAny { 341 // We can skip the "then" lookup on {receiver} if it's [[Prototype]] 342 // is the (initial) Promise.prototype and the Promise#then protector 343 // is intact, as that guards the lookup path for the "then" property 344 // on JSPromise instances which have the (initial) %PromisePrototype%. 345 if (!Is<Smi>(receiver) && 346 IsPromiseThenLookupChainIntact( 347 nativeContext, UnsafeCast<HeapObject>(receiver).map)) { 348 const then = 349 *NativeContextSlot(nativeContext, ContextSlot::PROMISE_THEN_INDEX); 350 return callFunctor.Call(nativeContext, then, receiver, arg1, arg2); 351 } else 352 deferred { 353 const then = UnsafeCast<JSAny>(GetProperty(receiver, kThenString)); 354 return callFunctor.Call(nativeContext, then, receiver, arg1, arg2); 355 } 356} 357 358transitioning 359macro InvokeThen(implicit context: Context)( 360 nativeContext: NativeContext, receiver: JSAny, arg: JSAny): JSAny { 361 return InvokeThen( 362 nativeContext, receiver, arg, Undefined, InvokeThenOneArgFunctor{}); 363} 364 365transitioning 366macro InvokeThen(implicit context: Context)( 367 nativeContext: NativeContext, receiver: JSAny, arg1: JSAny, 368 arg2: JSAny): JSAny { 369 return InvokeThen( 370 nativeContext, receiver, arg1, arg2, InvokeThenTwoArgFunctor{}); 371} 372 373transitioning 374macro BranchIfAccessCheckFailed(implicit context: Context)( 375 nativeContext: NativeContext, promiseConstructor: JSAny, 376 executor: JSAny): void labels IfNoAccess { 377 try { 378 // If executor is a bound function, load the bound function until we've 379 // reached an actual function. 380 let foundExecutor = executor; 381 while (true) { 382 typeswitch (foundExecutor) { 383 case (f: JSFunction): { 384 // Load the context from the function and compare it to the Promise 385 // constructor's context. If they match, everything is fine, 386 // otherwise, bail out to the runtime. 387 const functionContext = f.context; 388 const nativeFunctionContext = LoadNativeContext(functionContext); 389 if (TaggedEqual(nativeContext, nativeFunctionContext)) { 390 goto HasAccess; 391 } else { 392 goto CallRuntime; 393 } 394 } 395 case (b: JSBoundFunction): { 396 foundExecutor = b.bound_target_function; 397 } 398 case (Object): { 399 goto CallRuntime; 400 } 401 } 402 } 403 } label CallRuntime deferred { 404 const result = runtime::AllowDynamicFunction(promiseConstructor); 405 if (result != True) { 406 goto IfNoAccess; 407 } 408 } label HasAccess {} 409} 410} 411