Bug 1767525 - Implement (under a flag) ShadowRealm constructor and evaluate function. r=jandem

Shell only, without HostInitializeShadowRealm, nor importValue

Differential Revision: https://phabricator.services.mozilla.com/D146236
This commit is contained in:
Matthew Gaudet 2022-06-28 22:04:16 +00:00
parent b15ea105d5
commit 3c9ac3d334
17 changed files with 923 additions and 2 deletions

View File

@ -90,6 +90,7 @@
REAL(Set, OCLASP(Set)) \
REAL(DataView, OCLASP(DataView)) \
REAL(Symbol, OCLASP(Symbol)) \
REAL(ShadowRealm, OCLASP(ShadowRealm)) \
REAL(SharedArrayBuffer, OCLASP(SharedArrayBuffer)) \
REAL_IF_INTL(Intl, CLASP(Intl)) \
REAL_IF_INTL(Collator, OCLASP(Collator)) \

View File

@ -214,6 +214,12 @@ class JS_PUBLIC_API RealmCreationOptions {
return *this;
}
bool getShadowRealmsEnabled() const { return shadowRealms_; }
RealmCreationOptions& setShadowRealmsEnabled(bool flag) {
shadowRealms_ = flag;
return *this;
}
#ifdef NIGHTLY_BUILD
bool getArrayGroupingEnabled() const { return arrayGrouping_; }
RealmCreationOptions& setArrayGroupingEnabled(bool flag) {
@ -274,6 +280,7 @@ class JS_PUBLIC_API RealmCreationOptions {
bool propertyErrorMessageFix_ = false;
bool iteratorHelpers_ = false;
bool arrayFindLast_ = false;
bool shadowRealms_ = false;
#ifdef NIGHTLY_BUILD
bool arrayGrouping_ = true;
#endif

View File

@ -0,0 +1,50 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 js_ShadowReamCallbacks_h
#define js_ShadowReamCallbacks_h
#include "jstypes.h"
#include "js/RootingAPI.h"
#include "js/TypeDecls.h"
struct JS_PUBLIC_API JSContext;
namespace JS {
class RealmOptions;
using GlobalInitializeCallback = bool (*)(JSContext*, JS::Handle<JSObject*>);
// Install the HostInitializeShadowRealm callback that will be invoked when
// creating a shadow realm.
//
// The callback will be passed the realm's global object, so that it is possible
// for the embedding to make any host-determined manipulations to the global,
// such as installing interfaces or helpers that should exist even within
// ShadowRealms. (For example, in the web platform, WebIDL with the
// [Exposed=*] attribute should be installed within a shadow realm.)
extern JS_PUBLIC_API void SetShadowRealmInitializeGlobalCallback(
JSContext* cx, GlobalInitializeCallback callback);
using GlobalCreationCallback =
JSObject* (*)(JSContext* cx, JS::RealmOptions& creationOptions,
JSPrincipals* principals,
JS::Handle<JSObject*> enclosingGlobal);
// Create the Global object for a ShadowRealm.
//
// This isn't directly specified, however at least in Gecko, in order to
// correctly implement HostInitializeShadowRealm, there are requirements
// placed on the global for the ShadowRealm.
//
// This callback should return a Global object compatible with the
// callback installed by SetShadowRealmInitializeGlobalCallback
extern JS_PUBLIC_API void SetShadowRealmGlobalCreationCallback(
JSContext* cx, GlobalCreationCallback callback);
} // namespace JS
#endif // js_ShadowReamCallbacks_h

View File

@ -80,12 +80,12 @@ class MOZ_STACK_CLASS JS_PUBLIC_API AutoStableStringChars final {
mozilla::Range<const Latin1Char> latin1Range() const {
MOZ_ASSERT(state_ == Latin1);
return mozilla::Range<const Latin1Char>(latin1Chars_, GetStringLength(s_));
return mozilla::Range<const Latin1Char>(latin1Chars_, length());
}
mozilla::Range<const char16_t> twoByteRange() const {
MOZ_ASSERT(state_ == TwoByte);
return mozilla::Range<const char16_t>(twoByteChars_, GetStringLength(s_));
return mozilla::Range<const char16_t>(twoByteChars_, length());
}
/* If we own the chars, transfer ownership to the caller. */
@ -99,6 +99,8 @@ class MOZ_STACK_CLASS JS_PUBLIC_API AutoStableStringChars final {
return true;
}
size_t length() const { return GetStringLength(s_); }
private:
AutoStableStringChars(const AutoStableStringChars& other) = delete;
void operator=(const AutoStableStringChars& other) = delete;

View File

@ -178,6 +178,7 @@ MSG_DEF(JSMSG_TOO_MANY_ARGUMENTS, 0, JSEXN_RANGEERR, "too many arguments pr
MSG_DEF(JSMSG_CSP_BLOCKED_EVAL, 0, JSEXN_EVALERR, "call to eval() blocked by CSP")
MSG_DEF(JSMSG_CSP_BLOCKED_FUNCTION, 0, JSEXN_EVALERR, "call to Function() blocked by CSP")
MSG_DEF(JSMSG_CSP_BLOCKED_WASM, 1, JSEXN_WASMCOMPILEERROR, "call to {0}() blocked by CSP")
MSG_DEF(JSMSG_CSP_BLOCKED_SHADOWREALM, 0, JSEXN_EVALERR, "call to ShadowRealm.prototype.evaluate blocked by CSP")
// Wrappers
MSG_DEF(JSMSG_ACCESSOR_DEF_DENIED, 1, JSEXN_ERR, "Permission denied to define accessor property {0}")
@ -781,4 +782,17 @@ MSG_DEF(JSMSG_BAD_TUPLE_INDEX, 0, JSEXN_RANGEERR, "index out of range for
MSG_DEF(JSMSG_BAD_TUPLE_OBJECT, 0, JSEXN_TYPEERR, "value of TupleObject must be a Tuple")
MSG_DEF(JSMSG_RECORD_TUPLE_TO_NUMBER, 0, JSEXN_TYPEERR, "can't convert Record or Tuple to number")
// Shadow Realms
MSG_DEF(JSMSG_NOT_SHADOW_REALM, 0, JSEXN_TYPEERR, "Object is not a ShadowRealm")
MSG_DEF(JSMSG_SHADOW_REALM_EVALUATE_NOT_STRING, 0, JSEXN_TYPEERR, "a ShadowRealm can only evaluate a string")
MSG_DEF(JSMSG_SHADOW_REALM_INVALID_RETURN, 0, JSEXN_TYPEERR, "return value not primitive or callable")
MSG_DEF(JSMSG_SHADOW_REALM_WRAP_FAILURE, 0, JSEXN_TYPEERR, "unable to wrap callable return object")
MSG_DEF(JSMSG_SHADOW_REALM_EVALUATE_FAILURE, 0, JSEXN_TYPEERR, "evaluate failed")
MSG_DEF(JSMSG_SHADOW_REALM_WRAPPED_EXECUTION_FAILURE, 0, JSEXN_TYPEERR, "wrapped function threw")
MSG_DEF(JSMSG_SHADOW_REALM_GENERIC_SYNTAX, 0, JSEXN_SYNTAXERR, "Couldn't compile string")
MSG_DEF(JSMSG_SHADOW_REALM_IMPORTVALUE_NOT_IMPLEMENTED, 0, JSEXN_TYPEERR, "importValue not yet implemented")
//clang-format on

View File

@ -0,0 +1,389 @@
/* -*- 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/. */
#include "builtin/ShadowRealm.h"
#include "mozilla/Assertions.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "builtin/ModuleObject.h"
#include "builtin/Promise.h"
#include "builtin/WrappedFunctionObject.h"
#include "frontend/BytecodeCompilation.h"
#include "js/CompilationAndEvaluation.h"
#include "js/ErrorReport.h"
#include "js/Exception.h"
#include "js/GlobalObject.h"
#include "js/Principals.h"
#include "js/Promise.h"
#include "js/PropertyAndElement.h"
#include "js/PropertyDescriptor.h"
#include "js/ShadowRealmCallbacks.h"
#include "js/SourceText.h"
#include "js/StableStringChars.h"
#include "js/TypeDecls.h"
#include "js/Wrapper.h"
#include "vm/GlobalObject.h"
#include "vm/ObjectOperations.h"
#include "builtin/HandlerFunction-inl.h"
#include "vm/JSObject-inl.h"
using namespace js;
using JS::AutoStableStringChars;
using JS::CompileOptions;
using JS::SourceOwnership;
using JS::SourceText;
static JSObject* DefaultNewShadowRealmGlobal(JSContext* cx,
JS::RealmOptions& options,
JSPrincipals* principals,
HandleObject unused) {
static const JSClass shadowRealmGlobal = {
"ShadowRealmGlobal", JSCLASS_GLOBAL_FLAGS, &JS::DefaultGlobalClassOps};
return JS_NewGlobalObject(cx, &shadowRealmGlobal, principals,
JS::FireOnNewGlobalHook, options);
}
// https://tc39.es/proposal-shadowrealm/#sec-shadowrealm-constructor
/*static*/
bool ShadowRealmObject::construct(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1. If NewTarget is undefined, throw a TypeError exception.
if (!args.isConstructing()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_NOT_CONSTRUCTOR, "ShadowRealm");
return false;
}
// Step 2. Let O be ? OrdinaryCreateFromConstructor(NewTarget,
// "%ShadowRealm.prototype%", « [[ShadowRealm]], [[ExecutionContext]] »).
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ShadowRealm,
&proto)) {
return false;
}
Rooted<ShadowRealmObject*> shadowRealmObj(
cx, NewObjectWithClassProto<ShadowRealmObject>(cx, proto));
if (!shadowRealmObj) {
return false;
}
// Instead of managing Realms, spidermonkey associates a realm with a global
// object, and so we will manage and store a global.
// Step 3. Let realmRec be CreateRealm().
// Initially steal creation options from current realm:
JS::RealmOptions options(cx->realm()->creationOptions(),
cx->realm()->behaviors());
// We don't want to have to deal with CCWs in addition to
// WrappedFunctionObjects.
options.creationOptions().setExistingCompartment(cx->compartment());
JS::GlobalCreationCallback newGlobal =
cx->runtime()->getShadowRealmGlobalCreationCallback();
// If an embedding didn't provide a callback to initialize the global,
// use the basic default one.
if (!newGlobal) {
newGlobal = DefaultNewShadowRealmGlobal;
}
// Our shadow realm inherits the principals of the current realm,
// but is otherwise constrained.
JSPrincipals* principals = JS::GetRealmPrincipals(cx->realm());
// Steps 5-11: In SpiderMonkey these fall under the aegis of the global
// creation. It's worth noting that the newGlobal callback
// needs to respect the SetRealmGlobalObject call below, which
// sets the global to
// OrdinaryObjectCreate(intrinsics.[[%Object.prototype%]]).
//
// Step 5. Let context be a new execution context.
// Step 6. Set the Function of context to null.
// Step 7. Set the Realm of context to realmRec.
// Step 8. Set the ScriptOrModule of context to null.
// Step 9. Set O.[[ExecutionContext]] to context.
// Step 10. Perform ? SetRealmGlobalObject(realmRec, undefined, undefined).
// Step 11. Perform ? SetDefaultGlobalBindings(O.[[ShadowRealm]]).
RootedObject global(cx, newGlobal(cx, options, principals, cx->global()));
if (!global) {
return false;
}
// Make sure the new global hook obeyed our request in the
// creation options to have a same compartment global.
MOZ_RELEASE_ASSERT(global->compartment() == cx->compartment());
// Step 4. Set O.[[ShadowRealm]] to realmRec.
shadowRealmObj->initFixedSlot(GlobalSlot, ObjectValue(*global));
// Step 12. Perform ? HostInitializeShadowRealm(O.[[ShadowRealm]]).
JS::GlobalInitializeCallback hostInitializeShadowRealm =
cx->runtime()->getShadowRealmInitializeGlobalCallback();
if (hostInitializeShadowRealm) {
if (!hostInitializeShadowRealm(cx, global)) {
return false;
}
}
// Step 13. Return O.
args.rval().setObject(*shadowRealmObj);
return true;
}
// https://tc39.es/proposal-shadowrealm/#sec-validateshadowrealmobject
// (slightly modified into a cast operator too)
static ShadowRealmObject* ValidateShadowRealmObject(JSContext* cx,
HandleObject O) {
// Step 1. Perform ? RequireInternalSlot(O, [[ShadowRealm]]).
// Step 2. Perform ? RequireInternalSlot(O, [[ExecutionContext]]).
RootedObject maybeUnwrappedO(cx, O);
if (IsCrossCompartmentWrapper(O)) {
maybeUnwrappedO = CheckedUnwrapDynamic(O, cx);
// Unwrapping failed; security wrapper denied.
if (!maybeUnwrappedO) {
return nullptr;
}
}
if (!maybeUnwrappedO->is<ShadowRealmObject>()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_NOT_SHADOW_REALM);
return nullptr;
}
return &maybeUnwrappedO->as<ShadowRealmObject>();
}
// PerformShadowRealmEval ( sourceText: a String, callerRealm: a Realm Record,
// evalRealm: a Realm Record, )
//
// https://tc39.es/proposal-shadowrealm/#sec-performshadowrealmeval
static bool PerformShadowRealmEval(JSContext* cx, HandleString sourceText,
Realm* callerRealm, Realm* evalRealm,
MutableHandleValue rval) {
MOZ_ASSERT(callerRealm != evalRealm);
// Step 1. Perform ? HostEnsureCanCompileStrings(callerRealm, evalRealm).
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::JS, sourceText)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_CSP_BLOCKED_SHADOWREALM);
return false;
}
// Need to compile the script into the realm we will execute into.
//
// We hoist the error handling out however to ensure that errors
// are thrown from the correct realm.
bool compileSuccess = false;
bool evalSuccess = false;
do {
Rooted<GlobalObject*> evalRealmGlobal(cx, evalRealm->maybeGlobal());
AutoRealm ar(cx, evalRealmGlobal);
// Step 2. Perform the following substeps in an implementation-defined
// order, possibly interleaving parsing and error detection:
// a. Let script be ParseText(! StringToCodePoints(sourceText), Script).
// b. If script is a List of errors, throw a SyntaxError exception.
// c. If script Contains ScriptBody is false, return undefined.
// d. Let body be the ScriptBody of script.
// e. If body Contains NewTarget is true, throw a SyntaxError exception.
// f. If body Contains SuperProperty is true, throw a SyntaxError
// exception. g. If body Contains SuperCall is true, throw a SyntaxError
// exception.
AutoStableStringChars linearChars(cx);
if (!linearChars.initTwoByte(cx, sourceText)) {
return false;
}
SourceText<char16_t> srcBuf;
const char16_t* chars = linearChars.twoByteRange().begin().get();
SourceOwnership ownership = linearChars.maybeGiveOwnershipToCaller()
? SourceOwnership::TakeOwnership
: SourceOwnership::Borrowed;
if (!srcBuf.init(cx, chars, linearChars.length(), ownership)) {
return false;
}
// Lets propagate some information into the compilation here.
//
// We may need to censor the stacks eventually, see
// https://bugzilla.mozilla.org/show_bug.cgi?id=1770017
RootedScript callerScript(cx);
const char* filename;
unsigned lineno;
uint32_t pcOffset;
bool mutedErrors;
DescribeScriptedCallerForCompilation(cx, &callerScript, &filename, &lineno,
&pcOffset, &mutedErrors);
CompileOptions options(cx);
options.setIsRunOnce(true)
.setNoScriptRval(false)
.setMutedErrors(mutedErrors)
.setFileAndLine(filename, lineno);
Rooted<Scope*> enclosing(cx, &evalRealmGlobal->emptyGlobalScope());
RootedScript script(
cx, frontend::CompileEvalScript(cx, options, srcBuf, enclosing,
evalRealmGlobal));
compileSuccess = !!script;
if (!compileSuccess) {
break;
}
// Step 3. Let strictEval be IsStrict of script.
// Step 4. Let runningContext be the running execution context.
// Step 5. Let lexEnv be NewDeclarativeEnvironment(evalRealm.[[GlobalEnv]]).
// Step 6. Let varEnv be evalRealm.[[GlobalEnv]].
// Step 7. If strictEval is true, set varEnv to lexEnv.
// Step 8. If runningContext is not already suspended, suspend
// runningContext. Step 9. Let evalContext be a new ECMAScript code
// execution context. Step 10. Set evalContext's Function to null. Step 11.
// Set evalContext's Realm to evalRealm. Step 12. Set evalContext's
// ScriptOrModule to null. Step 13. Set evalContext's VariableEnvironment to
// varEnv. Step 14. Set evalContext's LexicalEnvironment to lexEnv. Step 15.
// Push evalContext onto the execution context stack; evalContext is
// now the running execution context.
// Step 16. Let result be EvalDeclarationInstantiation(body, varEnv,
// lexEnv, null, strictEval).
// Step 17. If result.[[Type]] is normal, then
// a. Set result to the result of evaluating body.
// Step 18. If result.[[Type]] is normal and result.[[Value]] is empty, then
// a. Set result to NormalCompletion(undefined).
// Step 19. Suspend evalContext and remove it from the execution context
// stack.
// Step 20. Resume the context that is now on the top of the execution
// context stack as the running execution context.
RootedObject environment(cx, &evalRealmGlobal->lexicalEnvironment());
evalSuccess = ExecuteKernel(cx, script, environment,
/* evalInFrame = */ NullFramePtr(), rval);
} while (false); // AutoRealm
if (!compileSuccess) {
// MG:XXX: figure out how to extract the syntax error information for
// re-throw here (See DebuggerObject::getErrorColumnNumber ?)
//
// https://bugzilla.mozilla.org/show_bug.cgi?id=1769848
// The SyntaxError here needs to come from the calling global, so has to
// happen outside the AutoRealm above.
JS_ClearPendingException(cx);
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_SHADOW_REALM_GENERIC_SYNTAX);
return false;
}
if (!evalSuccess) {
// Step 21. If result.[[Type]] is not normal, throw a TypeError
// exception.
//
// The type error here needs to come from the calling global, so has to
// happen outside the AutoRealm above.
// MG:XXX: Figure out how to extract the error message and include in
// message of TypeError (if possible): See discussion in
// https://github.com/tc39/proposal-shadowrealm/issues/353 for some
// potential pitfalls (i.e. what if the error has a getter on the message
// property?)
//
// I imagine we could do something like GetPropertyPure, and have a nice
// error message if we *don't* have anything to worry about.
//
// https://bugzilla.mozilla.org/show_bug.cgi?id=1769849
JS_ClearPendingException(cx);
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_SHADOW_REALM_EVALUATE_FAILURE);
return false;
}
// Step 22. Return ? GetWrappedValue(callerRealm, result.[[Value]]).
return GetWrappedValue(cx, callerRealm, rval, rval);
}
// ShadowRealm.prototype.evaluate ( sourceText )
// https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.evaluate
static bool ShadowRealm_evaluate(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1. Let O be this value (implicit ToObject)
RootedObject obj(cx, ToObject(cx, args.thisv()));
if (!obj) {
return false;
}
// Step 2. Perform ? ValidateShadowRealmObject(O)
Rooted<ShadowRealmObject*> shadowRealm(cx,
ValidateShadowRealmObject(cx, obj));
if (!shadowRealm) {
return false;
}
// Step 3. If Type(sourceText) is not String, throw a TypeError exception.
if (!args.get(0).isString()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_SHADOW_REALM_EVALUATE_NOT_STRING);
return false;
}
RootedString sourceText(cx, args.get(0).toString());
// Step 4. Let callerRealm be the current Realm Record.
Realm* callerRealm = cx->realm();
// Step 5. Let evalRealm be O.[[ShadowRealm]].
Realm* evalRealm = shadowRealm->getShadowRealm();
// Step 6. Return ? PerformShadowRealmEval(sourceText, callerRealm,
// evalRealm).
return PerformShadowRealmEval(cx, sourceText, callerRealm, evalRealm,
args.rval());
}
// ShadowRealm.prototype.importValue ( specifier, exportName )
// https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.importvalue
static bool ShadowRealm_importValue(JSContext* cx, unsigned argc, Value* vp) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_SHADOW_REALM_IMPORTVALUE_NOT_IMPLEMENTED);
return false;
}
static const JSFunctionSpec shadowrealm_methods[] = {
JS_FN("evaluate", ShadowRealm_evaluate, 1, 0),
JS_FN("importValue", ShadowRealm_importValue, 2, 0), JS_FS_END};
static const JSPropertySpec shadowrealm_properties[] = {
JS_STRING_SYM_PS(toStringTag, "ShadowRealm", JSPROP_READONLY), JS_PS_END};
static const ClassSpec ShadowRealmObjectClassSpec = {
GenericCreateConstructor<ShadowRealmObject::construct, 0,
gc::AllocKind::FUNCTION>,
GenericCreatePrototype<ShadowRealmObject>,
nullptr, // Static methods
nullptr, // Static properties
shadowrealm_methods, // Methods
shadowrealm_properties // Properties
};
const JSClass ShadowRealmObject::class_ = {
"ShadowRealm",
JSCLASS_HAS_CACHED_PROTO(JSProto_ShadowRealm) |
JSCLASS_HAS_RESERVED_SLOTS(ShadowRealmObject::SlotCount),
JS_NULL_CLASS_OPS, &ShadowRealmObjectClassSpec};
const JSClass ShadowRealmObject::protoClass_ = {
"ShadowRealm.prototype", JSCLASS_HAS_CACHED_PROTO(JSProto_ShadowRealm),
JS_NULL_CLASS_OPS, &ShadowRealmObjectClassSpec};

