Bug 1719459 - Part 2: Add shell ModuleObject wrapper to avoid unexpected operation. r=jonco

Differential Revision: https://phabricator.services.mozilla.com/D119566
This commit is contained in:
Tooru Fujisawa 2021-07-12 16:02:21 +00:00
parent e819448fb3
commit 4ccc6acc5c
6 changed files with 707 additions and 28 deletions

View File

@ -1,5 +1,3 @@
// |jit-test| error: TypeError
let a = registerModule('a', parseModule("var x = 1; export { x };"));
let b = registerModule('b', parseModule("import { x as y } from 'a';"));
a.__proto__ = {15: 1337};

View File

@ -0,0 +1,223 @@
// Test shell ModuleObject wrapper's accessors and methods.
load(libdir + "asserts.js");
function testGetter(obj, name) {
// Check the getter is defined on the instance, instead of prototype.
// * raw ModuleObject's getters are defined on prototype
// * ModuleObject wrapper's getters are defined on instance
const desc = Object.getOwnPropertyDescriptor(obj, name);
assertEq(typeof desc.get, "function");
assertEq(Object.getOwnPropertyDescriptor(Object.getPrototypeOf(obj), name),
undefined);
// Check invalid this value.
assertThrowsInstanceOf(() => {
desc.get.call({});
}, Error);
}
function testMethod(obj, name) {
// Check the method is defined on the instance, instead of prototype.
// * raw ModuleObject's methods are defined on prototype
// * ModuleObject wrapper's methods are defined on instance
const desc = Object.getOwnPropertyDescriptor(obj, name);
assertEq(typeof desc.value, "function");
assertEq(Object.getOwnPropertyDescriptor(Object.getPrototypeOf(obj), name),
undefined);
// Check invalid this value.
assertThrowsInstanceOf(() => {
desc.value.call({});
}, Error);
}
// ==== namespace getter ====
const a = registerModule('a', parseModule(`
export const v = 10;
`));
const b = registerModule('b', parseModule(`
import * as ns from 'a'
`));
b.declarationInstantiation();
b.evaluation();
assertEq(a.namespace.v, 10);
testGetter(a, "namespace");
// ==== status getter ====
const MODULE_STATUS_UNLINKED = 0;
const MODULE_STATUS_LINKED = 2;
const MODULE_STATUS_EVALUATED = 4;
const c = registerModule('c', parseModule(`
`));
assertEq(c.status, MODULE_STATUS_UNLINKED);
c.declarationInstantiation();
assertEq(c.status, MODULE_STATUS_LINKED);
c.evaluation();
assertEq(c.status, MODULE_STATUS_EVALUATED);
testGetter(c, "status");
// ==== evaluationError getter ====
const d = registerModule('d', parseModule(`
f();
`));
d.declarationInstantiation();
try {
d.evaluation();
} catch (e) {
}
assertEq(d.evaluationError instanceof ReferenceError, true);
testGetter(d, "evaluationError");
// ==== requestedModules getter ====
const e = parseModule(`
import a from 'b';
`);
assertEq(e.requestedModules.length, 1);
assertEq(e.requestedModules[0].moduleRequest.specifier, 'b');
assertEq(e.requestedModules[0].lineNumber, 2);
assertEq(e.requestedModules[0].columnNumber, 14);
testGetter(e, "requestedModules");
testGetter(e.requestedModules[0], "moduleRequest");
testGetter(e.requestedModules[0].moduleRequest, "specifier");
testGetter(e.requestedModules[0], "lineNumber");
testGetter(e.requestedModules[0], "columnNumber");
// ==== importEntries getter ====
const f = parseModule(`
import {a as A} from 'b';
`);
assertEq(f.importEntries.length, 1);
assertEq(f.importEntries[0].moduleRequest.specifier, 'b');
assertEq(f.importEntries[0].importName, 'a');
assertEq(f.importEntries[0].localName, 'A');
assertEq(f.importEntries[0].lineNumber, 2);
assertEq(f.importEntries[0].columnNumber, 8);
testGetter(f, "importEntries");
testGetter(f.importEntries[0], "moduleRequest");
testGetter(f.importEntries[0].moduleRequest, "specifier");
testGetter(f.importEntries[0], "importName");
testGetter(f.importEntries[0], "localName");
testGetter(f.importEntries[0], "lineNumber");
testGetter(f.importEntries[0], "columnNumber");
// ==== localExportEntries getter ====
const g = parseModule(`
export const v = 1;
`);
assertEq(g.localExportEntries.length, 1);
assertEq(g.localExportEntries[0].exportName, 'v');
assertEq(g.localExportEntries[0].moduleRequest.specifier, null);
assertEq(g.localExportEntries[0].importName, null);
assertEq(g.localExportEntries[0].localName, 'v');
assertEq(g.localExportEntries[0].lineNumber, 0);
assertEq(g.localExportEntries[0].columnNumber, 0);
testGetter(g, "localExportEntries");
testGetter(g.localExportEntries[0], "exportName");
testGetter(g.localExportEntries[0], "moduleRequest");
testGetter(g.localExportEntries[0].moduleRequest, "specifier");
testGetter(g.localExportEntries[0], "importName");
testGetter(g.localExportEntries[0], "localName");
testGetter(g.localExportEntries[0], "lineNumber");
testGetter(g.localExportEntries[0], "columnNumber");
// ==== indirectExportEntries getter ====
const h = parseModule(`
export {v} from "b";
`);
assertEq(h.indirectExportEntries.length, 1);
assertEq(h.indirectExportEntries[0].exportName, 'v');
assertEq(h.indirectExportEntries[0].moduleRequest.specifier, "b");
assertEq(h.indirectExportEntries[0].importName, "v");
assertEq(h.indirectExportEntries[0].localName, null);
assertEq(h.indirectExportEntries[0].lineNumber, 2);
assertEq(h.indirectExportEntries[0].columnNumber, 8);
// ==== starExportEntries getter ====
const i = parseModule(`
export * from "b";
`);
assertEq(i.starExportEntries.length, 1);
assertEq(i.starExportEntries[0].exportName, null);
assertEq(i.starExportEntries[0].moduleRequest.specifier, "b");
assertEq(i.starExportEntries[0].importName, null);
assertEq(i.starExportEntries[0].localName, null);
assertEq(i.starExportEntries[0].lineNumber, 2);
assertEq(i.starExportEntries[0].columnNumber, 7);
// ==== dfsIndex and dfsAncestorIndex getters ====
const j = registerModule('j', parseModule(`
export const v1 = 10;
import {v2} from 'k'
`));
const k = registerModule('k', parseModule(`
export const v2 = 10;
import {v1} from 'j'
`));
const l = registerModule('l', parseModule(`
export const v3 = 10;
import {v2} from 'k'
import {v1} from 'j'
`));
assertEq(j.dfsIndex, undefined);
assertEq(j.dfsAncestorIndex, undefined);
assertEq(k.dfsIndex, undefined);
assertEq(k.dfsAncestorIndex, undefined);
assertEq(l.dfsIndex, undefined);
assertEq(l.dfsAncestorIndex, undefined);
l.declarationInstantiation();
assertEq(j.dfsIndex, 2);
assertEq(j.dfsAncestorIndex, 1);
assertEq(k.dfsIndex, 1);
assertEq(k.dfsAncestorIndex, 1);
assertEq(l.dfsIndex, 0);
assertEq(l.dfsAncestorIndex, 0);
// ==== async and promises getters ====
const m = parseModule(`
`);
assertEq(m.async, false);
assertEq(m.topLevelCapability, undefined);
assertEq(m.asyncEvaluatingPostOrder, undefined);
assertEq(m.asyncParentModules[0], undefined);
assertEq(m.pendingAsyncDependencies, undefined);
testGetter(m, "async");
testGetter(m, "topLevelCapability");
testGetter(m, "asyncEvaluatingPostOrder");
testGetter(m, "asyncParentModules");
testGetter(m, "pendingAsyncDependencies");
// ==== getExportedNames method shouldn't be exposed ====
const n = parseModule(``);
assertEq(n.getExportedNames, undefined);
// ==== resolveExport method shouldn't be exposed ====
const o = parseModule(``);
assertEq(o.resolveExport, undefined);
// ==== declarationInstantiation and evaluationmethod methods ====
const p = parseModule(``);
p.declarationInstantiation();
p.evaluation();
testMethod(p, "declarationInstantiation");
testMethod(p, "evaluation");
const q = decodeModule(codeModule(parseModule(``)));
q.declarationInstantiation();
q.evaluation();
testMethod(q, "declarationInstantiation");
testMethod(q, "evaluation");
if (helperThreadCount() > 0) {
offThreadCompileModule(``);
let r = finishOffThreadModule();
r.declarationInstantiation();
r.evaluation();
testMethod(r, "declarationInstantiation");
testMethod(r, "evaluation");
}
// ==== gatherAsyncParentCompletions method shouldn't be exposed ====
const s = parseModule(``);
assertEq(s.gatherAsyncParentCompletions, undefined);

