diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/shell/ShellModuleObjectWrapper.cpp | 469 |
1 files changed, 469 insertions, 0 deletions
diff --git a/js/src/shell/ShellModuleObjectWrapper.cpp b/js/src/shell/ShellModuleObjectWrapper.cpp new file mode 100644 index 0000000000..4bc10d846d --- /dev/null +++ b/js/src/shell/ShellModuleObjectWrapper.cpp @@ -0,0 +1,469 @@ +/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 + * -*- */ +/* 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 "shell/ShellModuleObjectWrapper.h" + +#include "mozilla/Maybe.h" +#include "mozilla/Span.h" + +#include "jsapi.h" // JS_GetProperty, JS::Call, JS_NewPlainObject, JS_DefineProperty + +#include "builtin/ModuleObject.h" // js::ModuleObject +#include "js/CallAndConstruct.h" // JS::Call +#include "js/CallArgs.h" // JS::CallArgs +#include "js/CallNonGenericMethod.h" // CallNonGenericMethod +#include "js/Class.h" // JSClass, JSCLASS_* +#include "js/ErrorReport.h" // JS_ReportErrorASCII +#include "js/PropertyAndElement.h" // JS_GetProperty +#include "js/PropertySpec.h" // JSPropertySpec, JS_PSG, JS_PS_END, JSFunctionSpec, JS_FN, JS_FN_END +#include "js/RootingAPI.h" // JS::Rooted, JS::Handle, JS::MutableHandle +#include "js/Value.h" // JS::Value +#include "vm/ArrayObject.h" // ArrayObject, NewDenseFullyAllocatedArray +#include "vm/GlobalObject.h" // DefinePropertiesAndFunctions +#include "vm/JSFunction.h" // JSFunction +#include "vm/JSObject.h" // JSObject +#include "vm/List.h" // ListObject +#include "vm/NativeObject.h" // NativeObject +#include "vm/Stack.h" // FixedInvokeArgs + +#include "vm/NativeObject-inl.h" // NativeObject::ensureDenseInitializedLength + +using namespace js; +using namespace js::shell; + +using mozilla::Span; + +#define DEFINE_CLASS_IMPL(CLASS) \ + CLASS* Shell##CLASS##Wrapper::get() { \ + return &getReservedSlot(TargetSlot).toObject().as<CLASS>(); \ + } \ + /* static */ const JSClass Shell##CLASS##Wrapper::class_ = { \ + "Shell" #CLASS "Wrapper", \ + JSCLASS_HAS_RESERVED_SLOTS(Shell##CLASS##Wrapper::SlotCount)}; \ + MOZ_ALWAYS_INLINE bool IsShell##CLASS##Wrapper(JS::Handle<JS::Value> v) { \ + return v.isObject() && v.toObject().is<Shell##CLASS##Wrapper>(); \ + } + +#define DEFINE_CLASS(CLASS) \ + class Shell##CLASS##Wrapper : public js::NativeObject { \ + public: \ + using Target = CLASS; \ + enum ModuleSlot { TargetSlot = 0, SlotCount }; \ + static const JSClass class_; \ + static Shell##CLASS##Wrapper* create(JSContext* cx, \ + JS::Handle<CLASS*> obj); \ + CLASS* get(); \ + }; \ + DEFINE_CLASS_IMPL(CLASS) + +#define DEFINE_NATIVE_CLASS_IMPL(CLASS) \ + CLASS* Shell##CLASS##Wrapper::get() { \ + return static_cast<CLASS*>(getReservedSlot(TargetSlot).toPrivate()); \ + } \ + /* static */ const JSClass Shell##CLASS##Wrapper::class_ = { \ + "Shell" #CLASS "Wrapper", \ + JSCLASS_HAS_RESERVED_SLOTS(Shell##CLASS##Wrapper::SlotCount)}; \ + MOZ_ALWAYS_INLINE bool IsShell##CLASS##Wrapper(JS::Handle<JS::Value> v) { \ + return v.isObject() && v.toObject().is<Shell##CLASS##Wrapper>(); \ + } + +#define DEFINE_NATIVE_CLASS(CLASS) \ + class Shell##CLASS##Wrapper : public js::NativeObject { \ + public: \ + using Target = CLASS; \ + enum ModuleSlot { OwnerSlot = 0, TargetSlot, SlotCount }; \ + static const JSClass class_; \ + static Shell##CLASS##Wrapper* create(JSContext* cx, \ + JS::Handle<JSObject*> owner, \ + CLASS* obj); \ + CLASS* get(); \ + }; \ + DEFINE_NATIVE_CLASS_IMPL(CLASS) + +DEFINE_CLASS(ModuleRequestObject) +DEFINE_NATIVE_CLASS(ImportEntry) +DEFINE_NATIVE_CLASS(ExportEntry) +DEFINE_NATIVE_CLASS(RequestedModule) +// NOTE: We don't need wrapper for IndirectBindingMap and ModuleNamespaceObject +DEFINE_CLASS_IMPL(ModuleObject) + +#undef DEFINE_CLASS +#undef DEFINE_CLASS_IMPL +#undef DEFINE_NATIVE_CLASS +#undef DEFINE_NATIVE_CLASS_IMPL + +bool IdentFilter(JSContext* cx, JS::Handle<JS::Value> from, + JS::MutableHandle<JS::Value> to) { + to.set(from); + return true; +} + +template <class T> +bool SingleFilter(JSContext* cx, JS::Handle<JS::Value> from, + JS::MutableHandle<JS::Value> to) { + using TargetT = typename T::Target; + + if (!from.isObject() || !from.toObject().is<TargetT>()) { + to.set(from); + return true; + } + + JS::Rooted<TargetT*> obj(cx, &from.toObject().as<TargetT>()); + JS::Rooted<T*> filtered(cx, T::create(cx, obj)); + if (!filtered) { + return false; + } + to.setObject(*filtered); + return true; +} + +template <class T> +bool ArrayFilter(JSContext* cx, JS::Handle<JS::Value> from, + JS::MutableHandle<JS::Value> to) { + using TargetT = typename T::Target; + + if (!from.isObject() || !from.toObject().is<ArrayObject>()) { + to.set(from); + return true; + } + + JS::Rooted<ArrayObject*> fromArray(cx, &from.toObject().as<ArrayObject>()); + uint32_t length = fromArray->length(); + JS::Rooted<ArrayObject*> toArray(cx, NewDenseFullyAllocatedArray(cx, length)); + if (!toArray) { + return false; + } + + toArray->ensureDenseInitializedLength(0, length); + + for (uint32_t i = 0; i < length; i++) { + JS::Rooted<JS::Value> item(cx, fromArray->getDenseElement(i)); + JS::Rooted<TargetT*> req(cx, &item.toObject().as<TargetT>()); + JS::Rooted<T*> filtered(cx, T::create(cx, req)); + if (!filtered) { + return false; + } + toArray->initDenseElement(i, ObjectValue(*filtered)); + } + to.setObject(*toArray); + return true; +} + +template <class T> +bool ListToArrayFilter(JSContext* cx, JS::Handle<JS::Value> from, + JS::MutableHandle<JS::Value> to) { + using TargetT = typename T::Target; + + if (!from.isObject() || !from.toObject().is<ListObject>()) { + to.set(from); + return true; + } + + JS::Rooted<ListObject*> fromList(cx, &from.toObject().as<ListObject>()); + uint32_t length = fromList->length(); + JS::Rooted<ArrayObject*> toArray(cx, NewDenseFullyAllocatedArray(cx, length)); + if (!toArray) { + return false; + } + + toArray->ensureDenseInitializedLength(0, length); + + for (uint32_t i = 0; i < length; i++) { + JS::Rooted<JS::Value> item(cx, fromList->get(i)); + JS::Rooted<TargetT*> req(cx, &item.toObject().as<TargetT>()); + JS::Rooted<T*> filtered(cx, T::create(cx, req)); + if (!filtered) { + return false; + } + toArray->initDenseElement(i, ObjectValue(*filtered)); + } + to.setObject(*toArray); + return true; +} + +static Value StringOrNullValue(JSString* maybeString) { + if (!maybeString) { + return NullValue(); + } + + return StringValue(maybeString); +} + +static Value Uint32Value(uint32_t x) { + MOZ_ASSERT(x <= INT32_MAX); + return Int32Value(x); +} + +static Value Uint32OrUndefinedValue(mozilla::Maybe<uint32_t> x) { + if (x.isNothing()) { + return UndefinedValue(); + } + + return Uint32Value(x.value()); +} + +static Value StatusValue(ModuleStatus status) { + return Int32Value(int32_t(status)); +} + +static Value ObjectOrUndefinedValue(JSObject* object) { + if (!object) { + return UndefinedValue(); + } + + return ObjectValue(*object); +} + +template <class T, typename RawGetterT, typename FilterT> +bool ShellModuleWrapperGetter(JSContext* cx, const JS::CallArgs& args, + RawGetterT rawGetter, FilterT filter) { + JS::Rooted<T*> wrapper(cx, &args.thisv().toObject().as<T>()); + JS::Rooted<JS::Value> raw(cx, rawGetter(wrapper->get())); + + JS::Rooted<JS::Value> filtered(cx); + if (!filter(cx, raw, &filtered)) { + return false; + } + + args.rval().set(filtered); + return true; +} + +#define DEFINE_GETTER_FUNCTIONS(CLASS, PROP, TO_VALUE, FILTER) \ + static Value Shell##CLASS##Wrapper_##PROP##Getter_raw(CLASS* obj) { \ + return TO_VALUE(obj->PROP()); \ + } \ + static bool Shell##CLASS##Wrapper_##PROP##Getter_impl( \ + JSContext* cx, const JS::CallArgs& args) { \ + return ShellModuleWrapperGetter<Shell##CLASS##Wrapper>( \ + cx, args, Shell##CLASS##Wrapper_##PROP##Getter_raw, FILTER); \ + } \ + static bool Shell##CLASS##Wrapper_##PROP##Getter(JSContext* cx, \ + unsigned argc, Value* vp) { \ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \ + return CallNonGenericMethod<IsShell##CLASS##Wrapper, \ + Shell##CLASS##Wrapper_##PROP##Getter_impl>( \ + cx, args); \ + } + +template <class T> +bool SpanToArrayFilter(JSContext* cx, JS::Handle<JSObject*> owner, + Span<const typename T::Target> from, + JS::MutableHandle<JS::Value> to) { + size_t length = from.Length(); + JS::Rooted<ArrayObject*> toArray(cx, NewDenseFullyAllocatedArray(cx, length)); + if (!toArray) { + return false; + } + + toArray->ensureDenseInitializedLength(0, length); + + for (uint32_t i = 0; i < length; i++) { + auto* element = const_cast<typename T::Target*>(&from[i]); + JS::Rooted<T*> filtered(cx, T::create(cx, owner, element)); + if (!filtered) { + return false; + } + toArray->initDenseElement(i, ObjectValue(*filtered)); + } + + to.setObject(*toArray); + return true; +} + +template <class T, typename RawGetterT, typename FilterT> +bool ShellModuleNativeWrapperGetter(JSContext* cx, const JS::CallArgs& args, + RawGetterT rawGetter, FilterT filter) { + JS::Rooted<T*> wrapper(cx, &args.thisv().toObject().as<T>()); + JS::Rooted<typename T::Target*> owner(cx, wrapper->get()); + + JS::Rooted<JS::Value> filtered(cx); + if (!filter(cx, owner, rawGetter(owner), &filtered)) { + return false; + } + + args.rval().set(filtered); + return true; +} + +#define DEFINE_NATIVE_GETTER_FUNCTIONS(CLASS, PROP, FILTER) \ + static auto Shell##CLASS##Wrapper_##PROP##Getter_raw(CLASS* obj) { \ + return obj->PROP(); \ + } \ + static bool Shell##CLASS##Wrapper_##PROP##Getter_impl( \ + JSContext* cx, const JS::CallArgs& args) { \ + return ShellModuleNativeWrapperGetter<Shell##CLASS##Wrapper>( \ + cx, args, Shell##CLASS##Wrapper_##PROP##Getter_raw, FILTER); \ + } \ + static bool Shell##CLASS##Wrapper_##PROP##Getter(JSContext* cx, \ + unsigned argc, Value* vp) { \ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \ + return CallNonGenericMethod<IsShell##CLASS##Wrapper, \ + Shell##CLASS##Wrapper_##PROP##Getter_impl>( \ + cx, args); \ + } + +DEFINE_GETTER_FUNCTIONS(ModuleRequestObject, specifier, StringOrNullValue, + IdentFilter) +DEFINE_GETTER_FUNCTIONS(ModuleRequestObject, assertions, ObjectOrNullValue, + IdentFilter) + +static const JSPropertySpec ShellModuleRequestObjectWrapper_accessors[] = { + JS_PSG("specifier", ShellModuleRequestObjectWrapper_specifierGetter, 0), + JS_PSG("assertions", ShellModuleRequestObjectWrapper_assertionsGetter, 0), + JS_PS_END}; + +DEFINE_GETTER_FUNCTIONS(ImportEntry, moduleRequest, ObjectOrNullValue, + SingleFilter<ShellModuleRequestObjectWrapper>) +DEFINE_GETTER_FUNCTIONS(ImportEntry, importName, StringOrNullValue, IdentFilter) +DEFINE_GETTER_FUNCTIONS(ImportEntry, localName, StringValue, IdentFilter) +DEFINE_GETTER_FUNCTIONS(ImportEntry, lineNumber, Uint32Value, IdentFilter) +DEFINE_GETTER_FUNCTIONS(ImportEntry, columnNumber, Uint32Value, IdentFilter) + +static const JSPropertySpec ShellImportEntryWrapper_accessors[] = { + JS_PSG("moduleRequest", ShellImportEntryWrapper_moduleRequestGetter, 0), + JS_PSG("importName", ShellImportEntryWrapper_importNameGetter, 0), + JS_PSG("localName", ShellImportEntryWrapper_localNameGetter, 0), + JS_PSG("lineNumber", ShellImportEntryWrapper_lineNumberGetter, 0), + JS_PSG("columnNumber", ShellImportEntryWrapper_columnNumberGetter, 0), + JS_PS_END}; + +DEFINE_GETTER_FUNCTIONS(ExportEntry, exportName, StringOrNullValue, IdentFilter) +DEFINE_GETTER_FUNCTIONS(ExportEntry, moduleRequest, ObjectOrNullValue, + SingleFilter<ShellModuleRequestObjectWrapper>) +DEFINE_GETTER_FUNCTIONS(ExportEntry, importName, StringOrNullValue, IdentFilter) +DEFINE_GETTER_FUNCTIONS(ExportEntry, localName, StringOrNullValue, IdentFilter) +DEFINE_GETTER_FUNCTIONS(ExportEntry, lineNumber, Uint32Value, IdentFilter) +DEFINE_GETTER_FUNCTIONS(ExportEntry, columnNumber, Uint32Value, IdentFilter) + +static const JSPropertySpec ShellExportEntryWrapper_accessors[] = { + JS_PSG("exportName", ShellExportEntryWrapper_exportNameGetter, 0), + JS_PSG("moduleRequest", ShellExportEntryWrapper_moduleRequestGetter, 0), + JS_PSG("importName", ShellExportEntryWrapper_importNameGetter, 0), + JS_PSG("localName", ShellExportEntryWrapper_localNameGetter, 0), + JS_PSG("lineNumber", ShellExportEntryWrapper_lineNumberGetter, 0), + JS_PSG("columnNumber", ShellExportEntryWrapper_columnNumberGetter, 0), + JS_PS_END}; + +DEFINE_GETTER_FUNCTIONS(RequestedModule, moduleRequest, ObjectOrNullValue, + SingleFilter<ShellModuleRequestObjectWrapper>) +DEFINE_GETTER_FUNCTIONS(RequestedModule, lineNumber, Uint32Value, IdentFilter) +DEFINE_GETTER_FUNCTIONS(RequestedModule, columnNumber, Uint32Value, IdentFilter) + +static const JSPropertySpec ShellRequestedModuleWrapper_accessors[] = { + JS_PSG("moduleRequest", ShellRequestedModuleWrapper_moduleRequestGetter, 0), + JS_PSG("lineNumber", ShellRequestedModuleWrapper_lineNumberGetter, 0), + JS_PSG("columnNumber", ShellRequestedModuleWrapper_columnNumberGetter, 0), + JS_PS_END}; + +DEFINE_GETTER_FUNCTIONS(ModuleObject, namespace_, ObjectOrNullValue, + IdentFilter) +DEFINE_GETTER_FUNCTIONS(ModuleObject, status, StatusValue, IdentFilter) +DEFINE_GETTER_FUNCTIONS(ModuleObject, maybeEvaluationError, Value, IdentFilter) +DEFINE_NATIVE_GETTER_FUNCTIONS(ModuleObject, requestedModules, + SpanToArrayFilter<ShellRequestedModuleWrapper>) +DEFINE_NATIVE_GETTER_FUNCTIONS(ModuleObject, importEntries, + SpanToArrayFilter<ShellImportEntryWrapper>) +DEFINE_NATIVE_GETTER_FUNCTIONS(ModuleObject, localExportEntries, + SpanToArrayFilter<ShellExportEntryWrapper>) +DEFINE_NATIVE_GETTER_FUNCTIONS(ModuleObject, indirectExportEntries, + SpanToArrayFilter<ShellExportEntryWrapper>) +DEFINE_NATIVE_GETTER_FUNCTIONS(ModuleObject, starExportEntries, + SpanToArrayFilter<ShellExportEntryWrapper>) +DEFINE_GETTER_FUNCTIONS(ModuleObject, maybeDfsIndex, Uint32OrUndefinedValue, + IdentFilter) +DEFINE_GETTER_FUNCTIONS(ModuleObject, maybeDfsAncestorIndex, + Uint32OrUndefinedValue, IdentFilter) +DEFINE_GETTER_FUNCTIONS(ModuleObject, hasTopLevelAwait, BooleanValue, + IdentFilter) +DEFINE_GETTER_FUNCTIONS(ModuleObject, maybeTopLevelCapability, + ObjectOrUndefinedValue, IdentFilter) +DEFINE_GETTER_FUNCTIONS(ModuleObject, isAsyncEvaluating, BooleanValue, + IdentFilter) +DEFINE_GETTER_FUNCTIONS(ModuleObject, maybeAsyncEvaluatingPostOrder, + Uint32OrUndefinedValue, IdentFilter) +DEFINE_GETTER_FUNCTIONS(ModuleObject, asyncParentModules, ObjectOrNullValue, + ListToArrayFilter<ShellModuleObjectWrapper>) +DEFINE_GETTER_FUNCTIONS(ModuleObject, maybePendingAsyncDependencies, + Uint32OrUndefinedValue, IdentFilter) + +static const JSPropertySpec ShellModuleObjectWrapper_accessors[] = { + JS_PSG("namespace", ShellModuleObjectWrapper_namespace_Getter, 0), + JS_PSG("status", ShellModuleObjectWrapper_statusGetter, 0), + JS_PSG("evaluationError", + ShellModuleObjectWrapper_maybeEvaluationErrorGetter, 0), + JS_PSG("requestedModules", ShellModuleObjectWrapper_requestedModulesGetter, + 0), + JS_PSG("importEntries", ShellModuleObjectWrapper_importEntriesGetter, 0), + JS_PSG("localExportEntries", + ShellModuleObjectWrapper_localExportEntriesGetter, 0), + JS_PSG("indirectExportEntries", + ShellModuleObjectWrapper_indirectExportEntriesGetter, 0), + JS_PSG("starExportEntries", + ShellModuleObjectWrapper_starExportEntriesGetter, 0), + JS_PSG("dfsIndex", ShellModuleObjectWrapper_maybeDfsIndexGetter, 0), + JS_PSG("dfsAncestorIndex", + ShellModuleObjectWrapper_maybeDfsAncestorIndexGetter, 0), + JS_PSG("hasTopLevelAwait", ShellModuleObjectWrapper_hasTopLevelAwaitGetter, + 0), + JS_PSG("topLevelCapability", + ShellModuleObjectWrapper_maybeTopLevelCapabilityGetter, 0), + JS_PSG("isAsyncEvaluating", + ShellModuleObjectWrapper_isAsyncEvaluatingGetter, 0), + JS_PSG("asyncEvaluatingPostOrder", + ShellModuleObjectWrapper_maybeAsyncEvaluatingPostOrderGetter, 0), + JS_PSG("asyncParentModules", + ShellModuleObjectWrapper_asyncParentModulesGetter, 0), + JS_PSG("pendingAsyncDependencies", + ShellModuleObjectWrapper_maybePendingAsyncDependenciesGetter, 0), + JS_PS_END}; + +#undef DEFINE_GETTER_FUNCTIONS +#undef DEFINE_NATIVE_GETTER_FUNCTIONS + +#define DEFINE_CREATE(CLASS, ACCESSORS, FUNCTIONS) \ + /* static */ \ + Shell##CLASS##Wrapper* Shell##CLASS##Wrapper::create( \ + JSContext* cx, JS::Handle<CLASS*> target) { \ + JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &class_)); \ + if (!obj) { \ + return nullptr; \ + } \ + if (!DefinePropertiesAndFunctions(cx, obj, ACCESSORS, FUNCTIONS)) { \ + return nullptr; \ + } \ + auto* wrapper = &obj->as<Shell##CLASS##Wrapper>(); \ + wrapper->initReservedSlot(TargetSlot, ObjectValue(*target)); \ + return wrapper; \ + } + +#define DEFINE_NATIVE_CREATE(CLASS, ACCESSORS, FUNCTIONS) \ + /* static */ \ + Shell##CLASS##Wrapper* Shell##CLASS##Wrapper::create( \ + JSContext* cx, JS::Handle<JSObject*> owner, CLASS* target) { \ + JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &class_)); \ + if (!obj) { \ + return nullptr; \ + } \ + if (!DefinePropertiesAndFunctions(cx, obj, ACCESSORS, FUNCTIONS)) { \ + return nullptr; \ + } \ + auto* wrapper = &obj->as<Shell##CLASS##Wrapper>(); \ + wrapper->initReservedSlot(OwnerSlot, ObjectValue(*owner)); \ + wrapper->initReservedSlot(TargetSlot, PrivateValue(target)); \ + return wrapper; \ + } + +DEFINE_CREATE(ModuleRequestObject, ShellModuleRequestObjectWrapper_accessors, + nullptr) +DEFINE_NATIVE_CREATE(ImportEntry, ShellImportEntryWrapper_accessors, nullptr) +DEFINE_NATIVE_CREATE(ExportEntry, ShellExportEntryWrapper_accessors, nullptr) +DEFINE_NATIVE_CREATE(RequestedModule, ShellRequestedModuleWrapper_accessors, + nullptr) +DEFINE_CREATE(ModuleObject, ShellModuleObjectWrapper_accessors, nullptr) + +#undef DEFINE_CREATE +#undef DEFINE_NATIVE_CREATE |