View File

@ -0,0 +1,37 @@
/* -*- 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 builtin_ShadowRealm_h
#define builtin_ShadowRealm_h
#include "js/Wrapper.h"
#include "vm/GlobalObject.h"
#include "vm/NativeObject.h"
namespace js {
class ShadowRealmObject : public NativeObject {
public:
static const JSClass class_;
static const JSClass protoClass_;
enum { GlobalSlot, SlotCount };
static bool construct(JSContext* cx, unsigned argc, Value* vp);
Realm* getShadowRealm() {
MOZ_ASSERT(getWrappedGlobal());
return getWrappedGlobal()->nonCCWRealm();
}
JSObject* getWrappedGlobal() const {
return &getFixedSlot(GlobalSlot).toObject();
}
};
} // namespace js
#endif

View File

@ -0,0 +1,298 @@
/* -*- 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/. */
#include "builtin/WrappedFunctionObject.h"
#include "jsapi.h"
#include "js/CallAndConstruct.h"
#include "js/Class.h"
#include "js/ErrorReport.h"
#include "js/Exception.h"
#include "js/friend/DumpFunctions.h"
#include "js/TypeDecls.h"
#include "js/Value.h"
#include "js/Wrapper.h"
#include "vm/JSFunction.h"
#include "vm/ObjectOperations.h"
#include "vm/JSObject-inl.h"
using namespace js;
using namespace JS;
// GetWrappedValue ( callerRealm: a Realm Record, value: unknown )
bool js::GetWrappedValue(JSContext* cx, Realm* callerRealm, HandleValue value,
MutableHandleValue res) {
// Step 2. Return value (Reordered)
if (!value.isObject()) {
res.set(value);
return true;
}
// Step 1. If Type(value) is Object, then
// a. If IsCallable(value) is false, throw a TypeError exception.
RootedObject objectVal(cx, &value.toObject());
if (!IsCallable(objectVal)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_SHADOW_REALM_INVALID_RETURN);
return false;
}
// b. Return ? WrappedFunctionCreate(callerRealm, value).
return WrappedFunctionCreate(cx, callerRealm, objectVal, res);
}
// [[Call]]
// https://tc39.es/proposal-shadowrealm/#sec-wrapped-function-exotic-objects-call-thisargument-argumentslist
static bool WrappedFunction_Call(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject callee(cx, &args.callee());
MOZ_ASSERT(callee->is<WrappedFunctionObject>());
Handle<WrappedFunctionObject*> F = callee.as<WrappedFunctionObject>();
// 1. Let target be F.[[WrappedTargetFunction]].
RootedObject target(cx, F->getTargetFunction());
// 2. Assert: IsCallable(target) is true.
MOZ_ASSERT(IsCallable(ObjectValue(*target)));
// 3. Let targetRealm be ? GetFunctionRealm(target).
Rooted<Realm*> targetRealm(cx, GetFunctionRealm(cx, target));
if (!targetRealm) {
return false;
}
// 4. Let callerRealm be ? GetFunctionRealm(F).
Rooted<Realm*> callerRealm(cx, GetFunctionRealm(cx, F));
if (!callerRealm) {
return false;
}
// 5. NOTE: Any exception objects produced after this point are associated
// with callerRealm.
RootedValue result(cx);
{
RootedObject global(cx, JS::GetRealmGlobalOrNull(callerRealm));
MOZ_RELEASE_ASSERT(
global, "global is null; executing in a realm that's being GC'd?");
AutoRealm ar(cx, global);
// https://searchfox.org/mozilla-central/source/js/public/CallAndConstruct.h#57-73
// 6. Let wrappedArgs be a new empty List.
RootedValueVector wrappedArgs(cx);
// 7. For each element arg of argumentsList, do
// a. Let wrappedValue be ? GetWrappedValue(targetRealm, arg).
// b. Append wrappedValue to wrappedArgs.
RootedValue element(cx);
for (size_t i = 0; i < args.length(); i++) {
element = args.get(i);
if (!GetWrappedValue(cx, targetRealm, element, &element)) {
return false;
}
if (!wrappedArgs.append(element)) {
return false;
}
}
// 8. Let wrappedThisArgument to ? GetWrappedValue(targetRealm,
// thisArgument).
RootedValue wrappedThisArgument(cx);
if (!GetWrappedValue(cx, targetRealm, args.thisv(), &wrappedThisArgument)) {
return false;
}
// 9. Let result be the Completion Record of Call(target,
// wrappedThisArgument, wrappedArgs).
if (!JS::Call(cx, wrappedThisArgument, target, wrappedArgs, &result)) {
// 11. Else (reordered);
// a. Throw a TypeError exception.
JS_ClearPendingException(cx);
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_SHADOW_REALM_WRAPPED_EXECUTION_FAILURE);
return false;
}
// 10. If result.[[Type]] is normal or result.[[Type]] is return, then
// a. Return ? GetWrappedValue(callerRealm, result.[[Value]]).
if (!GetWrappedValue(cx, callerRealm, result, args.rval())) {
return false;
}
}
return true;
}
static bool CopyNameAndLength(JSContext* cx, HandleObject F,
HandleObject Target, char* prefix = nullptr,
unsigned argCount = 0) {
// 1. If argCount is undefined, then set argCount to 0 (implicit)
// 2. Let L be 0.
double L = 0;
// 3. Let targetHasLength be ? HasOwnProperty(Target, "length").
RootedId length(cx, NameToId(cx->names().length));
RootedId name(cx, NameToId(cx->names().name));
// Try to avoid invoking the resolve hook.
if (Target->is<JSFunction>() &&
!Target->as<JSFunction>().hasResolvedLength()) {
RootedValue targetLen(cx);
if (!JSFunction::getUnresolvedLength(cx, Target.as<JSFunction>(),
&targetLen)) {
return false;
}
L = std::max(0.0, targetLen.toNumber() - argCount);
} else {
bool targetHasLength;
if (!HasOwnProperty(cx, Target, length, &targetHasLength)) {
return false;
}
// https://searchfox.org/mozilla-central/source/js/src/vm/JSFunction.cpp#1298
// 4. If targetHasLength is true, then
if (targetHasLength) {
// a. Let targetLen be ? Get(Target, "length").
RootedValue targetLen(cx);
if (!GetProperty(cx, Target, Target, length, &targetLen)) {
return false;
}
// b. If Type(targetLen) is Number, then
// i. If targetLen is +∞𝔽, set L to +∞.
// ii. Else if targetLen is -∞𝔽, set L to 0.
// iii. Else,
// 1. Let targetLenAsInt be ! ToIntegerOrInfinity(targetLen).
// 2. Assert: targetLenAsInt is finite.
// 3. Set L to max(targetLenAsInt - argCount, 0).
if (targetLen.isNumber()) {
L = std::max(0.0, JS::ToInteger(targetLen.toNumber()) - argCount);
}
}
}
// 5. Perform ! SetFunctionLength(F, L).
RootedValue rootedL(cx, DoubleValue(L));
if (!JS_DefinePropertyById(cx, F, length, rootedL, JSPROP_READONLY)) {
return false;
}
// 6. Let targetName be ? Get(Target, "name").
RootedValue targetName(cx);
if (!GetProperty(cx, Target, Target, cx->names().name, &targetName)) {
return false;
}
// 7. If Type(targetName) is not String, set targetName to the empty String.
if (!targetName.isString()) {
targetName = StringValue(cx->runtime()->emptyString);
}
RootedString targetString(cx, targetName.toString());
RootedId targetNameId(cx);
if (!JS_StringToId(cx, targetString, &targetNameId)) {
return false;
}
// 8. Perform ! SetFunctionName(F, targetName, prefix).
// (inlined and specialized from js::SetFunctionName)
Rooted<JSAtom*> funName(cx, IdToFunctionName(cx, targetNameId));
if (!funName) {
return false;
}
RootedValue rootedFunName(cx, StringValue(funName));
return JS_DefinePropertyById(cx, F, name, rootedFunName, JSPROP_READONLY);
}
static const JSClassOps classOps = {
nullptr, // addProperty
nullptr, // delProperty
nullptr, // enumerate
nullptr, // newEnumerate
nullptr, // resolve
nullptr, // mayResolve
nullptr, // finalize
WrappedFunction_Call, // call
nullptr, // construct
nullptr, // trace
};
const JSClass WrappedFunctionObject::class_ = {
"WrappedFunctionObject",
JSCLASS_HAS_CACHED_PROTO(
JSProto_Function) | // MG:XXX: Is this going to correctly set the
// prototype for me?
JSCLASS_HAS_RESERVED_SLOTS(WrappedFunctionObject::SlotCount),
&classOps};
JSObject* GetRealmFunctionPrototype(JSContext* cx, Realm* realm) {
CHECK_THREAD(cx);
Rooted<GlobalObject*> global(cx, realm->maybeGlobal());
MOZ_RELEASE_ASSERT(global);
return GlobalObject::getOrCreateFunctionPrototype(cx, global);
}
// WrappedFunctionCreate ( callerRealm: a Realm Record, Target: a function
// object)
bool js::WrappedFunctionCreate(JSContext* cx, Realm* callerRealm,
HandleObject target, MutableHandleValue res) {
// Ensure that the function object has the correct realm by allocating it
// into that realm.
RootedObject global(cx, JS::GetRealmGlobalOrNull(callerRealm));
MOZ_RELEASE_ASSERT(global,
"global is null; executing in a realm that's being GC'd?");
AutoRealm ar(cx, global);
MOZ_ASSERT(target);
// Target *could* be a function from another realm
RootedObject maybeWrappedTarget(cx, target);
if (!JS_WrapObject(cx, &maybeWrappedTarget)) {
return false;
}
// 1. Let internalSlotsList be the internal slots listed in Table 2, plus
// [[Prototype]] and [[Extensible]].
// 2. Let wrapped be ! MakeBasicObject(internalSlotsList).
// 3. Set wrapped.[[Prototype]] to
// callerRealm.[[Intrinsics]].[[%Function.prototype%]].
RootedObject functionPrototype(cx,
GetRealmFunctionPrototype(cx, callerRealm));
MOZ_ASSERT(cx->compartment() == functionPrototype->compartment());
Rooted<WrappedFunctionObject*> wrapped(
cx,
NewObjectWithGivenProto<WrappedFunctionObject>(cx, functionPrototype));
if (!wrapped) {
return false;
}
// 4. Set wrapped.[[Call]] as described in 2.1 (implicit in JSClass call
// hook)
// 5. Set wrapped.[[WrappedTargetFunction]] to Target.
wrapped->setTargetFunction(*maybeWrappedTarget);
// 6. Set wrapped.[[Realm]] to callerRealm. (implicitly the realm of
// wrapped, which we assured with the AutoRealm
MOZ_ASSERT(wrapped->realm() == callerRealm);
// 7. Let result be CopyNameAndLength(wrapped, Target).
if (!CopyNameAndLength(cx, wrapped, maybeWrappedTarget)) {
// 8. If result is an Abrupt Completion, throw a TypeError exception.
JS_ClearPendingException(cx);
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_SHADOW_REALM_WRAP_FAILURE);
return false;
}
// 9. Return wrapped.
res.set(ObjectValue(*wrapped));
return true;
}