View File

@ -0,0 +1,385 @@
/* -*- 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 "jsapi.h" // JS_GetProperty, JS::Call, JS_NewPlainObject, JS_DefineProperty
#include "builtin/ModuleObject.h" // js::ModuleObject
#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/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;
#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_CLASS(ModuleRequestObject)
DEFINE_CLASS(ImportEntryObject)
DEFINE_CLASS(ExportEntryObject)
DEFINE_CLASS(RequestedModuleObject)
// NOTE: We don't need wrapper for IndirectBindingMap and ModuleNamespaceObject
DEFINE_CLASS_IMPL(ModuleObject)
#undef DEFINE_CLASS
#undef DEFINE_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;
}
template <class T, typename FilterT>
bool ShellModuleWrapperGetter(JSContext* cx, const JS::CallArgs& args,
const char* prop, FilterT filter) {
using TargetT = typename T::Target;
JS::Rooted<TargetT*> obj(cx, args.thisv().toObject().as<T>().get());
JS::Rooted<JS::Value> rval(cx);
if (!JS_GetProperty(cx, obj, prop, &rval)) {
return false;
}
JS::Rooted<JS::Value> filtered(cx);
if (!filter(cx, rval, &filtered)) {
return false;
}
args.rval().set(filtered);
return true;
}
#define DEFINE_GETTER_FUNCTIONS(CLASS, PROP, FILTER) \
static bool Shell##CLASS##Wrapper_##PROP##Getter_impl( \
JSContext* cx, const JS::CallArgs& args) { \
return ShellModuleWrapperGetter<Shell##CLASS##Wrapper>(cx, args, #PROP, \
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, IdentFilter)
static const JSPropertySpec ShellModuleRequestObjectWrapper_accessors[] = {
JS_PSG("specifier", ShellModuleRequestObjectWrapper_specifierGetter, 0),
JS_PS_END};
DEFINE_GETTER_FUNCTIONS(ImportEntryObject, moduleRequest,
SingleFilter<ShellModuleRequestObjectWrapper>)
DEFINE_GETTER_FUNCTIONS(ImportEntryObject, importName, IdentFilter)
DEFINE_GETTER_FUNCTIONS(ImportEntryObject, localName, IdentFilter)
DEFINE_GETTER_FUNCTIONS(ImportEntryObject, lineNumber, IdentFilter)
DEFINE_GETTER_FUNCTIONS(ImportEntryObject, columnNumber, IdentFilter)
static const JSPropertySpec ShellImportEntryObjectWrapper_accessors[] = {
JS_PSG("moduleRequest", ShellImportEntryObjectWrapper_moduleRequestGetter,
0),
JS_PSG("importName", ShellImportEntryObjectWrapper_importNameGetter, 0),
JS_PSG("localName", ShellImportEntryObjectWrapper_localNameGetter, 0),
JS_PSG("lineNumber", ShellImportEntryObjectWrapper_lineNumberGetter, 0),
JS_PSG("columnNumber", ShellImportEntryObjectWrapper_columnNumberGetter, 0),
JS_PS_END};
DEFINE_GETTER_FUNCTIONS(ExportEntryObject, exportName, IdentFilter)
DEFINE_GETTER_FUNCTIONS(ExportEntryObject, moduleRequest,
SingleFilter<ShellModuleRequestObjectWrapper>)
DEFINE_GETTER_FUNCTIONS(ExportEntryObject, importName, IdentFilter)
DEFINE_GETTER_FUNCTIONS(ExportEntryObject, localName, IdentFilter)
DEFINE_GETTER_FUNCTIONS(ExportEntryObject, lineNumber, IdentFilter)
DEFINE_GETTER_FUNCTIONS(ExportEntryObject, columnNumber, IdentFilter)
static const JSPropertySpec ShellExportEntryObjectWrapper_accessors[] = {
JS_PSG("exportName", ShellExportEntryObjectWrapper_exportNameGetter, 0),
JS_PSG("moduleRequest", ShellExportEntryObjectWrapper_moduleRequestGetter,
0),
JS_PSG("importName", ShellExportEntryObjectWrapper_importNameGetter, 0),
JS_PSG("localName", ShellExportEntryObjectWrapper_localNameGetter, 0),
JS_PSG("lineNumber", ShellExportEntryObjectWrapper_lineNumberGetter, 0),
JS_PSG("columnNumber", ShellExportEntryObjectWrapper_columnNumberGetter, 0),
JS_PS_END};
DEFINE_GETTER_FUNCTIONS(RequestedModuleObject, moduleRequest,
SingleFilter<ShellModuleRequestObjectWrapper>)
DEFINE_GETTER_FUNCTIONS(RequestedModuleObject, lineNumber, IdentFilter)
DEFINE_GETTER_FUNCTIONS(RequestedModuleObject, columnNumber, IdentFilter)
static const JSPropertySpec ShellRequestedModuleObjectWrapper_accessors[] = {
JS_PSG("moduleRequest",
ShellRequestedModuleObjectWrapper_moduleRequestGetter, 0),
JS_PSG("lineNumber", ShellRequestedModuleObjectWrapper_lineNumberGetter, 0),
JS_PSG("columnNumber", ShellRequestedModuleObjectWrapper_columnNumberGetter,
0),
JS_PS_END};
DEFINE_GETTER_FUNCTIONS(ModuleObject, namespace, IdentFilter)
DEFINE_GETTER_FUNCTIONS(ModuleObject, status, IdentFilter)
DEFINE_GETTER_FUNCTIONS(ModuleObject, evaluationError, IdentFilter)
DEFINE_GETTER_FUNCTIONS(ModuleObject, requestedModules,
ArrayFilter<ShellRequestedModuleObjectWrapper>)
DEFINE_GETTER_FUNCTIONS(ModuleObject, importEntries,
ArrayFilter<ShellImportEntryObjectWrapper>)
DEFINE_GETTER_FUNCTIONS(ModuleObject, localExportEntries,
ArrayFilter<ShellExportEntryObjectWrapper>)
DEFINE_GETTER_FUNCTIONS(ModuleObject, indirectExportEntries,
ArrayFilter<ShellExportEntryObjectWrapper>)
DEFINE_GETTER_FUNCTIONS(ModuleObject, starExportEntries,
ArrayFilter<ShellExportEntryObjectWrapper>)
DEFINE_GETTER_FUNCTIONS(ModuleObject, dfsIndex, IdentFilter)
DEFINE_GETTER_FUNCTIONS(ModuleObject, dfsAncestorIndex, IdentFilter)
DEFINE_GETTER_FUNCTIONS(ModuleObject, async, IdentFilter)
DEFINE_GETTER_FUNCTIONS(ModuleObject, topLevelCapability, IdentFilter)
DEFINE_GETTER_FUNCTIONS(ModuleObject, asyncEvaluatingPostOrder, IdentFilter)
DEFINE_GETTER_FUNCTIONS(ModuleObject, asyncParentModules,
ListToArrayFilter<ShellModuleObjectWrapper>)
DEFINE_GETTER_FUNCTIONS(ModuleObject, pendingAsyncDependencies, IdentFilter)
static const JSPropertySpec ShellModuleObjectWrapper_accessors[] = {
JS_PSG("namespace", ShellModuleObjectWrapper_namespaceGetter, 0),
JS_PSG("status", ShellModuleObjectWrapper_statusGetter, 0),
JS_PSG("evaluationError", ShellModuleObjectWrapper_evaluationErrorGetter,
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_dfsIndexGetter, 0),
JS_PSG("dfsAncestorIndex", ShellModuleObjectWrapper_dfsAncestorIndexGetter,
0),
JS_PSG("async", ShellModuleObjectWrapper_asyncGetter, 0),
JS_PSG("topLevelCapability",
ShellModuleObjectWrapper_topLevelCapabilityGetter, 0),
JS_PSG("asyncEvaluatingPostOrder",
ShellModuleObjectWrapper_asyncEvaluatingPostOrderGetter, 0),
JS_PSG("asyncParentModules",
ShellModuleObjectWrapper_asyncParentModulesGetter, 0),
JS_PSG("pendingAsyncDependencies",
ShellModuleObjectWrapper_pendingAsyncDependenciesGetter, 0),
JS_PS_END};
#undef DEFINE_GETTER_FUNCTIONS
template <class T, size_t ARGC, typename CheckArgsT, typename FilterT>
bool ShellModuleWrapperMethod(JSContext* cx, const JS::CallArgs& args,
const char* prop, CheckArgsT checkArgs,
FilterT filter) {
using TargetT = typename T::Target;
JS::Rooted<TargetT*> obj(cx, args.thisv().toObject().as<T>().get());
JS::Rooted<JS::Value> methodVal(cx);
if (!JS_GetProperty(cx, obj, prop, &methodVal)) {
return false;
}
if (!methodVal.isObject() || !methodVal.toObject().is<JSFunction>()) {
JS_ReportErrorASCII(cx, "method property is not function");
return false;
}
JS::Rooted<JSFunction*> method(cx, &methodVal.toObject().as<JSFunction>());
if (!checkArgs(cx, args)) {
return false;
}
FixedInvokeArgs<ARGC> invokeArgs(cx);
for (size_t i = 0; i < ARGC; i++) {
invokeArgs[i].set(args.get(i));
}
JS::Rooted<JS::Value> rval(cx);
if (!JS::Call(cx, obj, method, invokeArgs, &rval)) {
return false;
}
JS::Rooted<JS::Value> filtered(cx);
if (!filter(cx, rval, &filtered)) {
return false;
}
args.rval().set(filtered);
return true;
}
#define DEFINE_METHOD_FUNCTIONS(CLASS, METHOD, ARGC, CHECK, FILTER) \
static bool Shell##CLASS##Wrapper_##METHOD##_impl( \
JSContext* cx, const JS::CallArgs& args) { \
return ShellModuleWrapperMethod<Shell##CLASS##Wrapper, ARGC>( \
cx, args, #METHOD, CHECK, FILTER); \
} \
static bool Shell##CLASS##Wrapper##_##METHOD(JSContext* cx, unsigned argc, \
Value* vp) { \
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \
return CallNonGenericMethod<Is##Shell##CLASS##Wrapper, \
Shell##CLASS##Wrapper_##METHOD##_impl>(cx, \
args); \
}
static bool CheckNoArgs(JSContext* cx, const CallArgs& args) { return true; }
DEFINE_METHOD_FUNCTIONS(ModuleObject, declarationInstantiation, 0, CheckNoArgs,
IdentFilter)
DEFINE_METHOD_FUNCTIONS(ModuleObject, evaluation, 0, CheckNoArgs, IdentFilter)
static const JSFunctionSpec ShellModuleObjectWrapper_functions[] = {
JS_FN("declarationInstantiation",
ShellModuleObjectWrapper_declarationInstantiation, 0, 0),
JS_FN("evaluation", ShellModuleObjectWrapper_evaluation, 0, 0), JS_FS_END};
#undef DEFINE_METHOD_FUNCTIONS
#define DEFINE_CREATE(CLASS, ACCESSORS, FUNCTIONS) \
/* static */ \
Shell##CLASS##Wrapper* Shell##CLASS##Wrapper::create( \
JSContext* cx, JS::Handle<CLASS*> obj) { \
JS::Rooted<JSObject*> wrapper(cx, JS_NewObject(cx, &class_)); \
if (!wrapper) { \
return nullptr; \
} \
if (!DefinePropertiesAndFunctions(cx, wrapper, ACCESSORS, FUNCTIONS)) { \
return nullptr; \
} \
wrapper->as<Shell##CLASS##Wrapper>().initReservedSlot(TargetSlot, \
ObjectValue(*obj)); \
return &wrapper->as<Shell##CLASS##Wrapper>(); \
}
DEFINE_CREATE(ModuleRequestObject, ShellModuleRequestObjectWrapper_accessors,
nullptr)
DEFINE_CREATE(ImportEntryObject, ShellImportEntryObjectWrapper_accessors,
nullptr)
DEFINE_CREATE(ExportEntryObject, ShellExportEntryObjectWrapper_accessors,
nullptr)
DEFINE_CREATE(RequestedModuleObject,
ShellRequestedModuleObjectWrapper_accessors, nullptr)
DEFINE_CREATE(ModuleObject, ShellModuleObjectWrapper_accessors,
ShellModuleObjectWrapper_functions)
#undef DEFINE_CREATE

