diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index 0abf5ce93b..0cfdbd5764 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -872,7 +872,11 @@ namespace internal { CPP(ShadowRealmConstructor) \ TFS(ShadowRealmGetWrappedValue, kCreationContext, kTargetContext, kValue) \ CPP(ShadowRealmPrototypeEvaluate) \ - CPP(ShadowRealmPrototypeImportValue) \ + TFJ(ShadowRealmPrototypeImportValue, kJSArgcReceiverSlots + 2, kReceiver, \ + kSpecifier, kExportName) \ + TFJ(ShadowRealmImportValueFulfilled, kJSArgcReceiverSlots + 1, kReceiver, \ + kExports) \ + TFJ(ShadowRealmImportValueRejected, kDontAdaptArgumentsSentinel) \ \ /* SharedArrayBuffer */ \ CPP(SharedArrayBufferPrototypeGetByteLength) \ diff --git a/src/builtins/builtins-shadow-realms.cc b/src/builtins/builtins-shadow-realms.cc index 08b3f3ec31..f071bec895 100644 --- a/src/builtins/builtins-shadow-realms.cc +++ b/src/builtins/builtins-shadow-realms.cc @@ -216,11 +216,5 @@ BUILTIN(ShadowRealmPrototypeEvaluate) { return *wrapped_result; } -// https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.importvalue -BUILTIN(ShadowRealmPrototypeImportValue) { - HandleScope scope(isolate); - return ReadOnlyRoots(isolate).undefined_value(); -} - } // namespace internal } // namespace v8 diff --git a/src/builtins/builtins-shadowrealm-gen.cc b/src/builtins/builtins-shadowrealm-gen.cc index f65f611683..ca981dec59 100644 --- a/src/builtins/builtins-shadowrealm-gen.cc +++ b/src/builtins/builtins-shadowrealm-gen.cc @@ -6,6 +6,8 @@ #include "src/builtins/builtins.h" #include "src/codegen/code-stub-assembler.h" #include "src/objects/descriptor-array.h" +#include "src/objects/js-shadow-realms.h" +#include "src/objects/module.h" namespace v8 { namespace internal { @@ -15,11 +17,27 @@ class ShadowRealmBuiltinsAssembler : public CodeStubAssembler { explicit ShadowRealmBuiltinsAssembler(compiler::CodeAssemblerState* state) : CodeStubAssembler(state) {} + enum ImportValueFulfilledFunctionContextSlot { + kEvalContextSlot = Context::MIN_CONTEXT_SLOTS, + kSpecifierSlot, + kExportNameSlot, + kContextLength, + }; + protected: TNode AllocateJSWrappedFunction(TNode context, TNode target); void CheckAccessor(TNode array, TNode index, TNode name, Label* bailout); + TNode ImportValue(TNode caller_context, + TNode eval_context, + TNode specifier, TNode export_name); + TNode CreateImportValueFulfilledFunctionContext( + TNode caller_context, TNode eval_context, + TNode specifier, TNode export_name); + TNode AllocateImportValueFulfilledFunction( + TNode caller_context, TNode eval_context, + TNode specifier, TNode export_name); }; TNode ShadowRealmBuiltinsAssembler::AllocateJSWrappedFunction( @@ -35,6 +53,40 @@ TNode ShadowRealmBuiltinsAssembler::AllocateJSWrappedFunction( return wrapped; } +TNode +ShadowRealmBuiltinsAssembler::CreateImportValueFulfilledFunctionContext( + TNode caller_context, TNode eval_context, + TNode specifier, TNode export_name) { + const TNode context = AllocateSyntheticFunctionContext( + caller_context, ImportValueFulfilledFunctionContextSlot::kContextLength); + StoreContextElementNoWriteBarrier( + context, ImportValueFulfilledFunctionContextSlot::kEvalContextSlot, + eval_context); + StoreContextElementNoWriteBarrier( + context, ImportValueFulfilledFunctionContextSlot::kSpecifierSlot, + specifier); + StoreContextElementNoWriteBarrier( + context, ImportValueFulfilledFunctionContextSlot::kExportNameSlot, + export_name); + return context; +} + +TNode +ShadowRealmBuiltinsAssembler::AllocateImportValueFulfilledFunction( + TNode caller_context, TNode eval_context, + TNode specifier, TNode export_name) { + const TNode function_context = + CreateImportValueFulfilledFunctionContext(caller_context, eval_context, + specifier, export_name); + const TNode function_map = CAST(LoadContextElement( + caller_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX)); + const TNode info = + ShadowRealmImportValueFulfilledSFIConstant(); + + return AllocateFunctionWithMapAndContext(function_map, info, + function_context); +} + void ShadowRealmBuiltinsAssembler::CheckAccessor(TNode array, TNode index, TNode name, @@ -244,5 +296,131 @@ TF_BUILTIN(CallWrappedFunction, ShadowRealmBuiltinsAssembler) { Unreachable(); } +// https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.importvalue +TF_BUILTIN(ShadowRealmPrototypeImportValue, ShadowRealmBuiltinsAssembler) { + const char* const kMethodName = "ShadowRealm.prototype.importValue"; + TNode context = Parameter(Descriptor::kContext); + // 1. Let O be this value. + TNode O = Parameter(Descriptor::kReceiver); + // 2. Perform ? ValidateShadowRealmObject(O). + ThrowIfNotInstanceType(context, O, JS_SHADOW_REALM_TYPE, kMethodName); + + // 3. Let specifierString be ? ToString(specifier). + TNode specifier = Parameter(Descriptor::kSpecifier); + TNode specifier_string = ToString_Inline(context, specifier); + // 4. Let exportNameString be ? ToString(exportName). + TNode export_name = Parameter(Descriptor::kExportName); + TNode export_name_string = ToString_Inline(context, export_name); + // 5. Let callerRealm be the current Realm Record. + TNode caller_context = LoadNativeContext(context); + // 6. Let evalRealm be O.[[ShadowRealm]]. + // 7. Let evalContext be O.[[ExecutionContext]]. + TNode eval_context = + CAST(LoadObjectField(CAST(O), JSShadowRealm::kNativeContextOffset)); + // 8. Return ? ShadowRealmImportValue(specifierString, exportNameString, + // callerRealm, evalRealm, evalContext). + TNode result = ImportValue(caller_context, eval_context, + specifier_string, export_name_string); + Return(result); +} + +// https://tc39.es/proposal-shadowrealm/#sec-shadowrealmimportvalue +TNode ShadowRealmBuiltinsAssembler::ImportValue( + TNode caller_context, TNode eval_context, + TNode specifier, TNode export_name) { + // 1. Assert: evalContext is an execution context associated to a ShadowRealm + // instance's [[ExecutionContext]]. + // 2. Let innerCapability be ! NewPromiseCapability(%Promise%). + // 3. Let runningContext be the running execution context. + // 4. If runningContext is not already suspended, suspend runningContext. + // 5. Push evalContext onto the execution context stack; evalContext is now + // the running execution context. + // 6. Perform ! HostImportModuleDynamically(null, specifierString, + // innerCapability). + // 7. Suspend evalContext and remove it from the execution context stack. + // 8. Resume the context that is now on the top of the execution context stack + // as the running execution context. + TNode inner_capability = + CallRuntime(Runtime::kShadowRealmImportValue, eval_context, specifier); + + // 9. Let steps be the steps of an ExportGetter function as described below. + // 10. Let onFulfilled be ! CreateBuiltinFunction(steps, 1, "", « + // [[ExportNameString]] », callerRealm). + // 11. Set onFulfilled.[[ExportNameString]] to exportNameString. + TNode on_fulfilled = AllocateImportValueFulfilledFunction( + caller_context, eval_context, specifier, export_name); + + TNode on_rejected = CAST(LoadContextElement( + caller_context, Context::SHADOW_REALM_IMPORT_VALUE_REJECTED_INDEX)); + // 12. Let promiseCapability be ! NewPromiseCapability(%Promise%). + TNode promise = NewJSPromise(caller_context); + // 13. Return ! PerformPromiseThen(innerCapability.[[Promise]], onFulfilled, + // callerRealm.[[Intrinsics]].[[%ThrowTypeError%]], promiseCapability). + return CallBuiltin(Builtin::kPerformPromiseThen, caller_context, + inner_capability, on_fulfilled, on_rejected, promise); +} + +// ExportGetter of +// https://tc39.es/proposal-shadowrealm/#sec-shadowrealmimportvalue +TF_BUILTIN(ShadowRealmImportValueFulfilled, ShadowRealmBuiltinsAssembler) { + // An ExportGetter function is an anonymous built-in function with a + // [[ExportNameString]] internal slot. When an ExportGetter function is called + // with argument exports, it performs the following steps: + // 8. Let realm be f.[[Realm]]. + TNode context = Parameter(Descriptor::kContext); + TNode eval_context = CAST(LoadContextElement( + context, ImportValueFulfilledFunctionContextSlot::kEvalContextSlot)); + + Label get_export_exception(this, Label::kDeferred); + + // 2. Let f be the active function object. + // 3. Let string be f.[[ExportNameString]]. + // 4. Assert: Type(string) is String. + TNode export_name_string = CAST(LoadContextElement( + context, ImportValueFulfilledFunctionContextSlot::kExportNameSlot)); + + // 1. Assert: exports is a module namespace exotic object. + TNode exports = + Parameter(Descriptor::kExports); + + // 5. Let hasOwn be ? HasOwnProperty(exports, string). + // 6. If hasOwn is false, throw a TypeError exception. + // 7. Let value be ? Get(exports, string). + + // The only exceptions thrown by Runtime::kGetModuleNamespaceExport are + // either the export is not found or the module is not initialized. + TVARIABLE(Object, var_exception); + TNode value; + { + compiler::ScopedExceptionHandler handler(this, &get_export_exception, + &var_exception); + value = CallRuntime(Runtime::kGetModuleNamespaceExport, eval_context, + exports, export_name_string); + } + + // 9. Return ? GetWrappedValue(realm, value). + TNode caller_context = LoadNativeContext(context); + TNode wrapped_result = + CallBuiltin(Builtin::kShadowRealmGetWrappedValue, caller_context, + caller_context, eval_context, value); + Return(wrapped_result); + + BIND(&get_export_exception); + { + TNode specifier_string = CAST(LoadContextElement( + context, ImportValueFulfilledFunctionContextSlot::kSpecifierSlot)); + ThrowTypeError(context, MessageTemplate::kUnresolvableExport, + specifier_string, export_name_string); + } +} + +TF_BUILTIN(ShadowRealmImportValueRejected, ShadowRealmBuiltinsAssembler) { + TNode context = Parameter(Descriptor::kContext); + // TODO(v8:11989): provide a non-observable inspection on the + // pending_exception to the newly created TypeError. + // https://github.com/tc39/proposal-shadowrealm/issues/353 + ThrowTypeError(context, MessageTemplate::kImportShadowRealmRejected); +} + } // namespace internal } // namespace v8 diff --git a/src/codegen/code-stub-assembler.cc b/src/codegen/code-stub-assembler.cc index e6ff700927..d972dca506 100644 --- a/src/codegen/code-stub-assembler.cc +++ b/src/codegen/code-stub-assembler.cc @@ -6520,6 +6520,10 @@ TNode CodeStubAssembler::IsJSStringIterator(TNode object) { return HasInstanceType(object, JS_STRING_ITERATOR_TYPE); } +TNode CodeStubAssembler::IsJSShadowRealm(TNode object) { + return HasInstanceType(object, JS_SHADOW_REALM_TYPE); +} + TNode CodeStubAssembler::IsJSRegExpStringIterator( TNode object) { return HasInstanceType(object, JS_REG_EXP_STRING_ITERATOR_TYPE); diff --git a/src/codegen/code-stub-assembler.h b/src/codegen/code-stub-assembler.h index bccdc34b74..1769906002 100644 --- a/src/codegen/code-stub-assembler.h +++ b/src/codegen/code-stub-assembler.h @@ -111,6 +111,9 @@ enum class PrimitiveType { kBoolean, kNumber, kString, kSymbol }; V(ProxyRevokeSharedFun, proxy_revoke_shared_fun, ProxyRevokeSharedFun) \ V(RegExpSpeciesProtector, regexp_species_protector, RegExpSpeciesProtector) \ V(SetIteratorProtector, set_iterator_protector, SetIteratorProtector) \ + V(ShadowRealmImportValueFulfilledSFI, \ + shadow_realm_import_value_fulfilled_sfi, \ + ShadowRealmImportValueFulfilledSFI) \ V(SingleCharacterStringCache, single_character_string_cache, \ SingleCharacterStringCache) \ V(StringIteratorProtector, string_iterator_protector, \ @@ -2582,6 +2585,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler TNode IsJSPromise(TNode object); TNode IsJSProxy(TNode object); TNode IsJSStringIterator(TNode object); + TNode IsJSShadowRealm(TNode object); TNode IsJSRegExpStringIterator(TNode object); TNode IsJSReceiverInstanceType(TNode instance_type); TNode IsJSReceiverMap(TNode map); diff --git a/src/common/message-template.h b/src/common/message-template.h index 22fde49ffd..38ce038be0 100644 --- a/src/common/message-template.h +++ b/src/common/message-template.h @@ -105,6 +105,7 @@ namespace internal { T(ImportOutsideModule, "Cannot use import statement outside a module") \ T(ImportMetaOutsideModule, "Cannot use 'import.meta' outside a module") \ T(ImportMissingSpecifier, "import() requires a specifier") \ + T(ImportShadowRealmRejected, "Cannot import in the ShadowRealm") \ T(IncompatibleMethodReceiver, "Method % called on incompatible receiver %") \ T(InstanceofNonobjectProto, \ "Function has non-object prototype '%' in instanceof check") \ diff --git a/src/d8/d8.cc b/src/d8/d8.cc index 6f38f7280e..ae89240f69 100644 --- a/src/d8/d8.cc +++ b/src/d8/d8.cc @@ -653,6 +653,100 @@ namespace { const int kHostDefinedOptionsLength = 2; const uint32_t kHostDefinedOptionsMagicConstant = 0xF1F2F3F0; +std::string ToSTLString(Isolate* isolate, Local v8_str) { + String::Utf8Value utf8(isolate, v8_str); + // Should not be able to fail since the input is a String. + CHECK(*utf8); + return *utf8; +} + +// Per-context Module data, allowing sharing of module maps +// across top-level module loads. +class ModuleEmbedderData { + private: + class ModuleGlobalHash { + public: + explicit ModuleGlobalHash(Isolate* isolate) : isolate_(isolate) {} + size_t operator()(const Global& module) const { + return module.Get(isolate_)->GetIdentityHash(); + } + + private: + Isolate* isolate_; + }; + + public: + explicit ModuleEmbedderData(Isolate* isolate) + : module_to_specifier_map(10, ModuleGlobalHash(isolate)), + json_module_to_parsed_json_map( + 10, module_to_specifier_map.hash_function()) {} + + static ModuleType ModuleTypeFromImportAssertions( + Local context, Local import_assertions, + bool hasPositions) { + Isolate* isolate = context->GetIsolate(); + const int kV8AssertionEntrySize = hasPositions ? 3 : 2; + for (int i = 0; i < import_assertions->Length(); + i += kV8AssertionEntrySize) { + Local v8_assertion_key = + import_assertions->Get(context, i).As(); + std::string assertion_key = ToSTLString(isolate, v8_assertion_key); + + if (assertion_key == "type") { + Local v8_assertion_value = + import_assertions->Get(context, i + 1).As(); + std::string assertion_value = ToSTLString(isolate, v8_assertion_value); + if (assertion_value == "json") { + return ModuleType::kJSON; + } else { + // JSON is currently the only supported non-JS type + return ModuleType::kInvalid; + } + } + } + + // If no type is asserted, default to JS. + return ModuleType::kJavaScript; + } + + // Map from (normalized module specifier, module type) pair to Module. + std::map, Global> module_map; + // Map from Module to its URL as defined in the ScriptOrigin + std::unordered_map, std::string, ModuleGlobalHash> + module_to_specifier_map; + // Map from JSON Module to its parsed content, for use in module + // JSONModuleEvaluationSteps + std::unordered_map, Global, ModuleGlobalHash> + json_module_to_parsed_json_map; + + // Origin location used for resolving modules when referrer is null. + std::string origin; +}; + +enum { kModuleEmbedderDataIndex, kInspectorClientIndex }; + +std::shared_ptr InitializeModuleEmbedderData( + Local context) { + i::Isolate* i_isolate = reinterpret_cast(context->GetIsolate()); + const size_t kModuleEmbedderDataEstimate = 4 * 1024; // module map. + i::Handle> module_data_managed = + i::Managed::Allocate( + i_isolate, kModuleEmbedderDataEstimate, context->GetIsolate()); + v8::Local module_data = Utils::ToLocal(module_data_managed); + context->SetEmbedderData(kModuleEmbedderDataIndex, module_data); + return module_data_managed->get(); +} + +std::shared_ptr GetModuleDataFromContext( + Local context) { + v8::Local module_data = + context->GetEmbedderData(kModuleEmbedderDataIndex); + i::Handle> module_data_managed = + i::Handle>::cast( + Utils::OpenHandle(module_data)); + return module_data_managed->get(); +} + ScriptOrigin CreateScriptOrigin(Isolate* isolate, Local resource_name, v8::ScriptType type) { Local options = @@ -741,6 +835,10 @@ bool Shell::ExecuteString(Isolate* isolate, Local source, ScriptOrigin origin = CreateScriptOrigin(isolate, name, ScriptType::kClassic); + std::shared_ptr module_data = + GetModuleDataFromContext(realm); + module_data->origin = ToSTLString(isolate, name); + for (int i = 1; i < options.repeat_compile; ++i) { HandleScope handle_scope_for_compiling(isolate); if (CompileString