View File

@ -0,0 +1,43 @@
/* -*- 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 builtin_WrappedFunctionObject_h
#define builtin_WrappedFunctionObject_h
#include "js/Value.h"
#include "vm/NativeObject.h"
namespace js {
// Implementing Wrapped Function Exotic Objects from the ShadowRealms proposal
// https://tc39.es/proposal-shadowrealm/#sec-wrapped-function-exotic-objects
//
// These are produced as callables are passed across ShadowRealm boundaries,
// preventing functions from piercing the shadow realm barrier.
class WrappedFunctionObject : public NativeObject {
public:
static const JSClass class_;
enum { WrappedTargetFunctionSlot, SlotCount };
JSObject* getTargetFunction() const {
return &getFixedSlot(WrappedTargetFunctionSlot).toObject();
}
void setTargetFunction(JSObject& obj) {
setFixedSlot(WrappedTargetFunctionSlot, ObjectValue(obj));
}
};
bool WrappedFunctionCreate(JSContext* cx, Realm* callerRealm,
HandleObject target, MutableHandleValue res);
bool GetWrappedValue(JSContext* cx, Realm* callerRealm, HandleValue value,
MutableHandleValue res);
} // namespace js
#endif

View File

@ -4557,6 +4557,16 @@ JS_PUBLIC_API void JS::SetOutOfMemoryCallback(JSContext* cx,
cx->runtime()->oomCallbackData = data;
}
JS_PUBLIC_API void JS::SetShadowRealmInitializeGlobalCallback(
JSContext* cx, JS::GlobalInitializeCallback callback) {
cx->runtime()->shadowRealmInitializeGlobalCallback = callback;
}
JS_PUBLIC_API void JS::SetShadowRealmGlobalCreationCallback(
JSContext* cx, JS::GlobalCreationCallback callback) {
cx->runtime()->shadowRealmGlobalCreationCallback = callback;
}
JS::FirstSubsumedFrame::FirstSubsumedFrame(
JSContext* cx, bool ignoreSelfHostedFrames /* = true */)
: JS::FirstSubsumedFrame(cx, cx->realm()->principals(),

View File

@ -196,6 +196,7 @@ EXPORTS.js += [
"../public/SavedFrameAPI.h",
"../public/ScalarType.h",
"../public/ScriptPrivate.h",
"../public/ShadowRealmCallbacks.h",
"../public/SharedArrayBuffer.h",
"../public/SliceBudget.h",
"../public/SourceText.h",
@ -314,6 +315,7 @@ UNIFIED_SOURCES += [
"builtin/Promise.cpp",
"builtin/Reflect.cpp",
"builtin/ReflectParse.cpp",
"builtin/ShadowRealm.cpp",
"builtin/String.cpp",
"builtin/Symbol.cpp",
"builtin/TestingFunctions.cpp",
@ -321,6 +323,7 @@ UNIFIED_SOURCES += [
"builtin/WeakMapObject.cpp",
"builtin/WeakRefObject.cpp",
"builtin/WeakSetObject.cpp",
"builtin/WrappedFunctionObject.cpp",
"ds/Bitmap.cpp",
"ds/LifoAlloc.cpp",
"jsapi.cpp",

View File

@ -140,6 +140,7 @@
#include "js/MemoryFunctions.h"
#include "js/Modules.h" // JS::GetModulePrivate, JS::SetModule{DynamicImport,Metadata,Resolve}Hook, JS::SetModulePrivate
#include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetReservedSlot, JS::SetReservedSlot
#include "js/Principals.h"
#include "js/Printf.h"
#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineFunction, JS_DefineFunctions, JS_DefineProperties, JS_DefineProperty, JS_GetElement, JS_GetProperty, JS_GetPropertyById, JS_HasProperty, JS_SetElement, JS_SetProperty, JS_SetPropertyById
#include "js/PropertySpec.h"
@ -626,6 +627,7 @@ bool shell::enableWeakRefs = false;
bool shell::enableToSource = false;
bool shell::enablePropertyErrorMessageFix = false;
bool shell::enableIteratorHelpers = false;
bool shell::enableShadowRealms = false;
#ifdef NIGHTLY_BUILD
bool shell::enableArrayGrouping = true;
#endif
@ -4246,6 +4248,7 @@ static void SetStandardRealmOptions(JS::RealmOptions& options) {
.setToSourceEnabled(enableToSource)
.setPropertyErrorMessageFixEnabled(enablePropertyErrorMessageFix)
.setIteratorHelpersEnabled(enableIteratorHelpers)
.setShadowRealmsEnabled(enableShadowRealms)
#ifdef NIGHTLY_BUILD
.setArrayGroupingEnabled(enableArrayGrouping)
#endif
@ -11039,6 +11042,7 @@ static bool SetContextOptions(JSContext* cx, const OptionParser& op) {
enablePropertyErrorMessageFix =
!op.getBoolOption("disable-property-error-message-fix");
enableIteratorHelpers = op.getBoolOption("enable-iterator-helpers");
enableShadowRealms = op.getBoolOption("enable-shadow-realms");
#ifdef NIGHTLY_BUILD
enableArrayGrouping = op.getBoolOption("enable-array-grouping");
#endif
@ -12074,6 +12078,7 @@ int main(int argc, char** argv) {
"property of null or undefined") ||
!op.addBoolOption('\0', "enable-iterator-helpers",
"Enable iterator helpers") ||
!op.addBoolOption('\0', "enable-shadow-realms", "Enable ShadowRealms") ||
!op.addBoolOption('\0', "enable-array-grouping",
"Enable Array Grouping") ||
!op.addBoolOption('\0', "enable-array-find-last",
@ -12636,6 +12641,7 @@ int main(int argc, char** argv) {
// Waiting is allowed on the shell's main thread, for now.
JS_SetFutexCanWait(cx);
JS::SetWarningReporter(cx, WarningReporter);
if (!SetContextOptions(cx, op)) {
return 1;
}

View File

@ -134,6 +134,7 @@ extern bool enableWeakRefs;
extern bool enableToSource;
extern bool enablePropertyErrorMessageFix;
extern bool enableIteratorHelpers;
extern bool enableShadowRealms;
extern bool enableArrayGrouping;
extern bool enablePrivateClassFields;
extern bool enablePrivateClassMethods;

View File

@ -0,0 +1,33 @@
// |reftest| shell-option(--enable-shadow-realms) skip-if(!xulRuntime.shell)
var g = newGlobal({ newCompartment: true });
var sr = g.evaluate(`new ShadowRealm()`);
// sr should be a CCW to a ShadowRealm.
ShadowRealm.prototype.evaluate.call(sr, "var x = 10");
assertEq(sr.evaluate("x"), 10);
// wrappedf should *not* be a CCW, because we're using this realm's ShadowRealm.prototype.evaluate,
// and so the active realm when invoking will be this current realm.
//
// However, the target function (wrappedf's f) comes from another compartment, and will have to be a CCW.
var wrappedf = ShadowRealm.prototype.evaluate.call(sr, "function f() { return 10; }; f");
assertEq(wrappedf(), 10);
var evaluate_from_other_realm = g.evaluate('ShadowRealm.prototype.evaluate');
// wrappedb should be a CCW, since the callee of the .call comes from the other
// compartment.
var wrappedb = evaluate_from_other_realm.call(sr, "function b() { return 12; }; b");
assertEq(wrappedb(), 12);
nukeAllCCWs()
// This throws, but the dead object message is lost and replaced with the wrapped function
// object message.
assertThrowsInstanceOf(() => wrappedf(), TypeError);
assertThrowsInstanceOf(() => wrappedb(), TypeError);
if (typeof reportCompare === 'function')
reportCompare(true, true);

View File

@ -31,6 +31,7 @@
#include "builtin/Object.h"
#include "builtin/RegExp.h"
#include "builtin/SelfHostingDefines.h"
#include "builtin/ShadowRealm.h"
#include "builtin/Stream.h"
#include "builtin/streams/QueueingStrategies.h" // js::{ByteLength,Count}QueueingStrategy
#include "builtin/streams/ReadableStream.h" // js::ReadableStream
@ -209,6 +210,9 @@ bool GlobalObject::skipDeselectedConstructor(JSContext* cx, JSProtoKey key) {
case JSProto_AsyncIterator:
return !cx->realm()->creationOptions().getIteratorHelpersEnabled();
case JSProto_ShadowRealm:
return !cx->realm()->creationOptions().getShadowRealmsEnabled();
default:
MOZ_CRASH("unexpected JSProtoKey");
}

View File

@ -13,6 +13,7 @@
#include "jsfriendapi.h"
#include "builtin/WrappedFunctionObject.h"
#include "debugger/DebugAPI.h"
#include "debugger/Debugger.h"
#include "gc/Policy.h"
@ -777,6 +778,12 @@ JS_PUBLIC_API Realm* JS::GetFunctionRealm(JSContext* cx, HandleObject objArg) {
continue;
}
// WrappedFunctionObjects also have a [[Realm]] internal slot,
// which is the nonCCWRealm by construction.
if (obj->is<WrappedFunctionObject>()) {
return obj->nonCCWRealm();
}
// Step 4.
if (IsScriptedProxy(obj)) {
// Steps 4.a-b.

View File

@ -48,6 +48,7 @@
# include "js/Proxy.h" // For AutoEnterPolicy
#endif
#include "js/ScriptPrivate.h"
#include "js/ShadowRealmCallbacks.h"
#include "js/Stack.h"
#include "js/Stream.h" // JS::AbortSignalIsAborted
#include "js/StreamConsumer.h"
@ -1030,6 +1031,21 @@ struct JSRuntime {
};
ErrorInterceptionSupport errorInterception;
#endif // defined(NIGHTLY_BUILD)
public:
JS::GlobalInitializeCallback getShadowRealmInitializeGlobalCallback() {
return shadowRealmInitializeGlobalCallback;
}
JS::GlobalCreationCallback getShadowRealmGlobalCreationCallback() {
return shadowRealmGlobalCreationCallback;
}
js::MainThreadData<JS::GlobalInitializeCallback>
shadowRealmInitializeGlobalCallback;
js::MainThreadData<JS::GlobalCreationCallback>
shadowRealmGlobalCreationCallback;
};
// Context for sending telemetry to the embedder from any thread, main or