View File

@ -0,0 +1,36 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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/. */
#ifndef shell_ShellModuleObjectWrapper_h
#define shell_ShellModuleObjectWrapper_h
#include "builtin/ModuleObject.h" // js::ModuleObject
#include "js/Class.h" // JSClass
#include "js/RootingAPI.h" // JS::Handle
#include "vm/NativeObject.h" // js::NativeObject
namespace js {
namespace shell {
// ModuleObject's accessors and methods are only for internal usage in
// js/src/builtin/Module.js, and they don't check arguments types.
//
// To use ModuleObject in tests, add a wrapper that checks arguments types.
class ShellModuleObjectWrapper : public js::NativeObject {
public:
using Target = ModuleObject;
enum ModuleSlot { TargetSlot = 0, SlotCount };
static const JSClass class_;
static ShellModuleObjectWrapper* create(JSContext* cx,
JS::Handle<ModuleObject*> moduleObj);
ModuleObject* get();
};
} // namespace shell
} // namespace js
#endif /* shell_ShellModuleObjectWrapper_h */

View File

@ -150,6 +150,7 @@
#include "shell/jsoptparse.h"
#include "shell/jsshell.h"
#include "shell/OSObject.h"
#include "shell/ShellModuleObjectWrapper.h"
#include "shell/WasmTesting.h"
#include "threading/ConditionVariable.h"
#include "threading/ExclusiveData.h"
@ -3779,8 +3780,11 @@ static bool DisassembleToSprinter(JSContext* cx, unsigned argc, Value* vp,
RootedFunction fun(cx);
RootedScript script(cx);
RootedValue value(cx, p.argv[i]);
if (value.isObject() && value.toObject().is<ModuleObject>()) {
script = value.toObject().as<ModuleObject>().maybeScript();
if (value.isObject() && value.toObject().is<ShellModuleObjectWrapper>()) {
script = value.toObject()
.as<ShellModuleObjectWrapper>()
.get()
->maybeScript();
} else {
script = TestingFunctionArgumentToScript(cx, value, fun.address());
}
@ -4029,8 +4033,9 @@ static bool CacheIRHealthReport(JSContext* cx, unsigned argc, Value* vp) {
} else {
RootedValue value(cx, args.get(0));
if (value.isObject() && value.toObject().is<ModuleObject>()) {
script = value.toObject().as<ModuleObject>().maybeScript();
if (value.isObject() && value.toObject().is<ShellModuleObjectWrapper>()) {
script =
value.toObject().as<ShellModuleObjectWrapper>().get()->maybeScript();
} else {
script = TestingFunctionArgumentToScript(cx, args.get(0));
}
@ -5520,7 +5525,12 @@ static bool ParseModule(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
args.rval().setObject(*module);
Rooted<ShellModuleObjectWrapper*> wrapper(
cx, ShellModuleObjectWrapper::create(cx, module.as<ModuleObject>()));
if (!wrapper) {
return false;
}
args.rval().setObject(*wrapper);
return true;
}
@ -5605,13 +5615,15 @@ static bool CodeModule(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) {
if (!args[0].isObject() ||
!args[0].toObject().is<ShellModuleObjectWrapper>()) {
const char* typeName = InformalValueTypeName(args[0]);
JS_ReportErrorASCII(cx, "expected module object, got %s", typeName);
return false;
}
RootedModuleObject modObject(cx, &args[0].toObject().as<ModuleObject>());
RootedModuleObject modObject(
cx, args[0].toObject().as<ShellModuleObjectWrapper>().get());
if (modObject->status() >= MODULE_STATUS_LINKING) {
JS_ReportErrorASCII(cx, "cannot encode module after instantiation.");
return false;
@ -5660,7 +5672,12 @@ static bool DecodeModule(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
args.rval().setObject(*modObject);
Rooted<ShellModuleObjectWrapper*> wrapper(
cx, ShellModuleObjectWrapper::create(cx, modObject));
if (!wrapper) {
return false;
}
args.rval().setObject(*wrapper);
return true;
}
@ -5676,14 +5693,16 @@ static bool RegisterModule(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
if (!args[1].isObject() || !args[1].toObject().is<ModuleObject>()) {
const char* typeName = InformalValueTypeName(args[0]);
if (!args[1].isObject() ||
!args[1].toObject().is<ShellModuleObjectWrapper>()) {
const char* typeName = InformalValueTypeName(args[1]);
JS_ReportErrorASCII(cx, "expected module, got %s", typeName);
return false;
}
ShellContext* sc = GetShellContext(cx);
RootedModuleObject module(cx, &args[1].toObject().as<ModuleObject>());
RootedModuleObject module(
cx, args[1].toObject().as<ShellModuleObjectWrapper>().get());
RootedAtom specifier(cx, AtomizeString(cx, args[0].toString()));
if (!specifier) {
@ -5699,7 +5718,12 @@ static bool RegisterModule(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
args.rval().setObject(*module);
Rooted<ShellModuleObjectWrapper*> wrapper(
cx, ShellModuleObjectWrapper::create(cx, module));
if (!wrapper) {
return false;
}
args.rval().setObject(*wrapper);
return true;
}
@ -5719,12 +5743,15 @@ static bool GetModuleEnvironmentNames(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) {
JS_ReportErrorASCII(cx, "First argument should be a ModuleObject");
if (!args[0].isObject() ||
!args[0].toObject().is<ShellModuleObjectWrapper>()) {
JS_ReportErrorASCII(cx,
"First argument should be a ShellModuleObjectWrapper");
return false;
}
RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
RootedModuleObject module(
cx, args[0].toObject().as<ShellModuleObjectWrapper>().get());
if (module->hadEvaluationError()) {
JS_ReportErrorASCII(cx, "Module environment unavailable");
return false;
@ -5762,8 +5789,10 @@ static bool GetModuleEnvironmentValue(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<ModuleObject>()) {
JS_ReportErrorASCII(cx, "First argument should be a ModuleObject");
if (!args[0].isObject() ||
!args[0].toObject().is<ShellModuleObjectWrapper>()) {
JS_ReportErrorASCII(cx,
"First argument should be a ShellModuleObjectWrapper");
return false;
}
@ -5772,7 +5801,8 @@ static bool GetModuleEnvironmentValue(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
RootedModuleObject module(
cx, args[0].toObject().as<ShellModuleObjectWrapper>().get());
if (module->hadEvaluationError()) {
JS_ReportErrorASCII(cx, "Module environment unavailable");
return false;
@ -6565,7 +6595,12 @@ static bool FinishOffThreadModule(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
args.rval().setObject(*module);
Rooted<ShellModuleObjectWrapper*> wrapper(
cx, ShellModuleObjectWrapper::create(cx, module.as<ModuleObject>()));
if (!wrapper) {
return false;
}
args.rval().setObject(*wrapper);
return true;
}
@ -8462,8 +8497,9 @@ static bool DumpScopeChain(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
if (!args[0].isObject() || !(args[0].toObject().is<JSFunction>() ||
args[0].toObject().is<ModuleObject>())) {
if (!args[0].isObject() ||
!(args[0].toObject().is<JSFunction>() ||
args[0].toObject().is<ShellModuleObjectWrapper>())) {
ReportUsageErrorASCII(
cx, callee, "Argument must be an interpreted function or a module");
return false;
@ -8484,7 +8520,7 @@ static bool DumpScopeChain(JSContext* cx, unsigned argc, Value* vp) {
return false;
}
} else {
script = obj->as<ModuleObject>().maybeScript();
script = obj->as<ShellModuleObjectWrapper>().get()->maybeScript();
if (!script) {
JS_ReportErrorASCII(cx, "module does not have an associated script");
return false;
@ -9550,17 +9586,17 @@ static const JSFunctionSpecWithHelp shell_functions[] = {
JS_FN_HELP("parseModule", ParseModule, 1, 0,
"parseModule(code)",
" Parses source text as a module and returns a Module object."),
" Parses source text as a module and returns a ModuleObject wrapper object."),
JS_FN_HELP("codeModule", CodeModule, 1, 0,
"codeModule(module)",
" Takes an uninstantiated ModuleObject and returns a XDR bytecode\n"
" Takes an uninstantiated ModuleObject wrapper and returns a XDR bytecode\n"
" representation of that ModuleObject."),
JS_FN_HELP("decodeModule", DecodeModule, 1, 0,
"decodeModule(code)",
" Takes a XDR bytecode representation of an uninstantiated ModuleObject and\n"
" returns a ModuleObject."),
" Takes a XDR bytecode representation of an uninstantiated\n"
" ModuleObject and returns a ModuleObject wrapper."),
JS_FN_HELP("registerModule", RegisterModule, 2, 0,
"registerModule(specifier, module)",

View File

@ -21,6 +21,7 @@ UNIFIED_SOURCES += [
"jsshell.cpp",
"ModuleLoader.cpp",
"OSObject.cpp",
"ShellModuleObjectWrapper.cpp",
"WasmTesting.cpp",
]