In D220213 we tried to change `Symbol.unscopables` so that it's only supported on syntactic `with` environments (for performance reasons). This failed some WPT tests for event handlers because they use non-syntactic with-environments but have to support `Symbol.unscopables`. This patch adds a `JS::EnvironmentChain` class and uses it instead of an object vector for some JS APIs. This class also lets the embedder specify whether `Symbol.unscopables` must be supported. The `evaluate` shell function now has an option for this too. Differential Revision: https://phabricator.services.mozilla.com/D226139
343 lines
10 KiB
C++
343 lines
10 KiB
C++
/* -*- 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 <string.h>
|
|
|
|
#include "jsapi.h"
|
|
|
|
#include "frontend/CompilationStencil.h"
|
|
#include "js/CompilationAndEvaluation.h"
|
|
#include "js/EnvironmentChain.h" // JS::EnvironmentChain
|
|
#include "js/experimental/CompileScript.h"
|
|
#include "js/experimental/JSStencil.h"
|
|
#include "js/Modules.h"
|
|
#include "js/PropertyAndElement.h" // JS_GetProperty, JS_HasOwnProperty, JS_SetProperty
|
|
#include "js/Transcoding.h"
|
|
#include "jsapi-tests/tests.h"
|
|
#include "vm/HelperThreads.h" // js::RunPendingSourceCompressions
|
|
#include "vm/Monitor.h" // js::Monitor, js::AutoLockMonitor
|
|
|
|
BEGIN_TEST(testStencil_Basic) {
|
|
const char* chars =
|
|
"function f() { return 42; }"
|
|
"f();";
|
|
auto result = basic_test<char, mozilla::Utf8Unit>(chars);
|
|
CHECK(result);
|
|
|
|
const char16_t* chars16 =
|
|
u"function f() { return 42; }"
|
|
u"f();";
|
|
auto result16 = basic_test<char16_t, char16_t>(chars16);
|
|
CHECK(result16);
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT, typename SourceT>
|
|
bool basic_test(const CharT* chars) {
|
|
size_t length = std::char_traits<CharT>::length(chars);
|
|
|
|
JS::SourceText<SourceT> srcBuf;
|
|
CHECK(srcBuf.init(cx, chars, length, JS::SourceOwnership::Borrowed));
|
|
|
|
JS::CompileOptions options(cx);
|
|
RefPtr<JS::Stencil> stencil =
|
|
JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
|
|
CHECK(stencil);
|
|
|
|
JS::InstantiateOptions instantiateOptions(options);
|
|
JS::RootedScript script(
|
|
cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
|
|
CHECK(script);
|
|
|
|
JS::RootedValue rval(cx);
|
|
CHECK(JS_ExecuteScript(cx, script, &rval));
|
|
CHECK(rval.isNumber() && rval.toNumber() == 42);
|
|
|
|
return true;
|
|
}
|
|
END_TEST(testStencil_Basic)
|
|
|
|
BEGIN_TEST(testStencil_Module) {
|
|
const char* chars =
|
|
"export function f() { return 42; }"
|
|
"globalThis.x = f();";
|
|
auto result = basic_test<char, mozilla::Utf8Unit>(chars);
|
|
CHECK(result);
|
|
|
|
const char16_t* chars16 =
|
|
u"export function f() { return 42; }"
|
|
u"globalThis.x = f();";
|
|
auto result16 = basic_test<char16_t, char16_t>(chars16);
|
|
CHECK(result16);
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename CharT, typename SourceT>
|
|
bool basic_test(const CharT* chars) {
|
|
size_t length = std::char_traits<CharT>::length(chars);
|
|
|
|
JS::SourceText<SourceT> srcBuf;
|
|
CHECK(srcBuf.init(cx, chars, length, JS::SourceOwnership::Borrowed));
|
|
|
|
JS::CompileOptions options(cx);
|
|
options.setFile("testStencil_Module");
|
|
|
|
RefPtr<JS::Stencil> stencil =
|
|
JS::CompileModuleScriptToStencil(cx, options, srcBuf);
|
|
CHECK(stencil);
|
|
|
|
JS::InstantiateOptions instantiateOptions(options);
|
|
JS::RootedObject moduleObject(
|
|
cx, JS::InstantiateModuleStencil(cx, instantiateOptions, stencil));
|
|
CHECK(moduleObject);
|
|
|
|
// Link and evaluate the module graph. The link step used to be call
|
|
// "instantiate" but is unrelated to the concept in Stencil with same name.
|
|
JS::RootedValue rval(cx);
|
|
CHECK(JS::ModuleLink(cx, moduleObject));
|
|
CHECK(JS::ModuleEvaluate(cx, moduleObject, &rval));
|
|
CHECK(!rval.isUndefined());
|
|
|
|
js::RunJobs(cx);
|
|
CHECK(JS_GetProperty(cx, global, "x", &rval));
|
|
CHECK(rval.isNumber() && rval.toNumber() == 42);
|
|
|
|
return true;
|
|
}
|
|
END_TEST(testStencil_Module)
|
|
|
|
BEGIN_TEST(testStencil_NonSyntactic) {
|
|
const char* chars =
|
|
"function f() { return x; }"
|
|
"f();";
|
|
|
|
JS::SourceText<mozilla::Utf8Unit> srcBuf;
|
|
CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
|
|
|
|
JS::CompileOptions options(cx);
|
|
options.setNonSyntacticScope(true);
|
|
|
|
RefPtr<JS::Stencil> stencil =
|
|
JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
|
|
CHECK(stencil);
|
|
|
|
JS::InstantiateOptions instantiateOptions(options);
|
|
JS::RootedScript script(
|
|
cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
|
|
CHECK(script);
|
|
|
|
JS::RootedObject obj(cx, JS_NewPlainObject(cx));
|
|
JS::RootedValue val(cx, JS::Int32Value(42));
|
|
CHECK(obj);
|
|
CHECK(JS_SetProperty(cx, obj, "x", val));
|
|
|
|
JS::EnvironmentChain envChain(cx, JS::SupportUnscopables::No);
|
|
CHECK(envChain.append(obj));
|
|
|
|
JS::RootedValue rval(cx);
|
|
CHECK(JS_ExecuteScript(cx, envChain, script, &rval));
|
|
CHECK(rval.isNumber() && rval.toNumber() == 42);
|
|
|
|
return true;
|
|
}
|
|
END_TEST(testStencil_NonSyntactic)
|
|
|
|
BEGIN_TEST(testStencil_MultiGlobal) {
|
|
const char* chars =
|
|
"/**************************************/"
|
|
"/**************************************/"
|
|
"/**************************************/"
|
|
"/**************************************/"
|
|
"/**************************************/"
|
|
"/**************************************/"
|
|
"function f() { return 42; }"
|
|
"f();";
|
|
|
|
JS::SourceText<mozilla::Utf8Unit> srcBuf;
|
|
CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
|
|
|
|
JS::CompileOptions options(cx);
|
|
RefPtr<JS::Stencil> stencil =
|
|
JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
|
|
CHECK(stencil);
|
|
|
|
CHECK(RunInNewGlobal(cx, stencil));
|
|
CHECK(RunInNewGlobal(cx, stencil));
|
|
CHECK(RunInNewGlobal(cx, stencil));
|
|
|
|
// Start any pending SourceCompressionTasks now to confirm nothing fell apart
|
|
// when using a JS::Stencil multiple times.
|
|
CHECK(strlen(chars) > js::ScriptSource::MinimumCompressibleLength);
|
|
js::RunPendingSourceCompressions(cx->runtime());
|
|
|
|
return true;
|
|
}
|
|
bool RunInNewGlobal(JSContext* cx, RefPtr<JS::Stencil> stencil) {
|
|
JS::RootedObject otherGlobal(cx, createGlobal());
|
|
CHECK(otherGlobal);
|
|
|
|
JSAutoRealm ar(cx, otherGlobal);
|
|
|
|
JS::InstantiateOptions instantiateOptions;
|
|
JS::RootedScript script(
|
|
cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
|
|
CHECK(script);
|
|
|
|
JS::RootedValue rval(cx);
|
|
CHECK(JS_ExecuteScript(cx, script, &rval));
|
|
CHECK(rval.isNumber() && rval.toNumber() == 42);
|
|
|
|
return true;
|
|
}
|
|
END_TEST(testStencil_MultiGlobal)
|
|
|
|
BEGIN_TEST(testStencil_Transcode) {
|
|
JS::SetProcessBuildIdOp(TestGetBuildId);
|
|
|
|
JS::TranscodeBuffer buffer;
|
|
|
|
{
|
|
const char* chars =
|
|
"function f() { return 42; }"
|
|
"f();";
|
|
|
|
JS::SourceText<mozilla::Utf8Unit> srcBuf;
|
|
CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
|
|
|
|
JS::CompileOptions options(cx);
|
|
RefPtr<JS::Stencil> stencil =
|
|
JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
|
|
CHECK(stencil);
|
|
|
|
// Encode Stencil to XDR
|
|
JS::TranscodeResult res = JS::EncodeStencil(cx, stencil, buffer);
|
|
CHECK(res == JS::TranscodeResult::Ok);
|
|
CHECK(!buffer.empty());
|
|
|
|
// Instantiate and Run
|
|
JS::InstantiateOptions instantiateOptions(options);
|
|
JS::RootedScript script(
|
|
cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
|
|
JS::RootedValue rval(cx);
|
|
CHECK(script);
|
|
CHECK(JS_ExecuteScript(cx, script, &rval));
|
|
CHECK(rval.isNumber() && rval.toNumber() == 42);
|
|
}
|
|
|
|
// Create a new global
|
|
CHECK(createGlobal());
|
|
JSAutoRealm ar(cx, global);
|
|
|
|
// Confirm it doesn't have the old code
|
|
bool found = false;
|
|
CHECK(JS_HasOwnProperty(cx, global, "f", &found));
|
|
CHECK(!found);
|
|
|
|
{
|
|
// Decode the stencil into new range
|
|
RefPtr<JS::Stencil> stencil;
|
|
|
|
{
|
|
JS::DecodeOptions decodeOptions;
|
|
JS::TranscodeRange range(buffer.begin(), buffer.length());
|
|
JS::TranscodeResult res =
|
|
JS::DecodeStencil(cx, decodeOptions, range, getter_AddRefs(stencil));
|
|
CHECK(res == JS::TranscodeResult::Ok);
|
|
}
|
|
|
|
{
|
|
JS::FrontendContext* fc = JS::NewFrontendContext();
|
|
JS::DecodeOptions decodeOptions;
|
|
JS::TranscodeRange range(buffer.begin(), buffer.length());
|
|
JS::TranscodeResult res =
|
|
JS::DecodeStencil(fc, decodeOptions, range, getter_AddRefs(stencil));
|
|
CHECK(res == JS::TranscodeResult::Ok);
|
|
JS::DestroyFrontendContext(fc);
|
|
}
|
|
|
|
// Delete the buffer to verify that the decoded stencil has no dependency
|
|
// to the buffer.
|
|
memset(buffer.begin(), 0, buffer.length());
|
|
buffer.clear();
|
|
|
|
// Instantiate and Run
|
|
JS::InstantiateOptions instantiateOptions;
|
|
JS::RootedScript script(
|
|
cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
|
|
stencil = nullptr;
|
|
JS::RootedValue rval(cx);
|
|
CHECK(script);
|
|
CHECK(JS_ExecuteScript(cx, script, &rval));
|
|
CHECK(rval.isNumber() && rval.toNumber() == 42);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
static bool TestGetBuildId(JS::BuildIdCharVector* buildId) {
|
|
const char buildid[] = "testXDR";
|
|
return buildId->append(buildid, sizeof(buildid));
|
|
}
|
|
END_TEST(testStencil_Transcode)
|
|
|
|
BEGIN_TEST(testStencil_TranscodeBorrowing) {
|
|
JS::SetProcessBuildIdOp(TestGetBuildId);
|
|
|
|
JS::TranscodeBuffer buffer;
|
|
|
|
{
|
|
const char* chars =
|
|
"function f() { return 42; }"
|
|
"f();";
|
|
|
|
JS::SourceText<mozilla::Utf8Unit> srcBuf;
|
|
CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
|
|
|
|
JS::CompileOptions options(cx);
|
|
RefPtr<JS::Stencil> stencil =
|
|
JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
|
|
CHECK(stencil);
|
|
|
|
// Encode Stencil to XDR
|
|
JS::TranscodeResult res = JS::EncodeStencil(cx, stencil, buffer);
|
|
CHECK(res == JS::TranscodeResult::Ok);
|
|
CHECK(!buffer.empty());
|
|
}
|
|
|
|
JS::RootedScript script(cx);
|
|
{
|
|
JS::TranscodeRange range(buffer.begin(), buffer.length());
|
|
JS::DecodeOptions decodeOptions;
|
|
decodeOptions.borrowBuffer = true;
|
|
RefPtr<JS::Stencil> stencil;
|
|
JS::TranscodeResult res =
|
|
JS::DecodeStencil(cx, decodeOptions, range, getter_AddRefs(stencil));
|
|
CHECK(res == JS::TranscodeResult::Ok);
|
|
|
|
JS::InstantiateOptions instantiateOptions;
|
|
script = JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil);
|
|
CHECK(script);
|
|
}
|
|
|
|
// Delete the buffer to verify that the instantiated script has no dependency
|
|
// to the buffer.
|
|
memset(buffer.begin(), 0, buffer.length());
|
|
buffer.clear();
|
|
|
|
JS::RootedValue rval(cx);
|
|
CHECK(JS_ExecuteScript(cx, script, &rval));
|
|
CHECK(rval.isNumber() && rval.toNumber() == 42);
|
|
|
|
return true;
|
|
}
|
|
static bool TestGetBuildId(JS::BuildIdCharVector* buildId) {
|
|
const char buildid[] = "testXDR";
|
|
return buildId->append(buildid, sizeof(buildid));
|
|
}
|
|
END_TEST(testStencil_TranscodeBorrowing)
|