author | arthur.iakab <aiakab@mozilla.com> |
Thu, 15 Nov 2018 11:54:15 +0200 | |
changeset 446535 | dca9c72df68bb59692118595dea65b78cde5b371 |
parent 446479 | 48720735b142b5f96db418e8ee7c4882f22805d9 (current diff) |
parent 446534 | a10cbfd5f4110f2e5f96095408aebd1f8acd1b87 (diff) |
child 446553 | 9a65ee9355e8b014a8ff5fdb28e0f500f0aa652f |
child 446575 | efcfebe333b6be493f9a75957c4c063e103ed3c2 |
push id | 35042 |
push user | aiakab@mozilla.com |
push date | Thu, 15 Nov 2018 09:54:38 +0000 |
treeherder | mozilla-central@dca9c72df68b [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 65.0a1 |
first release with | nightly linux32
dca9c72df68b
/
65.0a1
/
20181115100051
/
files
nightly linux64
dca9c72df68b
/
65.0a1
/
20181115100051
/
files
nightly mac
dca9c72df68b
/
65.0a1
/
20181115100051
/
files
nightly win32
dca9c72df68b
/
65.0a1
/
20181115100051
/
files
nightly win64
dca9c72df68b
/
65.0a1
/
20181115100051
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
65.0a1
/
20181115100051
/
pushlog to previous
nightly linux64
65.0a1
/
20181115100051
/
pushlog to previous
nightly mac
65.0a1
/
20181115100051
/
pushlog to previous
nightly win32
65.0a1
/
20181115100051
/
pushlog to previous
nightly win64
65.0a1
/
20181115100051
/
pushlog to previous
|
--- a/browser/components/extensions/test/browser/browser.ini +++ b/browser/components/extensions/test/browser/browser.ini @@ -1,12 +1,11 @@ [DEFAULT] tags = webextensions in-process-webextensions support-files = head.js [browser_ext_autocompletepopup.js] -[browser_ext_legacy_extension_context_contentscript.js] [browser_ext_windows_allowScriptsToClose.js] [include:browser-common.ini] skip-if = os == 'win' # Windows WebExtensions always run OOP [parent:browser-common.ini]
deleted file mode 100644 --- a/browser/components/extensions/test/browser/browser_ext_legacy_extension_context_contentscript.js +++ /dev/null @@ -1,173 +0,0 @@ -"use strict"; - -const { - LegacyExtensionContext, -} = ChromeUtils.import("resource://gre/modules/LegacyExtensionsUtils.jsm", {}); - -function promiseAddonStartup(extension) { - const {Management} = ChromeUtils.import("resource://gre/modules/Extension.jsm", {}); - - return new Promise((resolve) => { - let listener = (evt, extensionInstance) => { - Management.off("startup", listener); - resolve(extensionInstance); - }; - Management.on("startup", listener); - }); -} - -/** - * This test case ensures that the LegacyExtensionContext can receive a connection - * from a content script and that the received port contains the expected sender - * tab info. - */ -add_task(async function test_legacy_extension_context_contentscript_connection() { - function backgroundScript() { - // Extract the assigned uuid from the background page url and send it - // in a test message. - let uuid = window.location.hostname; - - browser.test.onMessage.addListener(async msg => { - if (msg == "open-test-tab") { - let tab = await browser.tabs.create({url: "http://example.com/"}); - browser.test.sendMessage("get-expected-sender-info", - {uuid, tab}); - } else if (msg == "close-current-tab") { - try { - let [tab] = await browser.tabs.query({active: true}); - await browser.tabs.remove(tab.id); - browser.test.sendMessage("current-tab-closed", true); - } catch (e) { - browser.test.sendMessage("current-tab-closed", false); - } - } - }); - - browser.test.sendMessage("ready"); - } - - function contentScript() { - browser.runtime.sendMessage("webextension -> legacy_extension message", (reply) => { - browser.test.assertEq("legacy_extension -> webextension reply", reply, - "Got the expected reply from the LegacyExtensionContext"); - browser.test.sendMessage("got-reply-message"); - }); - - let port = browser.runtime.connect(); - - port.onMessage.addListener(msg => { - browser.test.assertEq("legacy_extension -> webextension port message", msg, - "Got the expected message from the LegacyExtensionContext"); - port.postMessage("webextension -> legacy_extension port message"); - }); - } - - let extensionData = { - background: `new ${backgroundScript}`, - manifest: { - content_scripts: [ - { - matches: ["http://example.com/*"], - js: ["content-script.js"], - }, - ], - }, - files: { - "content-script.js": `new ${contentScript}`, - }, - }; - - let extension = ExtensionTestUtils.loadExtension(extensionData); - - let waitForExtensionReady = extension.awaitMessage("ready"); - - let waitForExtensionInstance = promiseAddonStartup(extension); - - extension.startup(); - - let extensionInstance = await waitForExtensionInstance; - - // Connect to the target extension.id as an external context - // using the given custom sender info. - let legacyContext = new LegacyExtensionContext(extensionInstance); - - let waitConnectPort = new Promise(resolve => { - let {browser} = legacyContext.api; - browser.runtime.onConnect.addListener(port => { - resolve(port); - }); - }); - - let waitMessage = new Promise(resolve => { - let {browser} = legacyContext.api; - browser.runtime.onMessage.addListener((singleMsg, msgSender, sendReply) => { - sendReply("legacy_extension -> webextension reply"); - resolve({singleMsg, msgSender}); - }); - }); - - is(legacyContext.envType, "legacy_extension", - "LegacyExtensionContext instance has the expected type"); - - ok(legacyContext.api, "Got the API object"); - - await waitForExtensionReady; - - extension.sendMessage("open-test-tab"); - - let {tab} = await extension.awaitMessage("get-expected-sender-info"); - - let {singleMsg, msgSender} = await waitMessage; - is(singleMsg, "webextension -> legacy_extension message", - "Got the expected message"); - ok(msgSender, "Got a message sender object"); - - is(msgSender.id, extension.id, "The sender has the expected id property"); - is(msgSender.url, "http://example.com/", "The sender has the expected url property"); - ok(msgSender.tab, "The sender has a tab property"); - is(msgSender.tab.id, tab.id, "The port sender has the expected tab.id"); - - // Wait confirmation that the reply has been received. - await extension.awaitMessage("got-reply-message"); - - let port = await waitConnectPort; - - ok(port, "Got the Port API object"); - ok(port.sender, "The port has a sender property"); - - is(port.sender.id, extension.id, "The port sender has an id property"); - is(port.sender.url, "http://example.com/", "The port sender has the expected url property"); - ok(port.sender.tab, "The port sender has a tab property"); - is(port.sender.tab.id, tab.id, "The port sender has the expected tab.id"); - - let waitPortMessage = new Promise(resolve => { - port.onMessage.addListener((msg) => { - resolve(msg); - }); - }); - - port.postMessage("legacy_extension -> webextension port message"); - - let msg = await waitPortMessage; - - is(msg, "webextension -> legacy_extension port message", - "LegacyExtensionContext received the expected message from the webextension"); - - let waitForDisconnect = new Promise(resolve => { - port.onDisconnect.addListener(resolve); - }); - - let waitForTestDone = extension.awaitMessage("current-tab-closed"); - - extension.sendMessage("close-current-tab"); - - await waitForDisconnect; - - info("Got the disconnect event on tab closed"); - - let success = await waitForTestDone; - - ok(success, "Test completed successfully"); - - await extension.unload(); -});
--- a/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js +++ b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js @@ -1,26 +1,16 @@ /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; -requestLongerTimeout(2); - -function add_tasks(task) { - add_task(task.bind(null, {embedded: false})); - - add_task(task.bind(null, {embedded: true})); -} - async function loadExtension(options) { let extension = ExtensionTestUtils.loadExtension({ useAddonManager: "temporary", - embedded: options.embedded, - manifest: Object.assign({ "permissions": ["tabs"], }, options.manifest), files: { "options.html": `<!DOCTYPE html> <html> <head> @@ -64,22 +54,22 @@ async function loadExtension(options) { background: options.background, }); await extension.startup(); return extension; } -add_tasks(async function test_inline_options(extraOptions) { - info(`Test options opened inline (${JSON.stringify(extraOptions)})`); +add_task(async function test_inline_options() { + info(`Test options opened inline`); let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/"); - let extension = await loadExtension(Object.assign({}, extraOptions, { + let extension = await loadExtension({ manifest: { applications: {gecko: {id: "inline_options@tests.mozilla.org"}}, "options_ui": { "page": "options.html", }, }, background: async function() { @@ -189,17 +179,17 @@ add_tasks(async function test_inline_opt await browser.tabs.remove(tab.id); browser.test.notifyPass("options-ui"); } catch (error) { browser.test.fail(`Error: ${error} :: ${error.stack}`); browser.test.notifyFail("options-ui"); } }, - })); + }); await Promise.all([ extension.awaitMessage("options-html-inbound-pong"), extension.awaitMessage("options-html-outbound-pong"), extension.awaitMessage("bg-inbound-pong"), extension.awaitMessage("bg-outbound-pong"), ]); @@ -207,22 +197,22 @@ add_tasks(async function test_inline_opt await extension.awaitFinish("options-ui"); await extension.unload(); BrowserTestUtils.removeTab(tab); }); -add_tasks(async function test_tab_options(extraOptions) { - info(`Test options opened in a tab (${JSON.stringify(extraOptions)})`); +add_task(async function test_tab_options() { + info(`Test options opened in a tab`); let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/"); - let extension = await loadExtension(Object.assign({}, extraOptions, { + let extension = await loadExtension({ manifest: { applications: {gecko: {id: "tab_options@tests.mozilla.org"}}, "options_ui": { "page": "options.html", "open_in_tab": true, }, }, @@ -302,39 +292,39 @@ add_tasks(async function test_tab_option await browser.tabs.remove(tab.id); browser.test.notifyPass("options-ui-tab"); } catch (error) { browser.test.fail(`Error: ${error} :: ${error.stack}`); browser.test.notifyFail("options-ui-tab"); } }, - })); + }); await extension.awaitFinish("options-ui-tab"); await extension.unload(); BrowserTestUtils.removeTab(tab); }); -add_tasks(async function test_options_no_manifest(extraOptions) { - info(`Test with no manifest key (${JSON.stringify(extraOptions)})`); +add_task(async function test_options_no_manifest() { + info(`Test with no manifest key`); - let extension = await loadExtension(Object.assign({}, extraOptions, { + let extension = await loadExtension({ manifest: { applications: {gecko: {id: "no_options@tests.mozilla.org"}}, }, async background() { browser.test.log("Try to open options page when not specified in the manifest."); await browser.test.assertRejects( browser.runtime.openOptionsPage(), /No `options_ui` declared/, "Expected error from openOptionsPage()"); browser.test.notifyPass("options-no-manifest"); }, - })); + }); await extension.awaitFinish("options-no-manifest"); await extension.unload(); });
--- a/dom/base/DOMPrefsInternal.h +++ b/dom/base/DOMPrefsInternal.h @@ -23,9 +23,12 @@ DOM_WEBIDL_PREF(dom_testing_structuredcl DOM_WEBIDL_PREF(dom_promise_rejection_events_enabled) DOM_WEBIDL_PREF(dom_push_enabled) DOM_WEBIDL_PREF(gfx_offscreencanvas_enabled) DOM_WEBIDL_PREF(dom_webkitBlink_dirPicker_enabled) DOM_WEBIDL_PREF(dom_netinfo_enabled) DOM_WEBIDL_PREF(dom_fetchObserver_enabled) DOM_WEBIDL_PREF(dom_enable_performance_observer) DOM_WEBIDL_PREF(dom_performance_enable_scheduler_timing) +DOM_WEBIDL_PREF(dom_reporting_enabled) +DOM_WEBIDL_PREF(dom_reporting_testing_enabled) +DOM_WEBIDL_PREF(dom_reporting_featurePolicy_enabled) DOM_WEBIDL_PREF(javascript_options_streams)
--- a/dom/base/nsDOMNavigationTiming.cpp +++ b/dom/base/nsDOMNavigationTiming.cpp @@ -47,16 +47,18 @@ nsDOMNavigationTiming::Clear() mUnloadEnd = TimeStamp(); mLoadEventStart = TimeStamp(); mLoadEventEnd = TimeStamp(); mDOMLoading = TimeStamp(); mDOMInteractive = TimeStamp(); mDOMContentLoadedEventStart = TimeStamp(); mDOMContentLoadedEventEnd = TimeStamp(); mDOMComplete = TimeStamp(); + mContentfulPaint = TimeStamp(); + mNonBlankPaint = TimeStamp(); mDocShellHasBeenActiveSinceNavigationStart = false; } DOMTimeMilliSec nsDOMNavigationTiming::TimeStampToDOM(TimeStamp aStamp) const { if (aStamp.IsNull()) { @@ -303,50 +305,49 @@ MaxWithinWindowBeginningAtMin(const Time #define TTI_WINDOW_SIZE_MS (5 * 1000) void nsDOMNavigationTiming::TTITimeout(nsITimer* aTimer) { // Check TTI: see if it's been 5 seconds since the last Long Task TimeStamp now = TimeStamp::Now(); - MOZ_RELEASE_ASSERT(!mNonBlankPaint.IsNull(), "TTI timeout with no non-blank-paint?"); + MOZ_RELEASE_ASSERT(!mContentfulPaint.IsNull(), "TTI timeout with no contentful-paint?"); nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); TimeStamp lastLongTaskEnded; mainThread->GetLastLongNonIdleTaskEnd(&lastLongTaskEnded); if (!lastLongTaskEnded.IsNull()) { TimeDuration delta = now - lastLongTaskEnded; if (delta.ToMilliseconds() < TTI_WINDOW_SIZE_MS) { // Less than 5 seconds since the last long task. Schedule another check aTimer->InitWithNamedFuncCallback(TTITimeoutCallback, this, TTI_WINDOW_SIZE_MS, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "nsDOMNavigationTiming::TTITimeout"); return; } } - // To correctly implement TTI/TTFI as proposed, we'd need to use - // FirstContentfulPaint (FCP, which we have not yet implemented) instead - // of FirstNonBlankPaing (FNBP) to start at, and not fire it until there - // are no more than 2 network loads. By the proposed definition, without - // that we're closer to TimeToFirstInteractive. + // To correctly implement TTI/TTFI as proposed, we'd need to not + // fire it until there are no more than 2 network loads. By the + // proposed definition, without that we're closer to + // TimeToFirstInteractive. // XXX check number of network loads, and if > 2 mark to check if loads // decreases to 2 (or record that point and let the normal timer here // handle it) // TTI has occurred! TTI is either FCP (if there are no longtasks and no // DCLEnd in the window that starts at FCP), or at the end of the last // Long Task or DOMContentLoadedEnd (whichever is later). if (mTTFI.IsNull()) { mTTFI = MaxWithinWindowBeginningAtMin(lastLongTaskEnded, mDOMContentLoadedEventEnd, TimeDuration::FromMilliseconds(TTI_WINDOW_SIZE_MS)); if (mTTFI.IsNull()) { - mTTFI = mNonBlankPaint; + mTTFI = mContentfulPaint; } } // XXX Implement TTI via check number of network loads, and if > 2 mark // to check if loads decreases to 2 (or record that point and let the // normal timer here handle it) mTTITimer = nullptr; @@ -394,25 +395,16 @@ nsDOMNavigationTiming::NotifyNonBlankPai } nsPrintfCString marker("Non-blank paint after %dms for URL %s, %s", int(elapsed.ToMilliseconds()), spec.get(), mDocShellHasBeenActiveSinceNavigationStart ? "foreground tab" : "this tab was inactive some of the time between navigation start and first non-blank paint"); profiler_add_marker(marker.get()); } #endif - if (!mTTITimer) { - mTTITimer = NS_NewTimer(); - } - - // TTI is first checked 5 seconds after the FCP (non-blank-paint is very close to FCP). - mTTITimer->InitWithNamedFuncCallback(TTITimeoutCallback, this, TTI_WINDOW_SIZE_MS, - nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, - "nsDOMNavigationTiming::TTITimeout"); - if (mDocShellHasBeenActiveSinceNavigationStart) { if (net::nsHttp::IsBeforeLastActiveTabLoadOptimization(mNavigationStart)) { Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_NON_BLANK_PAINT_NETOPT_MS, mNavigationStart, mNonBlankPaint); } else { Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_NON_BLANK_PAINT_NO_NETOPT_MS, mNavigationStart, @@ -421,16 +413,52 @@ nsDOMNavigationTiming::NotifyNonBlankPai Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_NON_BLANK_PAINT_MS, mNavigationStart, mNonBlankPaint); } } void +nsDOMNavigationTiming::NotifyContentfulPaintForRootContentDocument() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mNavigationStart.IsNull()); + + if (!mContentfulPaint.IsNull()) { + return; + } + + mContentfulPaint = TimeStamp::Now(); + +#ifdef MOZ_GECKO_PROFILER + if (profiler_is_active()) { + TimeDuration elapsed = mContentfulPaint - mNavigationStart; + nsAutoCString spec; + if (mLoadedURI) { + mLoadedURI->GetSpec(spec); + } + nsPrintfCString marker("Contentful paint after %dms for URL %s, %s", + int(elapsed.ToMilliseconds()), spec.get(), + mDocShellHasBeenActiveSinceNavigationStart ? "foreground tab" : "this tab was inactive some of the time between navigation start and first non-blank paint"); + profiler_add_marker(marker.get()); + } +#endif + + if (!mTTITimer) { + mTTITimer = NS_NewTimer(); + } + + // TTI is first checked 5 seconds after the FCP (non-blank-paint is very close to FCP). + mTTITimer->InitWithNamedFuncCallback(TTITimeoutCallback, this, TTI_WINDOW_SIZE_MS, + nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, + "nsDOMNavigationTiming::TTITimeout"); +} + +void nsDOMNavigationTiming::NotifyDOMContentFlushedForRootContentDocument() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!mNavigationStart.IsNull()); if (!mDOMContentFlushed.IsNull()) { return; }
--- a/dom/base/nsDOMNavigationTiming.h +++ b/dom/base/nsDOMNavigationTiming.h @@ -92,16 +92,20 @@ public: DOMTimeMilliSec GetLoadEventEnd() const { return TimeStampToDOM(mLoadEventEnd); } DOMTimeMilliSec GetTimeToNonBlankPaint() const { return TimeStampToDOM(mNonBlankPaint); } + DOMTimeMilliSec GetTimeToContentfulPaint() const + { + return TimeStampToDOM(mContentfulPaint); + } DOMTimeMilliSec GetTimeToTTFI() const { return TimeStampToDOM(mTTFI); } DOMTimeMilliSec GetTimeToDOMContentFlushed() const { return TimeStampToDOM(mDOMContentFlushed); } @@ -169,16 +173,17 @@ public: void NotifyDOMContentLoadedStart(nsIURI* aURI); void NotifyDOMContentLoadedEnd(nsIURI* aURI); static void TTITimeoutCallback(nsITimer* aTimer, void *aClosure); void TTITimeout(nsITimer* aTimer); void NotifyLongTask(mozilla::TimeStamp aWhen); void NotifyNonBlankPaintForRootContentDocument(); + void NotifyContentfulPaintForRootContentDocument(); void NotifyDOMContentFlushedForRootContentDocument(); void NotifyDocShellStateChanged(DocShellState aDocShellState); DOMTimeMilliSec TimeStampToDOM(mozilla::TimeStamp aStamp) const; inline DOMHighResTimeStamp TimeStampToDOMHighRes(mozilla::TimeStamp aStamp) const { if (aStamp.IsNull()) { @@ -204,16 +209,17 @@ private: nsCOMPtr<nsIURI> mUnloadedURI; nsCOMPtr<nsIURI> mLoadedURI; nsCOMPtr<nsITimer> mTTITimer; Type mNavigationType; DOMHighResTimeStamp mNavigationStartHighRes; mozilla::TimeStamp mNavigationStart; mozilla::TimeStamp mNonBlankPaint; + mozilla::TimeStamp mContentfulPaint; mozilla::TimeStamp mDOMContentFlushed; mozilla::TimeStamp mBeforeUnloadStart; mozilla::TimeStamp mUnloadStart; mozilla::TimeStamp mUnloadEnd; mozilla::TimeStamp mLoadEventStart; mozilla::TimeStamp mLoadEventEnd;
--- a/dom/base/nsDeprecatedOperationList.h +++ b/dom/base/nsDeprecatedOperationList.h @@ -39,12 +39,15 @@ DEPRECATED_OPERATION(MixedDisplayObjectS DEPRECATED_OPERATION(MotionEvent) DEPRECATED_OPERATION(OrientationEvent) DEPRECATED_OPERATION(ProximityEvent) DEPRECATED_OPERATION(AmbientLightEvent) DEPRECATED_OPERATION(IDBOpenDBOptions_StorageType) DEPRECATED_OPERATION(DOMAttrModifiedEvent) DEPRECATED_OPERATION(MozBoxOrInlineBoxDisplay) DEPRECATED_OPERATION(DOMQuadBoundsAttr) +DEPRECATED_OPERATION(DeprecatedTestingInterface) +DEPRECATED_OPERATION(DeprecatedTestingMethod) +DEPRECATED_OPERATION(DeprecatedTestingAttribute) DEPRECATED_OPERATION(CreateImageBitmapCanvasRenderingContext2D) DEPRECATED_OPERATION(MozRequestFullScreenDeprecatedPrefix) DEPRECATED_OPERATION(MozfullscreenchangeDeprecatedPrefix) DEPRECATED_OPERATION(MozfullscreenerrorDeprecatedPrefix)
--- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -3039,16 +3039,21 @@ nsIDocument::InitFeaturePolicy(nsIChanne } } if (parentPolicy) { // Let's inherit the policy from the parent HTMLIFrameElement if it exists. mFeaturePolicy->InheritPolicy(parentPolicy); } + // We don't want to parse the http Feature-Policy header if this pref is off. + if (!StaticPrefs::dom_security_featurePolicy_header_enabled()) { + return NS_OK; + } + nsCOMPtr<nsIHttpChannel> httpChannel; nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!httpChannel) { return NS_OK;
--- a/dom/base/nsFrameMessageManager.cpp +++ b/dom/base/nsFrameMessageManager.cpp @@ -26,17 +26,17 @@ #include "nsIScriptError.h" #include "nsIConsoleService.h" #include "nsIMemoryReporter.h" #include "nsIProtocolHandler.h" #include "nsIScriptSecurityManager.h" #include "xpcpublic.h" #include "js/CompilationAndEvaluation.h" #include "js/JSON.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/Preferences.h" #include "mozilla/ScriptPreloader.h" #include "mozilla/Telemetry.h" #include "mozilla/dom/ChildProcessMessageManager.h" #include "mozilla/dom/ChromeMessageBroadcaster.h" #include "mozilla/dom/File.h" @@ -1386,20 +1386,24 @@ nsMessageManagerScriptExecutor::TryCache } uint32_t size = (uint32_t)std::min(written, (uint64_t)UINT32_MAX); ScriptLoader::ConvertToUTF16(channel, (uint8_t*)buffer.get(), size, EmptyString(), nullptr, dataStringBuf, dataStringLength); } - JS::SourceBufferHolder srcBuf(dataStringBuf, dataStringLength, - JS::SourceBufferHolder::GiveOwnership); + if (!dataStringBuf || dataStringLength == 0) { + return; + } - if (!dataStringBuf || dataStringLength == 0) { + JS::UniqueTwoByteChars srcChars(dataStringBuf); + + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(cx, std::move(srcChars), dataStringLength)) { return; } JS::CompileOptions options(cx); options.setFileAndLine(url.get(), 1); options.setNoScriptRval(true); if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script)) {
--- a/dom/base/nsGlobalWindowInner.cpp +++ b/dom/base/nsGlobalWindowInner.cpp @@ -232,16 +232,18 @@ #include "mozilla/dom/WindowBinding.h" #include "nsITabChild.h" #include "mozilla/dom/MediaQueryList.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/NavigatorBinding.h" #include "mozilla/dom/ImageBitmap.h" #include "mozilla/dom/ImageBitmapBinding.h" #include "mozilla/dom/InstallTriggerBinding.h" +#include "mozilla/dom/Report.h" +#include "mozilla/dom/ReportingObserver.h" #include "mozilla/dom/ServiceWorker.h" #include "mozilla/dom/ServiceWorkerRegistration.h" #include "mozilla/dom/ServiceWorkerRegistrationDescriptor.h" #include "mozilla/dom/U2F.h" #include "mozilla/dom/WebIDLGlobalNameHash.h" #include "mozilla/dom/Worklet.h" #ifdef HAVE_SIDEBAR #include "mozilla/dom/ExternalBinding.h" @@ -332,16 +334,19 @@ using mozilla::dom::cache::CacheStorage; #define MAX_SUCCESSIVE_DIALOG_COUNT 5 // Idle fuzz time upper limit #define MAX_IDLE_FUZZ_TIME_MS 90000 // Min idle notification time in seconds. #define MIN_IDLE_NOTIFICATION_TIME_S 1 +// Max number of Report objects +#define MAX_REPORT_RECORDS 100 + static LazyLogModule gDOMLeakPRLogInner("DOMLeakInner"); static bool gIdleObserversAPIFuzzTimeDisabled = false; static FILE *gDumpFile = nullptr; nsGlobalWindowInner::InnerWindowByIdTable *nsGlobalWindowInner::sInnerWindowsById = nullptr; bool nsGlobalWindowInner::sDragServiceDisabled = false; @@ -1466,16 +1471,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollbars) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrypto) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mU2F) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPaintWorklet) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExternal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInstallTrigger) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIntlUtils) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReportRecords) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReportingObservers) tmp->TraverseHostObjectURIs(cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mMessageManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mGroupMessageManagers) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPromises) @@ -1555,16 +1562,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollbars) NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto) NS_IMPL_CYCLE_COLLECTION_UNLINK(mU2F) NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPaintWorklet) NS_IMPL_CYCLE_COLLECTION_UNLINK(mExternal) NS_IMPL_CYCLE_COLLECTION_UNLINK(mInstallTrigger) NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntlUtils) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mReportRecords) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mReportingObservers) tmp->UnlinkHostObjectURIs(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleRequestExecutor) // Here the IdleRequest list would've been unlinked, but we rely on // that IdleRequest objects have been traced and will remove // themselves while unlinking. @@ -5664,16 +5673,17 @@ nsGlobalWindowInner::Observe(nsISupports FireOfflineStatusEventIfChanged(); } return NS_OK; } if (!nsCRT::strcmp(aTopic, MEMORY_PRESSURE_OBSERVER_TOPIC)) { if (mPerformance) { mPerformance->MemoryPressure(); + mReportRecords.Clear(); } return NS_OK; } if (!nsCRT::strcmp(aTopic, "clear-site-data-reload-needed")) { // The reload is propagated from the top-level window only. NS_ConvertUTF16toUTF8 otherOrigin(aData); PropagateClearSiteDataReload(otherOrigin); @@ -8081,13 +8091,69 @@ nsPIDOMWindowInner::nsPIDOMWindowInner(n mHasTriedToCacheTopInnerWindow(false), mNumOfIndexedDBDatabases(0), mNumOfOpenWebSockets(0), mEvent(nullptr) { MOZ_ASSERT(aOuterWindow); } +void +nsPIDOMWindowInner::RegisterReportingObserver(ReportingObserver* aObserver, + bool aBuffered) +{ + MOZ_ASSERT(aObserver); + + if (mReportingObservers.Contains(aObserver)) { + return; + } + + if (NS_WARN_IF(!mReportingObservers.AppendElement(aObserver, fallible))) { + return; + } + + if (!aBuffered) { + return; + } + + for (Report* report : mReportRecords) { + aObserver->MaybeReport(report); + } +} + +void +nsPIDOMWindowInner::UnregisterReportingObserver(ReportingObserver* aObserver) +{ + MOZ_ASSERT(aObserver); + mReportingObservers.RemoveElement(aObserver); +} + +void +nsPIDOMWindowInner::BroadcastReport(Report* aReport) +{ + MOZ_ASSERT(aReport); + + for (ReportingObserver* observer : mReportingObservers) { + observer->MaybeReport(aReport); + } + + if (NS_WARN_IF(!mReportRecords.AppendElement(aReport, fallible))) { + return; + } + + while (mReportRecords.Length() > MAX_REPORT_RECORDS) { + mReportRecords.RemoveElementAt(0); + } +} + +void +nsPIDOMWindowInner::NotifyReportingObservers() +{ + for (ReportingObserver* observer : mReportingObservers) { + observer->MaybeNotify(); + } +} + nsPIDOMWindowInner::~nsPIDOMWindowInner() {} #undef FORWARD_TO_OUTER #undef FORWARD_TO_OUTER_OR_THROW #undef FORWARD_TO_OUTER_VOID
--- a/dom/base/nsJSUtils.cpp +++ b/dom/base/nsJSUtils.cpp @@ -11,17 +11,17 @@ * the generated code itself. */ #include "nsJSUtils.h" #include "jsapi.h" #include "jsfriendapi.h" #include "js/CompilationAndEvaluation.h" #include "js/OffThreadScriptCompilation.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "nsIScriptContext.h" #include "nsIScriptElement.h" #include "nsIScriptGlobalObject.h" #include "nsIXPConnect.h" #include "nsCOMPtr.h" #include "nsIScriptSecurityManager.h" #include "nsPIDOMWindow.h" #include "GeckoProfiler.h" @@ -91,19 +91,26 @@ nsJSUtils::CompileFunction(AutoJSAPI& js js::IsObjectInContextCompartment(aScopeChain[0], cx)); // Do the junk Gecko is supposed to do before calling into JSAPI. for (size_t i = 0; i < aScopeChain.length(); ++i) { JS::ExposeObjectToActiveJS(aScopeChain[i]); } // Compile. + const nsPromiseFlatString& flatBody = PromiseFlatString(aBody); + + JS::SourceText<char16_t> source; + if (!source.init(cx, flatBody.get(), flatBody.Length(), + JS::SourceOwnership::Borrowed)) + { + return NS_ERROR_FAILURE; + } + JS::Rooted<JSFunction*> fun(cx); - JS::SourceBufferHolder source(PromiseFlatString(aBody).get(), aBody.Length(), - JS::SourceBufferHolder::NoOwnership); if (!JS::CompileFunction(cx, aScopeChain, aOptions, PromiseFlatCString(aName).get(), aArgCount, aArgArray, source, &fun)) { return NS_ERROR_FAILURE; } @@ -212,17 +219,17 @@ nsJSUtils::ExecutionContext::JoinAndExec return mRv; } return NS_OK; } nsresult nsJSUtils::ExecutionContext::CompileAndExec(JS::CompileOptions& aCompileOptions, - JS::SourceBufferHolder& aSrcBuf, + JS::SourceText<char16_t>& aSrcBuf, JS::MutableHandle<JSScript*> aScript) { if (mSkip) { return mRv; } MOZ_ASSERT(aSrcBuf.get()); MOZ_ASSERT(mRetValue.isUndefined()); @@ -265,18 +272,25 @@ nsJSUtils::ExecutionContext::CompileAndE const nsAString& aScript) { MOZ_ASSERT(!mEncodeBytecode, "A JSScript is needed for calling FinishIncrementalEncoding"); if (mSkip) { return mRv; } const nsPromiseFlatString& flatScript = PromiseFlatString(aScript); - JS::SourceBufferHolder srcBuf(flatScript.get(), aScript.Length(), - JS::SourceBufferHolder::NoOwnership); + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(mCx, flatScript.get(), flatScript.Length(), + JS::SourceOwnership::Borrowed)) + { + mSkip = true; + mRv = EvaluationExceptionToNSResult(mCx); + return mRv; + } + JS::Rooted<JSScript*> script(mCx); return CompileAndExec(aCompileOptions, srcBuf, &script); } nsresult nsJSUtils::ExecutionContext::DecodeAndExec(JS::CompileOptions& aCompileOptions, mozilla::Vector<uint8_t>& aBytecodeBuf, size_t aBytecodeIndex) @@ -468,17 +482,17 @@ nsJSUtils::ExecutionContext::ExtractRetu } aRetValue.set(mRetValue); return NS_OK; } nsresult nsJSUtils::CompileModule(JSContext* aCx, - JS::SourceBufferHolder& aSrcBuf, + JS::SourceText<char16_t>& aSrcBuf, JS::Handle<JSObject*> aEvaluationGlobal, JS::CompileOptions &aCompileOptions, JS::MutableHandle<JSObject*> aModule) { AUTO_PROFILER_LABEL("nsJSUtils::CompileModule", JS); MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); MOZ_ASSERT(aSrcBuf.get());
--- a/dom/base/nsJSUtils.h +++ b/dom/base/nsJSUtils.h @@ -15,16 +15,17 @@ */ #include "mozilla/Assertions.h" #include "GeckoProfiler.h" #include "jsapi.h" #include "jsfriendapi.h" #include "js/Conversions.h" +#include "js/SourceText.h" #include "js/StableStringChars.h" #include "nsString.h" class nsIScriptContext; class nsIScriptElement; class nsIScriptGlobalObject; class nsXBLPrototypeBinding; @@ -152,19 +153,19 @@ public: // After getting a notification that an off-thread compilation terminated, // this function will take the result of the parser by moving it to the main // thread before starting the execution of the script. // // The compiled script would be returned in the |aScript| out-param. MOZ_MUST_USE nsresult JoinAndExec(JS::OffThreadToken** aOffThreadToken, JS::MutableHandle<JSScript*> aScript); - // Compile a script contained in a SourceBuffer, and execute it. + // Compile a script contained in a SourceText, and execute it. nsresult CompileAndExec(JS::CompileOptions& aCompileOptions, - JS::SourceBufferHolder& aSrcBuf, + JS::SourceText<char16_t>& aSrcBuf, JS::MutableHandle<JSScript*> aScript); // Compile a script contained in a string, and execute it. nsresult CompileAndExec(JS::CompileOptions& aCompileOptions, const nsAString& aScript); // Decode a script contained in a buffer, and execute it. MOZ_MUST_USE nsresult DecodeAndExec(JS::CompileOptions& aCompileOptions, @@ -181,17 +182,17 @@ public: // Decode a BinAST encoded script contained in a buffer, and execute it. nsresult DecodeBinASTAndExec(JS::CompileOptions& aCompileOptions, const uint8_t* aBuf, size_t aLength, JS::MutableHandle<JSScript*> aScript); }; static nsresult CompileModule(JSContext* aCx, - JS::SourceBufferHolder& aSrcBuf, + JS::SourceText<char16_t>& aSrcBuf, JS::Handle<JSObject*> aEvaluationGlobal, JS::CompileOptions &aCompileOptions, JS::MutableHandle<JSObject*> aModule); static nsresult InitModuleSourceElement(JSContext* aCx, JS::Handle<JSObject*> aModule, nsIScriptElement* aElement);
--- a/dom/base/nsPIDOMWindow.h +++ b/dom/base/nsPIDOMWindow.h @@ -53,16 +53,19 @@ class ClientInfo; class ClientState; class ContentFrameMessageManager; class DocGroup; class TabGroup; class Element; class MozIdleObserver; class Navigator; class Performance; +class Report; +class ReportBody; +class ReportingObserver; class Selection; class ServiceWorker; class ServiceWorkerDescriptor; class Timeout; class TimeoutManager; class CustomElementRegistry; enum class CallerType : uint32_t; } // namespace dom @@ -623,16 +626,29 @@ public: virtual nsISerialEventTarget* EventTargetFor(mozilla::TaskCategory aCategory) const = 0; // Returns the AutoplayPermissionManager that documents in this window should // use to request permission to autoplay. already_AddRefed<mozilla::AutoplayPermissionManager> GetAutoplayPermissionManager(); + void + RegisterReportingObserver(mozilla::dom::ReportingObserver* aObserver, + bool aBuffered); + + void + UnregisterReportingObserver(mozilla::dom::ReportingObserver* aObserver); + + void + BroadcastReport(mozilla::dom::Report* aReport); + + void + NotifyReportingObservers(); + protected: void CreatePerformanceObjectIfNeeded(); // Lazily instantiate an about:blank document if necessary, and if // we have what it takes to do so. void MaybeCreateDoc(); void SetChromeEventHandlerInternal(mozilla::dom::EventTarget* aChromeEventHandler) { @@ -717,16 +733,20 @@ protected: // If we're in the process of requesting permission for this window to // play audible media, or we've already been granted permission by the // user, this is non-null, and encapsulates the request. RefPtr<mozilla::AutoplayPermissionManager> mAutoplayPermissionManager; // The event dispatch code sets and unsets this while keeping // the event object alive. mozilla::dom::Event* mEvent; + + // List of Report objects for ReportingObservers. + nsTArray<RefPtr<mozilla::dom::ReportingObserver>> mReportingObservers; + nsTArray<RefPtr<mozilla::dom::Report>> mReportRecords; }; NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID) class nsPIDOMWindowOuter : public mozIDOMWindowProxy { protected: explicit nsPIDOMWindowOuter(uint64_t aWindowID);
--- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -8,53 +8,57 @@ #include <algorithm> #include <stdarg.h> #include "mozilla/Assertions.h" #include "mozilla/DebugOnly.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs.h" #include "mozilla/Unused.h" #include "mozilla/UseCounter.h" #include "AccessCheck.h" #include "js/JSON.h" #include "js/StableStringChars.h" #include "jsfriendapi.h" #include "nsContentCreatorFunctions.h" #include "nsContentUtils.h" #include "nsGlobalWindow.h" #include "nsHTMLTags.h" #include "nsIDocShell.h" #include "nsIDOMGlobalPropertyInitializer.h" #include "nsINode.h" #include "nsIPermissionManager.h" #include "nsIPrincipal.h" +#include "nsIURIFixup.h" #include "nsIXPConnect.h" #include "nsUTF8Utils.h" #include "WorkerPrivate.h" #include "WorkerRunnable.h" #include "WrapperFactory.h" #include "xpcprivate.h" #include "XrayWrapper.h" #include "nsPrintfCString.h" #include "mozilla/Sprintf.h" #include "nsGlobalWindow.h" #include "nsReadableUtils.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/CustomElementRegistry.h" +#include "mozilla/dom/DeprecationReportBody.h" #include "mozilla/dom/DOMException.h" #include "mozilla/dom/ElementBinding.h" #include "mozilla/dom/HTMLObjectElement.h" #include "mozilla/dom/HTMLObjectElementBinding.h" #include "mozilla/dom/HTMLEmbedElement.h" #include "mozilla/dom/HTMLElementBinding.h" #include "mozilla/dom/HTMLEmbedElementBinding.h" +#include "mozilla/dom/ReportingUtils.h" #include "mozilla/dom/XULElementBinding.h" #include "mozilla/dom/XULFrameElementBinding.h" #include "mozilla/dom/XULMenuElementBinding.h" #include "mozilla/dom/XULPopupElementBinding.h" #include "mozilla/dom/XULTextElementBinding.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/WebIDLGlobalNameHash.h" #include "mozilla/dom/WorkerPrivate.h" @@ -2195,17 +2199,17 @@ GetCachedSlotStorageObjectSlow(JSContext if (!xpc::WrapperFactory::IsXrayWrapper(obj)) { JSObject* retval = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false); MOZ_ASSERT(IsDOMObject(retval)); *isXray = false; return retval; } *isXray = true; - return xpc::EnsureXrayExpandoObject(cx, obj);; + return xpc::EnsureXrayExpandoObject(cx, obj); } DEFINE_XRAY_EXPANDO_CLASS(, DefaultXrayExpandoObjectClass, 0); NativePropertyHooks sEmptyNativePropertyHooks = { nullptr, nullptr, nullptr, @@ -4098,21 +4102,105 @@ SetDocumentAndPageUseCounter(JSObject* a nsGlobalWindowInner* win = xpc::WindowGlobalOrNull(js::UncheckedUnwrap(aObject)); if (win && win->GetDocument()) { win->GetDocument()->SetDocumentAndPageUseCounter(aUseCounter); } } namespace { +#define DEPRECATED_OPERATION(_op) #_op, +static const char* kDeprecatedOperations[] = { +#include "nsDeprecatedOperationList.h" + nullptr +}; +#undef DEPRECATED_OPERATION + +void +ReportDeprecation(nsPIDOMWindowInner* aWindow, nsIURI* aURI, + nsIDocument::DeprecatedOperations aOperation, + const nsAString& aFileName, + const Nullable<uint32_t>& aLineNumber, + const Nullable<uint32_t>& aColumnNumber) +{ + MOZ_ASSERT(aURI); + + // Anonymize the URL. + // Strip the URL of any possible username/password and make it ready to be + // presented in the UI. + nsCOMPtr<nsIURIFixup> urifixup = services::GetURIFixup(); + if (NS_WARN_IF(!urifixup)) { + return; + } + + nsCOMPtr<nsIURI> exposableURI; + nsresult rv = urifixup->CreateExposableURI(aURI, getter_AddRefs(exposableURI)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsAutoCString spec; + rv = exposableURI->GetSpec(spec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsAutoString type; + type.AssignASCII(kDeprecatedOperations[aOperation]); + + nsAutoCString key; + key.AssignASCII(kDeprecatedOperations[aOperation]); + key.AppendASCII("Warning"); + + nsAutoString msg; + rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + key.get(), msg); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + RefPtr<DeprecationReportBody> body = + new DeprecationReportBody(aWindow, type, Nullable<Date>(), + msg, aFileName, aLineNumber, aColumnNumber); + + ReportingUtils::Report(aWindow, nsGkAtoms::deprecation, + NS_ConvertUTF8toUTF16(spec), body); +} + +void +MaybeReportDeprecation(nsPIDOMWindowInner* aWindow, + nsIDocument::DeprecatedOperations aOperation, + const nsAString& aFileName, + const Nullable<uint32_t>& aLineNumber, + const Nullable<uint32_t>& aColumnNumber) +{ + MOZ_ASSERT(aWindow); + + if (!StaticPrefs::dom_reporting_enabled()) { + return; + } + + if (NS_WARN_IF(!aWindow->GetExtantDoc())) { + return; + } + + nsCOMPtr<nsIURI> uri = aWindow->GetExtantDoc()->GetDocumentURI(); + if (NS_WARN_IF(!uri)) { + return; + } + + ReportDeprecation(aWindow, uri, aOperation, aFileName, aLineNumber, + aColumnNumber); +} + // This runnable is used to write a deprecation message from a worker to the // console running on the main-thread. class DeprecationWarningRunnable final : public WorkerProxyToMainThreadRunnable { - nsIDocument::DeprecatedOperations mOperation; + const nsIDocument::DeprecatedOperations mOperation; public: explicit DeprecationWarningRunnable(nsIDocument::DeprecatedOperations aOperation) : mOperation(aOperation) {} private: void @@ -4156,16 +4244,31 @@ DeprecationWarning(JSContext* aCx, JSObj void DeprecationWarning(const GlobalObject& aGlobal, nsIDocument::DeprecatedOperations aOperation) { if (NS_IsMainThread()) { nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports()); if (window && window->GetExtantDoc()) { window->GetExtantDoc()->WarnOnceAbout(aOperation); + + nsAutoCString fileName; + Nullable<uint32_t> lineNumber; + Nullable<uint32_t> columnNumber; + uint32_t line = 0; + uint32_t column = 0; + if (nsJSUtils::GetCallingLocation(aGlobal.Context(), fileName, + &line, &column)) { + lineNumber.SetValue(line); + columnNumber.SetValue(column); + } + + MaybeReportDeprecation(window, aOperation, + NS_ConvertUTF8toUTF16(fileName), lineNumber, + columnNumber); } return; } WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aGlobal.Context()); if (!workerPrivate) { return;
--- a/dom/html/HTMLCanvasElement.cpp +++ b/dom/html/HTMLCanvasElement.cpp @@ -391,16 +391,17 @@ HTMLCanvasElementObserver::HandleEvent(E NS_IMPL_ISUPPORTS(HTMLCanvasElementObserver, nsIObserver) // --------------------------------------------------------------------------- HTMLCanvasElement::HTMLCanvasElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) : nsGenericHTMLElement(std::move(aNodeInfo)), mResetLayer(true) , + mMaybeModified(false) , mWriteOnly(false) {} HTMLCanvasElement::~HTMLCanvasElement() { if (mContextObserver) { mContextObserver->Destroy(); mContextObserver = nullptr; @@ -1005,30 +1006,32 @@ HTMLCanvasElement::MozGetAsFileImpl(cons return NS_OK; } nsresult HTMLCanvasElement::GetContext(const nsAString& aContextId, nsISupports** aContext) { ErrorResult rv; + mMaybeModified = true; // For FirstContentfulPaint *aContext = GetContext(nullptr, aContextId, JS::NullHandleValue, rv).take(); return rv.StealNSResult(); } already_AddRefed<nsISupports> HTMLCanvasElement::GetContext(JSContext* aCx, const nsAString& aContextId, JS::Handle<JS::Value> aContextOptions, ErrorResult& aRv) { if (mOffscreenCanvas) { return nullptr; } + mMaybeModified = true; // For FirstContentfulPaint return CanvasRenderingContextHelper::GetContext(aCx, aContextId, aContextOptions.isObject() ? aContextOptions : JS::NullHandleValue, aRv); } already_AddRefed<nsISupports> HTMLCanvasElement::MozGetIPCContext(const nsAString& aContextId, ErrorResult& aRv)
--- a/dom/html/HTMLCanvasElement.h +++ b/dom/html/HTMLCanvasElement.h @@ -344,16 +344,18 @@ public: void OnMemoryPressure(); static void SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer); static void InvalidateFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer); already_AddRefed<layers::SharedSurfaceTextureClient> GetVRFrame(); + bool MaybeModified() const { return mMaybeModified; }; + protected: virtual ~HTMLCanvasElement(); virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; virtual nsIntSize GetWidthHeight() override; virtual already_AddRefed<nsICanvasRenderingContextInternal> @@ -382,16 +384,17 @@ protected: bool aNotify) override; virtual nsresult OnAttrSetButNotChanged(int32_t aNamespaceID, nsAtom* aName, const nsAttrValueOrString& aValue, bool aNotify) override; AsyncCanvasRenderer* GetAsyncCanvasRenderer(); bool mResetLayer; + bool mMaybeModified; // we fetched the context, so we may have written to the canvas RefPtr<HTMLCanvasElement> mOriginalCanvas; RefPtr<PrintCallback> mPrintCallback; RefPtr<HTMLCanvasPrintState> mPrintState; nsTArray<WeakPtr<FrameCaptureListener>> mRequestedFrameListeners; RefPtr<RequestedFrameRefreshObserver> mRequestedFrameRefreshObserver; RefPtr<AsyncCanvasRenderer> mAsyncCanvasRenderer; RefPtr<OffscreenCanvas> mOffscreenCanvas; RefPtr<HTMLCanvasElementObserver> mContextObserver;
--- a/dom/html/test/test_fullscreen-api.html +++ b/dom/html/test/test_fullscreen-api.html @@ -43,17 +43,20 @@ var gTestWindows = [ { test: "file_fullscreen-top-layer.html" }, { test: "file_fullscreen-backdrop.html" }, { test: "file_fullscreen-nested.html" }, { test: "file_fullscreen-prefixed.html" }, { test: "file_fullscreen-unprefix-disabled.html" }, { test: "file_fullscreen-lenient-setters.html" }, { test: "file_fullscreen-table.html" }, { test: "file_fullscreen-event-order.html" }, - { test: "file_fullscreen-featurePolicy.html", prefs: [["dom.security.featurePolicy.enabled", true]] }, + { test: "file_fullscreen-featurePolicy.html", + prefs: [["dom.security.featurePolicy.enabled", true], + ["dom.security.featurePolicy.header.enabled", true], + ["dom.security.featurePolicy.webidl.enabled", true]] }, ]; var testWindow = null; var gTestIndex = 0; function finish() { SimpleTest.finish(); }
--- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -348,16 +348,23 @@ MixedDisplayObjectSubrequestWarning=Load MotionEventWarning=Use of the motion sensor is deprecated. OrientationEventWarning=Use of the orientation sensor is deprecated. ProximityEventWarning=Use of the proximity sensor is deprecated. AmbientLightEventWarning=Use of the ambient light sensor is deprecated. # LOCALIZATION NOTE: Do not translate "storage", "indexedDB.open" and "navigator.storage.persist()". IDBOpenDBOptions_StorageTypeWarning=The ‘storage’ attribute in options passed to indexedDB.open is deprecated and will soon be removed. To get persistent storage, please use navigator.storage.persist() instead. DOMQuadBoundsAttrWarning=DOMQuad.bounds is deprecated in favor of DOMQuad.getBounds() UnsupportedEntryTypesIgnored=Ignoring unsupported entryTypes: %S. + +#LOCALIZATION NOTE(DeprecatedTestingInterfaceWarning): Do not translate this message. It's just testing only. +DeprecatedTestingInterfaceWarning=TestingDeprecatedInterface is a testing-only interface and this is its testing deprecation message. +#LOCALIZATION NOTE(DeprecatedTestingMethodWarning): Do not translate this message. It's just testing only. +DeprecatedTestingMethodWarning=TestingDeprecatedInterface.deprecatedMethod() is a testing-only method and this is its testing deprecation message. +#LOCALIZATION NOTE(DeprecatedTestingAttributeWarning): Do not translate this message. It's just testing only. +DeprecatedTestingAttributeWarning=TestingDeprecatedInterface.deprecatedAttribute is a testing-only attribute and this is its testing deprecation message. # LOCALIZATION NOTE (CreateImageBitmapCanvasRenderingContext2DWarning): Do not translate CanvasRenderingContext2D and createImageBitmap. CreateImageBitmapCanvasRenderingContext2DWarning=Use of CanvasRenderingContext2D in createImageBitmap is deprecated. # LOCALIZATION NOTE (MozRequestFullScreenDeprecatedPrefixWarning): Do not translate mozRequestFullScreen. MozRequestFullScreenDeprecatedPrefixWarning=mozRequestFullScreen() is deprecated. # LOCALIZATION NOTE (MozfullscreenchangeDeprecatedPrefixWarning): Do not translate onmozfullscreenchange. MozfullscreenchangeDeprecatedPrefixWarning=onmozfullscreenchange is deprecated. # LOCALIZATION NOTE (MozfullscreenerrorDeprecatedPrefixWarning): Do not translate onmozfullscreenerror. MozfullscreenerrorDeprecatedPrefixWarning=onmozfullscreenerror is deprecated.
--- a/dom/moz.build +++ b/dom/moz.build @@ -97,16 +97,17 @@ DIRS += [ 'webbrowserpersist', 'xhr', 'worklet', 'script', 'payments', 'websocket', 'serviceworkers', 'simpledb', + 'reporting', ] if CONFIG['MOZ_LIBPRIO']: DIRS += ['prio'] if CONFIG['OS_ARCH'] == 'WINNT': DIRS += ['plugins/ipc/hangui']
--- a/dom/performance/PerformanceTiming.h +++ b/dom/performance/PerformanceTiming.h @@ -435,16 +435,30 @@ public: if (mPerformance->IsSystemPrincipal()) { return GetDOMTiming()->GetTimeToNonBlankPaint(); } return nsRFPService::ReduceTimePrecisionAsMSecs( GetDOMTiming()->GetTimeToNonBlankPaint(), mPerformance->GetRandomTimelineSeed()); } + DOMTimeMilliSec TimeToContentfulPaint() const + { + if (!nsContentUtils::IsPerformanceTimingEnabled() || + nsContentUtils::ShouldResistFingerprinting()) { + return 0; + } + if (mPerformance->IsSystemPrincipal()) { + return GetDOMTiming()->GetTimeToContentfulPaint(); + } + return nsRFPService::ReduceTimePrecisionAsMSecs( + GetDOMTiming()->GetTimeToContentfulPaint(), + mPerformance->GetRandomTimelineSeed()); + } + DOMTimeMilliSec TimeToDOMContentFlushed() const { if (!nsContentUtils::IsPerformanceTimingEnabled() || nsContentUtils::ShouldResistFingerprinting()) { return 0; } if (mPerformance->IsSystemPrincipal()) { return GetDOMTiming()->GetTimeToDOMContentFlushed();
new file mode 100644 --- /dev/null +++ b/dom/reporting/DeprecationReportBody.cpp @@ -0,0 +1,76 @@ +/* -*- 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 "mozilla/dom/DeprecationReportBody.h" +#include "mozilla/dom/ReportingBinding.h" + +namespace mozilla { +namespace dom { + +DeprecationReportBody::DeprecationReportBody(nsPIDOMWindowInner* aWindow, + const nsAString& aId, + const Nullable<Date>& aDate, + const nsAString& aMessage, + const nsAString& aSourceFile, + const Nullable<uint32_t>& aLineNumber, + const Nullable<uint32_t>& aColumnNumber) + : ReportBody(aWindow) + , mId(aId) + , mDate(aDate) + , mMessage(aMessage) + , mSourceFile(aSourceFile) + , mLineNumber(aLineNumber) + , mColumnNumber(aColumnNumber) +{ + MOZ_ASSERT(aWindow); +} + +DeprecationReportBody::~DeprecationReportBody() = default; + +JSObject* +DeprecationReportBody::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return DeprecationReportBody_Binding::Wrap(aCx, this, aGivenProto); +} + +void +DeprecationReportBody::GetId(nsAString& aId) const +{ + aId = mId; +} + +Nullable<Date> +DeprecationReportBody::GetAnticipatedRemoval() const +{ + return mDate; +} + +void +DeprecationReportBody::GetMessage(nsAString& aMessage) const +{ + aMessage = mMessage; +} + +void +DeprecationReportBody::GetSourceFile(nsAString& aSourceFile) const +{ + aSourceFile = mSourceFile; +} + +Nullable<uint32_t> +DeprecationReportBody::GetLineNumber() const +{ + return mLineNumber; +} + +Nullable<uint32_t> +DeprecationReportBody::GetColumnNumber() const +{ + return mColumnNumber; +} + +} // dom namespace +} // mozilla namespace
new file mode 100644 --- /dev/null +++ b/dom/reporting/DeprecationReportBody.h @@ -0,0 +1,62 @@ +/* -*- 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 mozilla_dom_DeprecationReportBody_h +#define mozilla_dom_DeprecationReportBody_h + +#include "mozilla/dom/ReportBody.h" +#include "mozilla/dom/Date.h" + +namespace mozilla { +namespace dom { + +class DeprecationReportBody final : public ReportBody +{ +public: + DeprecationReportBody(nsPIDOMWindowInner* aWindow, + const nsAString& aId, + const Nullable<Date>& aDate, + const nsAString& aMessage, + const nsAString& aSourceFile, + const Nullable<uint32_t>& aLineNumber, + const Nullable<uint32_t>& aColumnNumber); + + JSObject* + WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + void + GetId(nsAString& aId) const; + + Nullable<Date> + GetAnticipatedRemoval() const; + + void + GetMessage(nsAString& aMessage) const; + + void + GetSourceFile(nsAString& aSourceFile) const; + + Nullable<uint32_t> + GetLineNumber() const; + + Nullable<uint32_t> + GetColumnNumber() const; + +private: + ~DeprecationReportBody(); + + const nsString mId; + const Nullable<Date> mDate; + const nsString mMessage; + const nsString mSourceFile; + const Nullable<uint32_t> mLineNumber; + const Nullable<uint32_t> mColumnNumber; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_DeprecationReportBody_h
new file mode 100644 --- /dev/null +++ b/dom/reporting/FeaturePolicyViolationReportBody.cpp @@ -0,0 +1,66 @@ +/* -*- 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 "mozilla/dom/FeaturePolicyViolationReportBody.h" +#include "mozilla/dom/FeaturePolicyBinding.h" + +namespace mozilla { +namespace dom { + +FeaturePolicyViolationReportBody::FeaturePolicyViolationReportBody(nsPIDOMWindowInner* aWindow, + const nsAString& aFeatureId, + const nsAString& aSourceFile, + const Nullable<int32_t>& aLineNumber, + const Nullable<int32_t>& aColumnNumber, + const nsAString& aDisposition) + : ReportBody(aWindow) + , mFeatureId(aFeatureId) + , mSourceFile(aSourceFile) + , mLineNumber(aLineNumber) + , mColumnNumber(aColumnNumber) + , mDisposition(aDisposition) +{} + +FeaturePolicyViolationReportBody::~FeaturePolicyViolationReportBody() = default; + +JSObject* +FeaturePolicyViolationReportBody::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return FeaturePolicyViolationReportBody_Binding::Wrap(aCx, this, aGivenProto); +} + +void +FeaturePolicyViolationReportBody::GetFeatureId(nsAString& aFeatureId) const +{ + aFeatureId = mFeatureId; +} + +void +FeaturePolicyViolationReportBody::GetSourceFile(nsAString& aSourceFile) const +{ + aSourceFile = mSourceFile; +} + +Nullable<int32_t> +FeaturePolicyViolationReportBody::GetLineNumber() const +{ + return mLineNumber; +} + +Nullable<int32_t> +FeaturePolicyViolationReportBody::GetColumnNumber() const +{ + return mColumnNumber; +} + +void +FeaturePolicyViolationReportBody::GetDisposition(nsAString& aDisposition) const +{ + aDisposition = mDisposition; +} + +} // dom namespace +} // mozilla namespace
new file mode 100644 --- /dev/null +++ b/dom/reporting/FeaturePolicyViolationReportBody.h @@ -0,0 +1,57 @@ +/* -*- 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 mozilla_dom_FeaturePolicyViolationReportBody_h +#define mozilla_dom_FeaturePolicyViolationReportBody_h + +#include "mozilla/dom/ReportBody.h" +#include "mozilla/dom/Date.h" + +namespace mozilla { +namespace dom { + +class FeaturePolicyViolationReportBody final : public ReportBody +{ +public: + FeaturePolicyViolationReportBody(nsPIDOMWindowInner* aWindow, + const nsAString& aFeatureId, + const nsAString& aSourceFile, + const Nullable<int32_t>& aLineNumber, + const Nullable<int32_t>& aColumnNumber, + const nsAString& aDisposition); + + JSObject* + WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + void + GetFeatureId(nsAString& aFeatureId) const; + + void + GetSourceFile(nsAString& aSourceFile) const; + + Nullable<int32_t> + GetLineNumber() const; + + Nullable<int32_t> + GetColumnNumber() const; + + void + GetDisposition(nsAString& aDisposition) const; + +private: + ~FeaturePolicyViolationReportBody(); + + const nsString mFeatureId; + const nsString mSourceFile; + const Nullable<int32_t> mLineNumber; + const Nullable<int32_t> mColumnNumber; + const nsString mDisposition; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_FeaturePolicyViolationReportBody_h
new file mode 100644 --- /dev/null +++ b/dom/reporting/Report.cpp @@ -0,0 +1,70 @@ +/* -*- 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 "mozilla/dom/Report.h" +#include "mozilla/dom/ReportBody.h" +#include "mozilla/dom/ReportingBinding.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Report, mWindow, mBody) +NS_IMPL_CYCLE_COLLECTING_ADDREF(Report) +NS_IMPL_CYCLE_COLLECTING_RELEASE(Report) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Report) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +Report::Report(nsPIDOMWindowInner* aWindow, + const nsAString& aType, + const nsAString& aURL, + ReportBody* aBody) + : mWindow(aWindow) + , mType(aType) + , mURL(aURL) + , mBody(aBody) +{ + MOZ_ASSERT(aWindow); +} + +Report::~Report() = default; + +already_AddRefed<Report> +Report::Clone() +{ + RefPtr<Report> report = + new Report(mWindow, mType, mURL, mBody); + return report.forget(); +} + +JSObject* +Report::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return Report_Binding::Wrap(aCx, this, aGivenProto); +} + +void +Report::GetType(nsAString& aType) const +{ + aType = mType; +} + +void +Report::GetUrl(nsAString& aURL) const +{ + aURL = mURL; +} + +ReportBody* +Report::GetBody() const +{ + return mBody; +} + +} // dom namespace +} // mozilla namespace
new file mode 100644 --- /dev/null +++ b/dom/reporting/Report.h @@ -0,0 +1,66 @@ +/* -*- 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 mozilla_dom_Report_h +#define mozilla_dom_Report_h + +#include "mozilla/Attributes.h" +#include "mozilla/dom/BindingUtils.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" + +namespace mozilla { +namespace dom { + +class ReportBody; + +class Report final : public nsISupports + , public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Report) + + Report(nsPIDOMWindowInner* aWindow, + const nsAString& aType, + const nsAString& aURL, + ReportBody* aBody); + + already_AddRefed<Report> + Clone(); + + JSObject* + WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + nsPIDOMWindowInner* + GetParentObject() const + { + return mWindow; + } + + void + GetType(nsAString& aType) const; + + void + GetUrl(nsAString& aURL) const; + + ReportBody* + GetBody() const; + +private: + ~Report(); + + nsCOMPtr<nsPIDOMWindowInner> mWindow; + + const nsString mType; + const nsString mURL; + RefPtr<ReportBody> mBody; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_Report_h
new file mode 100644 --- /dev/null +++ b/dom/reporting/ReportBody.cpp @@ -0,0 +1,38 @@ +/* -*- 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 "mozilla/dom/ReportBody.h" +#include "mozilla/dom/ReportingBinding.h" +#include "nsPIDOMWindow.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ReportBody, mWindow) +NS_IMPL_CYCLE_COLLECTING_ADDREF(ReportBody) +NS_IMPL_CYCLE_COLLECTING_RELEASE(ReportBody) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReportBody) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +ReportBody::ReportBody(nsPIDOMWindowInner* aWindow) + : mWindow(aWindow) +{ + MOZ_ASSERT(aWindow); +} + +ReportBody::~ReportBody() = default; + +JSObject* +ReportBody::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return ReportBody_Binding::Wrap(aCx, this, aGivenProto); +} + +} // dom namespace +} // mozilla namespace
new file mode 100644 --- /dev/null +++ b/dom/reporting/ReportBody.h @@ -0,0 +1,47 @@ +/* -*- 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 mozilla_dom_ReportBody_h +#define mozilla_dom_ReportBody_h + +#include "mozilla/Attributes.h" +#include "mozilla/dom/BindingUtils.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" + +class nsPIDOMWindowInner; + +namespace mozilla { +namespace dom { + +class ReportBody : public nsISupports + , public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ReportBody) + + explicit ReportBody(nsPIDOMWindowInner* aWindow); + + virtual JSObject* + WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + nsPIDOMWindowInner* + GetParentObject() const + { + return mWindow; + } + +protected: + virtual ~ReportBody(); + + nsCOMPtr<nsPIDOMWindowInner> mWindow; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_ReportBody_h
new file mode 100644 --- /dev/null +++ b/dom/reporting/ReportingObserver.cpp @@ -0,0 +1,197 @@ +/* -*- 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 "mozilla/dom/ReportingObserver.h" +#include "mozilla/dom/ReportingBinding.h" +#include "nsContentUtils.h" +#include "nsGlobalWindowInner.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_CLASS(ReportingObserver) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ReportingObserver) + tmp->Shutdown(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mReports) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ReportingObserver) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReports) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(ReportingObserver) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(ReportingObserver) +NS_IMPL_CYCLE_COLLECTING_RELEASE(ReportingObserver) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReportingObserver) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_END + +/* static */ already_AddRefed<ReportingObserver> +ReportingObserver::Constructor(const GlobalObject& aGlobal, + ReportingObserverCallback& aCallback, + const ReportingObserverOptions& aOptions, + ErrorResult& aRv) +{ + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports()); + MOZ_ASSERT(window); + + nsTArray<nsString> types; + if (aOptions.mTypes.WasPassed()) { + types = aOptions.mTypes.Value(); + } + + RefPtr<ReportingObserver> ro = + new ReportingObserver(window, aCallback, types, aOptions.mBuffered); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + aRv = obs->AddObserver(ro, "memory-pressure", true); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return ro.forget(); +} + +ReportingObserver::ReportingObserver(nsPIDOMWindowInner* aWindow, + ReportingObserverCallback& aCallback, + const nsTArray<nsString>& aTypes, + bool aBuffered) + : mWindow(aWindow) + , mCallback(&aCallback) + , mTypes(aTypes) + , mBuffered(aBuffered) +{ + MOZ_ASSERT(aWindow); +} + +ReportingObserver::~ReportingObserver() +{ + Shutdown(); +} + +void +ReportingObserver::Shutdown() +{ + Disconnect(); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "memory-pressure"); + } +} + +JSObject* +ReportingObserver::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return ReportingObserver_Binding::Wrap(aCx, this, aGivenProto); +} + +void +ReportingObserver::Observe() +{ + mWindow->RegisterReportingObserver(this, mBuffered); +} + +void +ReportingObserver::Disconnect() +{ + if (mWindow) { + mWindow->UnregisterReportingObserver(this); + } +} + +void +ReportingObserver::TakeRecords(nsTArray<RefPtr<Report>>& aRecords) +{ + mReports.SwapElements(aRecords); +} + +void +ReportingObserver::MaybeReport(Report* aReport) +{ + MOZ_ASSERT(aReport); + + if (!mTypes.IsEmpty()) { + nsAutoString type; + aReport->GetType(type); + + if (!mTypes.Contains(type)) { + return; + } + } + + bool wasEmpty = mReports.IsEmpty(); + + RefPtr<Report> report = aReport->Clone(); + MOZ_ASSERT(report); + + if (NS_WARN_IF(!mReports.AppendElement(report, fallible))) { + return; + } + + if (!wasEmpty) { + return; + } + + nsCOMPtr<nsPIDOMWindowInner> window = mWindow; + + nsCOMPtr<nsIRunnable> r = + NS_NewRunnableFunction( + "ReportingObserver::MaybeReport", + [window]() { + window->NotifyReportingObservers(); + }); + + NS_DispatchToCurrentThread(r); +} + +void +ReportingObserver::MaybeNotify() +{ + if (mReports.IsEmpty()) { + return; + } + + // Let's take the ownership of the reports. + nsTArray<RefPtr<Report>> list; + list.SwapElements(mReports); + + Sequence<OwningNonNull<Report>> reports; + for (Report* report : list) { + if (NS_WARN_IF(!reports.AppendElement(*report, fallible))) { + return; + } + } + + // We should report if this throws exception. But where? + mCallback->Call(reports, *this); +} + +NS_IMETHODIMP +ReportingObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + MOZ_ASSERT(!strcmp(aTopic, "memory-pressure")); + mReports.Clear(); + return NS_OK; +} + +} // dom namespace +} // mozilla namespace
new file mode 100644 --- /dev/null +++ b/dom/reporting/ReportingObserver.h @@ -0,0 +1,89 @@ +/* -*- 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 mozilla_dom_ReportingObserver_h +#define mozilla_dom_ReportingObserver_h + +#include "mozilla/Attributes.h" +#include "mozilla/dom/BindingUtils.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" +#include "nsWrapperCache.h" +#include "nsTArray.h" + +class nsPIDOMWindowInner; + +namespace mozilla { +namespace dom { + +class Report; +class ReportingObserverCallback; +struct ReportingObserverOptions; + +class ReportingObserver final : public nsIObserver + , public nsWrapperCache + , public nsSupportsWeakReference +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(ReportingObserver, + nsIObserver) + NS_DECL_NSIOBSERVER + + static already_AddRefed<ReportingObserver> + Constructor(const GlobalObject& aGlobal, + ReportingObserverCallback& aCallback, + const ReportingObserverOptions& aOptions, + ErrorResult& aRv); + + ReportingObserver(nsPIDOMWindowInner* aWindow, + ReportingObserverCallback& aCallback, + const nsTArray<nsString>& aTypes, + bool aBuffered); + + JSObject* + WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + nsPIDOMWindowInner* + GetParentObject() const + { + return mWindow; + } + + void + Observe(); + + void + Disconnect(); + + void + TakeRecords(nsTArray<RefPtr<Report>>& aRecords); + + void + MaybeReport(Report* aReport); + + void + MaybeNotify(); + +private: + ~ReportingObserver(); + + void + Shutdown(); + + nsTArray<RefPtr<Report>> mReports; + + nsCOMPtr<nsPIDOMWindowInner> mWindow; + RefPtr<ReportingObserverCallback> mCallback; + nsTArray<nsString> mTypes; + bool mBuffered; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_ReportingObserver_h
new file mode 100644 --- /dev/null +++ b/dom/reporting/ReportingUtils.cpp @@ -0,0 +1,33 @@ +/* -*- 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 "mozilla/dom/ReportingUtils.h" +#include "mozilla/dom/ReportBody.h" +#include "mozilla/dom/Report.h" +#include "nsAtom.h" +#include "nsPIDOMWindow.h" + +namespace mozilla { +namespace dom { + +/* static */ void +ReportingUtils::Report(nsPIDOMWindowInner* aWindow, + nsAtom* aType, + const nsAString& aURL, + ReportBody* aBody) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aWindow); + MOZ_ASSERT(aBody); + + RefPtr<mozilla::dom::Report> report = + new mozilla::dom::Report(aWindow, nsDependentAtomString(aType), aURL, + aBody); + aWindow->BroadcastReport(report); +} + +} // dom namespace +} // mozilla namespace
new file mode 100644 --- /dev/null +++ b/dom/reporting/ReportingUtils.h @@ -0,0 +1,32 @@ +/* -*- 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 mozilla_dom_ReportingUtils_h +#define mozilla_dom_ReportingUtils_h + +#include "nsString.h" + +class nsIPDOMWindowInner; + +namespace mozilla { +namespace dom { + +class ReportBody; + +class ReportingUtils final +{ +public: + static void + Report(nsPIDOMWindowInner* aWindow, + nsAtom* aType, + const nsAString& aURL, + ReportBody* aBody); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ReportingUtils_h
new file mode 100644 --- /dev/null +++ b/dom/reporting/TestingDeprecatedInterface.cpp @@ -0,0 +1,59 @@ +/* -*- 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 "mozilla/dom/TestingDeprecatedInterface.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestingDeprecatedInterface, mGlobal) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(TestingDeprecatedInterface) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TestingDeprecatedInterface) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestingDeprecatedInterface) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +/* static */ already_AddRefed<TestingDeprecatedInterface> +TestingDeprecatedInterface::Constructor(const GlobalObject& aGlobal, + ErrorResult& aRv) +{ + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + MOZ_ASSERT(global); + + RefPtr<TestingDeprecatedInterface> obj = + new TestingDeprecatedInterface(global); + return obj.forget(); +} + +TestingDeprecatedInterface::TestingDeprecatedInterface(nsIGlobalObject* aGlobal) + : mGlobal(aGlobal) +{} + + +TestingDeprecatedInterface::~TestingDeprecatedInterface() = default; + +JSObject* +TestingDeprecatedInterface::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) +{ + return TestingDeprecatedInterface_Binding::Wrap(aCx, this, aGivenProto); +} + +void +TestingDeprecatedInterface::DeprecatedMethod() const +{} + +bool +TestingDeprecatedInterface::DeprecatedAttribute() const +{ + return true; +} + +} // dom namespace +} // mozilla namespace
new file mode 100644 --- /dev/null +++ b/dom/reporting/TestingDeprecatedInterface.h @@ -0,0 +1,55 @@ +/* -*- 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 mozilla_dom_TestingDeprecatedInterface_h +#define mozilla_dom_TestingDeprecatedInterface_h + +#include "mozilla/Attributes.h" +#include "mozilla/dom/BindingUtils.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" + +namespace mozilla { +namespace dom { + +class TestingDeprecatedInterface final : public nsISupports + , public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestingDeprecatedInterface) + + static already_AddRefed<TestingDeprecatedInterface> + Constructor(const GlobalObject& aGlobal, + ErrorResult& aRv); + + JSObject* + WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + nsIGlobalObject* + GetParentObject() const + { + return mGlobal; + } + + void + DeprecatedMethod() const; + + bool + DeprecatedAttribute() const; + +private: + explicit TestingDeprecatedInterface(nsIGlobalObject* aGlobal); + ~TestingDeprecatedInterface(); + + nsCOMPtr<nsIGlobalObject> mGlobal; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_TestingDeprecatedInterface_h +
new file mode 100644 --- /dev/null +++ b/dom/reporting/moz.build @@ -0,0 +1,34 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS.mozilla.dom = [ + 'DeprecationReportBody.h', + 'FeaturePolicyViolationReportBody.h', + 'Report.h', + 'ReportBody.h', + 'ReportingObserver.h', + 'ReportingUtils.h', + 'TestingDeprecatedInterface.h', +] + +UNIFIED_SOURCES += [ + 'DeprecationReportBody.cpp', + 'FeaturePolicyViolationReportBody.cpp', + 'Report.cpp', + 'ReportBody.cpp', + 'ReportingObserver.cpp', + 'ReportingUtils.cpp', + 'TestingDeprecatedInterface.cpp', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +with Files('**'): + BUG_COMPONENT = ('DOM', 'Security') + +FINAL_LIBRARY = 'xul' + +MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
new file mode 100644 --- /dev/null +++ b/dom/reporting/tests/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "plugin:mozilla/mochitest-test", + ], +};
new file mode 100644 --- /dev/null +++ b/dom/reporting/tests/mochitest.ini @@ -0,0 +1,7 @@ +[DEFAULT] +prefs = + dom.reporting.enabled=true + dom.reporting.testing.enabled=true + +[test_deprecated.html] +[test_memoryPressure.html]
new file mode 100644 --- /dev/null +++ b/dom/reporting/tests/test_deprecated.html @@ -0,0 +1,143 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Deprecated reports</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript"> + +let testingInterface; + +(new Promise(resolve => { + info("Testing DeprecatedTestingInterface report"); + let obs = new ReportingObserver((reports, o) => { + is(obs, o, "Same observer!"); + ok(reports.length == 1, "We have 1 report"); + + let report = reports[0]; + is(report.type, "deprecation", "Deprecation report received"); + is(report.url, location.href, "URL is window.location"); + ok(!!report.body, "The report has a body"); + ok(report.body instanceof DeprecationReportBody, "Correct type for the body"); + is(report.body.id, "DeprecatedTestingInterface", "report.body.id matches DeprecatedTestingMethod"); + ok(!report.body.anticipatedRemoval, "We don't have a anticipatedRemoval"); + ok(report.body.message.includes("TestingDeprecatedInterface"), "We have a message"); + is(report.body.sourceFile, location.href, "We have a sourceFile"); + is(report.body.lineNumber, 40, "We have a lineNumber"); + is(report.body.columnNumber, 21, "We have a columnNumber"); + + obs.disconnect(); + resolve(); + }); + ok(!!obs, "ReportingObserver is a thing"); + + obs.observe(); + ok(true, "ReportingObserver.observe() is callable"); + + testingInterface = new TestingDeprecatedInterface(); + ok(true, "Created a deprecated interface"); +})) + +.then(() => { + info("Testing DeprecatedTestingMethod report"); + return new Promise(resolve => { + let obs = new ReportingObserver((reports, o) => { + is(obs, o, "Same observer!"); + ok(reports.length == 1, "We have 1 report"); + + let report = reports[0]; + is(report.type, "deprecation", "Deprecation report received"); + is(report.url, location.href, "URL is window.location"); + ok(!!report.body, "The report has a body"); + ok(report.body instanceof DeprecationReportBody, "Correct type for the body"); + is(report.body.id, "DeprecatedTestingMethod", "report.body.id matches DeprecatedTestingMethod"); + ok(!report.body.anticipatedRemoval, "We don't have a anticipatedRemoval"); + ok(report.body.message.includes("TestingDeprecatedInterface.deprecatedMethod"), "We have a message"); + is(report.body.sourceFile, location.href, "We have a sourceFile"); + is(report.body.lineNumber, 71, "We have a lineNumber"); + is(report.body.columnNumber, 4, "We have a columnNumber"); + + obs.disconnect(); + resolve(); + }); + ok(!!obs, "ReportingObserver is a thing"); + + obs.observe(); + ok(true, "ReportingObserver.observe() is callable"); + + testingInterface.deprecatedMethod(); + ok(true, "Run a deprecated method."); + }); +}) + +.then(() => { + info("Testing DeprecatedTestingAttribute report"); + return new Promise(resolve => { + let obs = new ReportingObserver((reports, o) => { + is(obs, o, "Same observer!"); + ok(reports.length == 1, "We have 1 report"); + + let report = reports[0]; + is(report.type, "deprecation", "Deprecation report received"); + is(report.url, location.href, "URL is window.location"); + ok(!!report.body, "The report has a body"); + ok(report.body instanceof DeprecationReportBody, "Correct type for the body"); + is(report.body.id, "DeprecatedTestingAttribute", "report.body.id matches DeprecatedTestingAttribute"); + ok(!report.body.anticipatedRemoval, "We don't have a anticipatedRemoval"); + ok(report.body.message.includes("TestingDeprecatedInterface.deprecatedAttribute"), "We have a message"); + is(report.body.sourceFile, location.href, "We have a sourceFile"); + is(report.body.lineNumber, 103, "We have a lineNumber"); + is(report.body.columnNumber, 4, "We have a columnNumber"); + + obs.disconnect(); + resolve(); + }); + ok(!!obs, "ReportingObserver is a thing"); + + obs.observe(); + ok(true, "ReportingObserver.observe() is callable"); + + ok(testingInterface.deprecatedAttribute, "Attributed called"); + }); +}) + +.then(() => { + info("Testing ReportingObserver.takeRecords()"); + let p = new Promise(resolve => { + let obs = new ReportingObserver((reports, o) => { + is(obs, o, "Same observer!"); + resolve(obs); + }); + ok(!!obs, "ReportingObserver is a thing"); + + obs.observe(); + ok(true, "ReportingObserver.observe() is callable"); + + testingInterface.deprecatedMethod(); + ok(true, "Run a deprecated method."); + }); + + return p.then(obs => { + let reports = obs.takeRecords(); + is(reports.length, 0, "No reports after an callback"); + + testingInterface.deprecatedAttribute + 1; + + reports = obs.takeRecords(); + ok(reports.length >= 1, "We have at least 1 report"); + + reports = obs.takeRecords(); + is(reports.length, 0, "No more reports"); + }); +}) + +.then(() => { SimpleTest.finish(); }); + +SimpleTest.waitForExplicitFinish(); + +</script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/reporting/tests/test_memoryPressure.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for ReportingObserver + memory-pressure</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript"> + +info("Testing TakeRecords() without memory-pressure"); +let o = new ReportingObserver(() => {}); +o.observe(); + +new TestingDeprecatedInterface(); +let r = o.takeRecords(); +is(r.length, 1, "We have 1 report"); + +r = o.takeRecords(); +is(r.length, 0, "We have 0 reports after a takeRecords()"); + +info("Testing DeprecatedTestingMethod report"); + +new TestingDeprecatedInterface(); +SpecialPowers.Services.obs.notifyObservers(null, "memory-pressure", "heap-minimize"); + +r = o.takeRecords(); +is(r.length, 0, "We have 0 reports after a memory-pressure"); + +</script> +</body> +</html>
--- a/dom/script/ScriptLoader.cpp +++ b/dom/script/ScriptLoader.cpp @@ -11,17 +11,17 @@ #include "ModuleLoadRequest.h" #include "ModuleScript.h" #include "prsystem.h" #include "jsapi.h" #include "jsfriendapi.h" #include "js/CompilationAndEvaluation.h" #include "js/OffThreadScriptCompilation.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "js/Utility.h" #include "xpcpublic.h" #include "nsCycleCollectionParticipant.h" #include "nsIContent.h" #include "nsJSUtils.h" #include "mozilla/dom/DocGroup.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/ScriptSettings.h" @@ -63,17 +63,17 @@ #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/Attributes.h" #include "mozilla/Telemetry.h" #include "mozilla/TimeStamp.h" #include "mozilla/Unused.h" #include "nsIScriptError.h" #include "nsIOutputStream.h" -using JS::SourceBufferHolder; +using JS::SourceText; using mozilla::Telemetry::LABELS_DOM_SCRIPT_PRELOAD_RESULT; namespace mozilla { namespace dom { LazyLogModule ScriptLoader::gCspPRLog("CSP"); LazyLogModule ScriptLoader::gScriptLoaderLog("ScriptLoader"); @@ -1928,40 +1928,56 @@ ScriptLoader::CompileOffThreadOrProcessR if (couldCompile) { return NS_OK; } return ProcessRequest(aRequest); } -mozilla::Maybe<SourceBufferHolder> +mozilla::Maybe<SourceText<char16_t>> ScriptLoader::GetScriptSource(JSContext* aCx, ScriptLoadRequest* aRequest) { - // Return a SourceBufferHolder object holding the script's source text. - // Ownership of the buffer is transferred to the resulting SourceBufferHolder. + // Return a SourceText<char16_t> object holding the script's source text. + // Ownership of the buffer is transferred to the resulting holder. // If there's no script text, we try to get it from the element if (aRequest->mIsInline) { nsAutoString inlineData; aRequest->Element()->GetScriptText(inlineData); size_t nbytes = inlineData.Length() * sizeof(char16_t); JS::UniqueTwoByteChars chars(static_cast<char16_t*>(JS_malloc(aCx, nbytes))); if (!chars) { return Nothing(); } memcpy(chars.get(), inlineData.get(), nbytes); - return Some(SourceBufferHolder(std::move(chars), inlineData.Length())); + + SourceText<char16_t> srcBuf; + if (!srcBuf.init(aCx, std::move(chars), inlineData.Length())) { + return Nothing(); + } + + return Some(SourceText<char16_t>(std::move(srcBuf))); } size_t length = aRequest->ScriptText().length(); JS::UniqueTwoByteChars chars(aRequest->ScriptText().extractOrCopyRawBuffer()); - return Some(SourceBufferHolder(std::move(chars), length)); + if (!chars) { + JS_ReportOutOfMemory(aCx); + return Nothing(); + } + + SourceText<char16_t> srcBuf; + if (!srcBuf.init(aCx, std::move(chars), length)) { + return Nothing(); + } + + return Some(SourceText<char16_t>(std::move(srcBuf))); } nsresult ScriptLoader::ProcessRequest(ScriptLoadRequest* aRequest) { LOG(("ScriptLoadRequest (%p): Process request", aRequest)); NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
--- a/dom/script/ScriptLoader.h +++ b/dom/script/ScriptLoader.h @@ -27,17 +27,19 @@ #include "mozilla/MozPromise.h" #include "mozilla/net/ReferrerPolicy.h" #include "mozilla/StaticPrefs.h" #include "mozilla/Vector.h" class nsIURI; namespace JS { - class SourceBufferHolder; + +template<typename UnitT> class SourceText; + } // namespace JS namespace mozilla { namespace dom { class AutoJSAPI; class ModuleLoadRequest; class ModuleScript; @@ -502,18 +504,18 @@ private: nsresult aStatus); void AddDeferRequest(ScriptLoadRequest* aRequest); void AddAsyncRequest(ScriptLoadRequest* aRequest); bool MaybeRemovedDeferRequests(); void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest); - mozilla::Maybe<JS::SourceBufferHolder> GetScriptSource(JSContext* aCx, - ScriptLoadRequest* aRequest); + mozilla::Maybe<JS::SourceText<char16_t>> + GetScriptSource(JSContext* aCx, ScriptLoadRequest* aRequest); void SetModuleFetchStarted(ModuleLoadRequest *aRequest); void SetModuleFetchFinishedAndResumeWaitingRequests(ModuleLoadRequest* aRequest, nsresult aResult); bool IsFetchingModule(ModuleLoadRequest* aRequest) const; bool ModuleMapContainsURL(nsIURI* aURL) const;
--- a/dom/security/featurepolicy/FeaturePolicy.h +++ b/dom/security/featurepolicy/FeaturePolicy.h @@ -45,16 +45,19 @@ * * When FeaturePolicy must decide if feature X is allowed or denied for the * current origin, it checks if the parent context denied that feature. * If not, it checks if there is a Feature object for that * feature named X and if the origin is allowed or not. * * From a C++ point of view, use FeaturePolicyUtils to obtain the list of * features and to check if they are allowed in the current context. + * + * dom.security.featurePolicy.header.enabled pref can be used to disable the + * HTTP header support. **/ class nsIDocument; class nsIHttpChannel; class nsINode; namespace mozilla { namespace dom {
--- a/dom/security/featurepolicy/FeaturePolicyUtils.cpp +++ b/dom/security/featurepolicy/FeaturePolicyUtils.cpp @@ -1,18 +1,21 @@ /* -*- 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 "FeaturePolicyUtils.h" #include "mozilla/dom/FeaturePolicy.h" +#include "mozilla/dom/FeaturePolicyViolationReportBody.h" +#include "mozilla/dom/ReportingUtils.h" #include "mozilla/StaticPrefs.h" #include "nsIDocument.h" +#include "nsIURIFixup.h" namespace mozilla { namespace dom { struct FeatureMap { const char* mFeatureName; FeaturePolicyUtils::FeaturePolicyValue mDefaultAllowList; }; @@ -82,13 +85,78 @@ FeaturePolicyUtils::IsFeatureAllowed(nsI if (!aDocument->IsHTMLDocument()) { return true; } FeaturePolicy* policy = aDocument->Policy(); MOZ_ASSERT(policy); - return policy->AllowsFeatureInternal(aFeatureName, policy->DefaultOrigin()); + if (policy->AllowsFeatureInternal(aFeatureName, policy->DefaultOrigin())) { + return true; + } + + ReportViolation(aDocument, aFeatureName); + return false; +} + +/* static */ void +FeaturePolicyUtils::ReportViolation(nsIDocument* aDocument, + const nsAString& aFeatureName) +{ + MOZ_ASSERT(aDocument); + + nsCOMPtr<nsIURI> uri = aDocument->GetDocumentURI(); + if (NS_WARN_IF(!uri)) { + return; + } + + // Strip the URL of any possible username/password and make it ready to be + // presented in the UI. + nsCOMPtr<nsIURIFixup> urifixup = services::GetURIFixup(); + if (NS_WARN_IF(!urifixup)) { + return; + } + + nsCOMPtr<nsIURI> exposableURI; + nsresult rv = urifixup->CreateExposableURI(uri, getter_AddRefs(exposableURI)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsAutoCString spec; + rv = exposableURI->GetSpec(spec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + JSContext* cx = nsContentUtils::GetCurrentJSContext(); + if (NS_WARN_IF(!cx)) { + return; + } + + nsAutoCString fileName; + Nullable<int32_t> lineNumber; + Nullable<int32_t> columnNumber; + uint32_t line = 0; + uint32_t column = 0; + if (nsJSUtils::GetCallingLocation(cx, fileName, &line, &column)) { + lineNumber.SetValue(static_cast<int32_t>(line)); + columnNumber.SetValue(static_cast<int32_t>(column)); + } + + nsPIDOMWindowInner* window = aDocument->GetInnerWindow(); + if (NS_WARN_IF(!window)) { + return; + } + + RefPtr<FeaturePolicyViolationReportBody> body = + new FeaturePolicyViolationReportBody(window, aFeatureName, + NS_ConvertUTF8toUTF16(fileName), + lineNumber, columnNumber, + NS_LITERAL_STRING("enforce")); + + ReportingUtils::Report(window, + nsGkAtoms::featurePolicyViolation, + NS_ConvertUTF8toUTF16(spec), body); } } // dom namespace } // mozilla namespace
--- a/dom/security/featurepolicy/FeaturePolicyUtils.h +++ b/dom/security/featurepolicy/FeaturePolicyUtils.h @@ -43,14 +43,19 @@ public: // Runs aCallback for each known feature policy, with the feature name as // argument. static void ForEachFeature(const std::function<void(const char*)>& aCallback); // Returns the default policy value for aFeatureName. static FeaturePolicyValue DefaultAllowListFeature(const nsAString& aFeatureName); + +private: + static void + ReportViolation(nsIDocument* aDocument, + const nsAString& aFeatureName); }; } // dom namespace } // mozilla namespace #endif // mozilla_dom_FeaturePolicyUtils_h
--- a/dom/security/featurepolicy/test/mochitest/mochitest.ini +++ b/dom/security/featurepolicy/test/mochitest/mochitest.ini @@ -1,8 +1,10 @@ [DEFAULT] prefs = dom.security.featurePolicy.enabled=true + dom.security.featurePolicy.header.enabled=true + dom.security.featurePolicy.webidl.enabled=true support-files = empty.html test_parser.html^headers^ [test_parser.html]
--- a/dom/security/nsCSPContext.cpp +++ b/dom/security/nsCSPContext.cpp @@ -804,22 +804,24 @@ nsCSPContext::logToConsole(const char* a void StripURIForReporting(nsIURI* aURI, nsIURI* aSelfURI, nsACString& outStrippedURI) { // 1) If the origin of uri is a globally unique identifier (for example, // aURI has a scheme of data, blob, or filesystem), then return the // ASCII serialization of uri’s scheme. - bool isHttpOrFtp = - (NS_SUCCEEDED(aURI->SchemeIs("http", &isHttpOrFtp)) && isHttpOrFtp) || - (NS_SUCCEEDED(aURI->SchemeIs("https", &isHttpOrFtp)) && isHttpOrFtp) || - (NS_SUCCEEDED(aURI->SchemeIs("ftp", &isHttpOrFtp)) && isHttpOrFtp); + bool isHttpFtpOrWs = + (NS_SUCCEEDED(aURI->SchemeIs("http", &isHttpFtpOrWs)) && isHttpFtpOrWs) || + (NS_SUCCEEDED(aURI->SchemeIs("https", &isHttpFtpOrWs)) && isHttpFtpOrWs) || + (NS_SUCCEEDED(aURI->SchemeIs("ftp", &isHttpFtpOrWs)) && isHttpFtpOrWs) || + (NS_SUCCEEDED(aURI->SchemeIs("ws", &isHttpFtpOrWs)) && isHttpFtpOrWs) || + (NS_SUCCEEDED(aURI->SchemeIs("wss", &isHttpFtpOrWs)) && isHttpFtpOrWs); - if (!isHttpOrFtp) { + if (!isHttpFtpOrWs) { // not strictly spec compliant, but what we really care about is // http/https and also ftp. If it's not http/https or ftp, then treat aURI // as if it's a globally unique identifier and just return the scheme. aURI->GetScheme(outStrippedURI); return; } // Return uri, with any fragment component removed.
--- a/dom/tests/mochitest/geolocation/test_featurePolicy.html +++ b/dom/tests/mochitest/geolocation/test_featurePolicy.html @@ -40,12 +40,16 @@ function nextTest() { document.body.removeChild(iframe); SimpleTest.executeSoon(nextTest); }; iframe.src = "file_featurePolicy.html"; document.body.appendChild(iframe); } -SpecialPowers.pushPrefEnv({"set": [["dom.security.featurePolicy.enabled", true]]}).then(nextTest); +SpecialPowers.pushPrefEnv({"set": [ + ["dom.security.featurePolicy.enabled", true], + ["dom.security.featurePolicy.header.enabled", true], + ["dom.security.featurePolicy.webidl.enabled", true], +]}).then(nextTest); </script> </body> </html>
--- a/dom/webidl/Document.webidl +++ b/dom/webidl/Document.webidl @@ -545,11 +545,11 @@ Document implements TouchEventHandlers; Document implements ParentNode; Document implements OnErrorEventHandlerForNodes; Document implements GeometryUtils; Document implements FontFaceSource; Document implements DocumentOrShadowRoot; // https://wicg.github.io/feature-policy/#policy partial interface Document { - [SameObject, Pref="dom.security.featurePolicy.enabled"] + [SameObject, Pref="dom.security.featurePolicy.webidl.enabled"] readonly attribute Policy policy; };
--- a/dom/webidl/FeaturePolicy.webidl +++ b/dom/webidl/FeaturePolicy.webidl @@ -8,8 +8,17 @@ */ [NoInterfaceObject] interface Policy { boolean allowsFeature(DOMString feature, optional DOMString origin); sequence<DOMString> allowedFeatures(); sequence<DOMString> getAllowlistForFeature(DOMString feature); }; + +[Func="mozilla::dom::DOMPrefs::dom_reporting_featurePolicy_enabled"] +interface FeaturePolicyViolationReportBody : ReportBody { + readonly attribute DOMString featureId; + readonly attribute DOMString? sourceFile; + readonly attribute long? lineNumber; + readonly attribute long? columnNumber; + readonly attribute DOMString disposition; +};
--- a/dom/webidl/HTMLIFrameElement.webidl +++ b/dom/webidl/HTMLIFrameElement.webidl @@ -67,14 +67,14 @@ partial interface HTMLIFrameElement { attribute boolean mozbrowser; }; HTMLIFrameElement implements MozFrameLoaderOwner; HTMLIFrameElement implements BrowserElement; // https://wicg.github.io/feature-policy/#policy partial interface HTMLIFrameElement { - [SameObject, Pref="dom.security.featurePolicy.enabled"] + [SameObject, Pref="dom.security.featurePolicy.webidl.enabled"] readonly attribute Policy policy; [CEReactions, SetterThrows, Pure, Pref="dom.security.featurePolicy.enabled"] attribute DOMString allow; };
--- a/dom/webidl/PerformanceTiming.webidl +++ b/dom/webidl/PerformanceTiming.webidl @@ -34,16 +34,20 @@ interface PerformanceTiming { readonly attribute unsigned long long loadEventEnd; // This is a Chrome proprietary extension and not part of the // performance/navigation timing specification. // Returns 0 if a non-blank paint has not happened. [Pref="dom.performance.time_to_non_blank_paint.enabled"] readonly attribute unsigned long long timeToNonBlankPaint; + // Returns 0 if a contentful paint has not happened. + [Pref="dom.performance.time_to_contentful_paint.enabled"] + readonly attribute unsigned long long timeToContentfulPaint; + // This is a Mozilla proprietary extension and not part of the // performance/navigation timing specification. It marks the // completion of the first presentation flush after DOMContentLoaded. [Pref="dom.performance.time_to_dom_content_flushed.enabled"] readonly attribute unsigned long long timeToDOMContentFlushed; // This is a Chrome proprietary extension and not part of the // performance/navigation timing specification.
new file mode 100644 --- /dev/null +++ b/dom/webidl/Reporting.webidl @@ -0,0 +1,57 @@ +/* -*- Mode: IDL; tab-width: 2; 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/. + * + * The origin of this IDL file is + * https://w3c.github.io/reporting/#interface-reporting-observer + */ + +[Func="mozilla::dom::DOMPrefs::dom_reporting_enabled"] +interface ReportBody { +}; + +[Func="mozilla::dom::DOMPrefs::dom_reporting_enabled"] +interface Report { + readonly attribute DOMString type; + readonly attribute DOMString url; + readonly attribute ReportBody? body; +}; + +[Constructor(ReportingObserverCallback callback, optional ReportingObserverOptions options), + Func="mozilla::dom::DOMPrefs::dom_reporting_enabled"] +interface ReportingObserver { + void observe(); + void disconnect(); + ReportList takeRecords(); +}; + +callback ReportingObserverCallback = void (sequence<Report> reports, ReportingObserver observer); + +dictionary ReportingObserverOptions { + sequence<DOMString> types; + boolean buffered = false; +}; + +typedef sequence<Report> ReportList; + +[Func="mozilla::dom::DOMPrefs::dom_reporting_enabled"] +interface DeprecationReportBody : ReportBody { + readonly attribute DOMString id; + readonly attribute Date? anticipatedRemoval; + readonly attribute DOMString message; + readonly attribute DOMString? sourceFile; + readonly attribute unsigned long? lineNumber; + readonly attribute unsigned long? columnNumber; +}; + +[Constructor(), Deprecated="DeprecatedTestingInterface", + Func="mozilla::dom::DOMPrefs::dom_reporting_testing_enabled", + Exposed=(Window,DedicatedWorker)] +interface TestingDeprecatedInterface { + [Deprecated="DeprecatedTestingMethod"] + void deprecatedMethod(); + + [Deprecated="DeprecatedTestingAttribute"] + readonly attribute boolean deprecatedAttribute; +};
--- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -735,16 +735,17 @@ WEBIDL_FILES = [ 'PushEvent.webidl', 'PushManager.webidl', 'PushManager.webidl', 'PushMessageData.webidl', 'PushSubscription.webidl', 'PushSubscriptionOptions.webidl', 'RadioNodeList.webidl', 'Range.webidl', + 'Reporting.webidl', 'Request.webidl', 'Response.webidl', 'RTCStatsReport.webidl', 'Screen.webidl', 'ScreenOrientation.webidl', 'ScriptProcessorNode.webidl', 'ScrollAreaEvent.webidl', 'Selection.webidl',
--- a/dom/workers/ScriptLoader.cpp +++ b/dom/workers/ScriptLoader.cpp @@ -1,16 +1,18 @@ /* -*- 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 "ScriptLoader.h" +#include <algorithm> + #include "nsIChannel.h" #include "nsIContentPolicy.h" #include "nsIContentSecurityPolicy.h" #include "nsIDocShell.h" #include "nsIHttpChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIInputStreamPump.h" #include "nsIIOService.h" @@ -21,17 +23,17 @@ #include "nsIStreamLoader.h" #include "nsIStreamListenerTee.h" #include "nsIThreadRetargetableRequest.h" #include "nsIURI.h" #include "jsapi.h" #include "jsfriendapi.h" #include "js/CompilationAndEvaluation.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "nsError.h" #include "nsContentPolicyUtils.h" #include "nsContentUtils.h" #include "nsDocShellCID.h" #include "nsISupportsPrimitives.h" #include "nsNetUtil.h" #include "nsIPipe.h" #include "nsIOutputStream.h" @@ -2119,21 +2121,29 @@ ScriptExecutorRunnable::WorkerRun(JSCont JS::CompileOptions options(aCx); options.setFileAndLine(filename.get(), 1) .setNoScriptRval(true); MOZ_ASSERT(loadInfo.mMutedErrorFlag.isSome()); options.setMutedErrors(loadInfo.mMutedErrorFlag.valueOr(true)); - JS::SourceBufferHolder srcBuf(loadInfo.mScriptTextBuf, - loadInfo.mScriptTextLength, - JS::SourceBufferHolder::GiveOwnership); - loadInfo.mScriptTextBuf = nullptr; - loadInfo.mScriptTextLength = 0; + // Pass ownership of the data, first to local variables, then to the + // UniqueTwoByteChars moved into the |init| function. + size_t dataLength = 0; + char16_t* data = nullptr; + + std::swap(dataLength, loadInfo.mScriptTextLength); + std::swap(data, loadInfo.mScriptTextBuf); + + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(aCx, JS::UniqueTwoByteChars(data), dataLength)) { + mScriptLoader.mRv.StealExceptionFromJSContext(aCx); + return true; + } // Our ErrorResult still shouldn't be a failure. MOZ_ASSERT(!mScriptLoader.mRv.Failed(), "Who failed it and why?"); JS::Rooted<JS::Value> unused(aCx); if (!JS::Evaluate(aCx, options, srcBuf, &unused)) { mScriptLoader.mRv.StealExceptionFromJSContext(aCx); return true; }
--- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -4,17 +4,17 @@ * 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 "WorkerPrivate.h" #include "js/CompilationAndEvaluation.h" #include "js/LocaleSensitive.h" #include "js/MemoryMetrics.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "MessageEventRunnable.h" #include "mozilla/ScopeExit.h" #include "mozilla/StaticPrefs.h" #include "mozilla/dom/BlobURLProtocolHandler.h" #include "mozilla/dom/ClientManager.h" #include "mozilla/dom/ClientSource.h" #include "mozilla/dom/ClientState.h" #include "mozilla/dom/Console.h" @@ -4897,22 +4897,25 @@ WorkerPrivate::RunExpiredTimeouts(JSCont uint32_t lineNo = 0, dummyColumn = 0; info->mHandler->GetLocation(&filename, &lineNo, &dummyColumn); JS::CompileOptions options(aes.cx()); options.setFileAndLine(filename, lineNo).setNoScriptRval(true); JS::Rooted<JS::Value> unused(aes.cx()); - JS::SourceBufferHolder srcBuf(script.BeginReading(), script.Length(), - JS::SourceBufferHolder::NoOwnership); - if (!JS::Evaluate(aes.cx(), options, srcBuf, &unused) && - !JS_IsExceptionPending(aCx)) { - retval = false; - break; + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(aes.cx(), script.BeginReading(), script.Length(), + JS::SourceOwnership::Borrowed) || + !JS::Evaluate(aes.cx(), options, srcBuf, &unused)) + { + if (!JS_IsExceptionPending(aCx)) { + retval = false; + break; + } } } else { ErrorResult rv; JS::Rooted<JS::Value> ignoredVal(aCx); callback->Call(GlobalScope(), info->mHandler->GetArgs(), &ignoredVal, rv, reason); if (rv.IsUncatchableException()) { rv.SuppressException();
--- a/dom/worklet/Worklet.cpp +++ b/dom/worklet/Worklet.cpp @@ -12,17 +12,17 @@ #include "mozilla/dom/BlobBinding.h" #include "mozilla/dom/Fetch.h" #include "mozilla/dom/PromiseNativeHandler.h" #include "mozilla/dom/Response.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/ScriptLoader.h" #include "mozilla/dom/WorkletImpl.h" #include "js/CompilationAndEvaluation.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "nsIInputStreamPump.h" #include "nsIThreadRetargetableRequest.h" #include "nsNetUtil.h" #include "xpcprivate.h" namespace mozilla { namespace dom { @@ -396,20 +396,20 @@ ExecutionRunnable::RunOnWorkletThread() JS::CompileOptions compileOptions(cx); compileOptions.setIntroductionType("Worklet"); compileOptions.setFileAndLine(url.get(), 0); compileOptions.setIsRunOnce(true); compileOptions.setNoScriptRval(true); JSAutoRealm ar(cx, globalObj); - JS::SourceBufferHolder buffer(mScriptBuffer.release(), mScriptLength, - JS::SourceBufferHolder::GiveOwnership); JS::Rooted<JS::Value> unused(cx); - if (!JS::Evaluate(cx, compileOptions, buffer, &unused)) { + JS::SourceText<char16_t> buffer; + if (!buffer.init(cx, std::move(mScriptBuffer), mScriptLength) || + !JS::Evaluate(cx, compileOptions, buffer, &unused)) { ErrorResult error; error.MightThrowJSException(); error.StealExceptionFromJSContext(cx); mResult = error.StealNSResult(); return; } // All done.
--- a/dom/xul/XULDocument.cpp +++ b/dom/xul/XULDocument.cpp @@ -18,16 +18,18 @@ which may be lazily created during frame construction, the document observer methods will never be called because we'll be adding the XUL nodes into the content model "quietly". */ #include "mozilla/ArrayUtils.h" +#include <algorithm> + #include "XULDocument.h" #include "nsError.h" #include "nsIBoxObject.h" #include "nsIChromeRegistry.h" #include "nsView.h" #include "nsViewManager.h" #include "nsIContentViewer.h" @@ -84,17 +86,17 @@ #include "mozilla/dom/XULDocumentBinding.h" #include "mozilla/dom/XULPersist.h" #include "mozilla/EventDispatcher.h" #include "mozilla/LoadInfo.h" #include "mozilla/Preferences.h" #include "nsTextNode.h" #include "nsJSUtils.h" #include "js/CompilationAndEvaluation.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "mozilla/dom/URL.h" #include "nsIContentPolicy.h" #include "mozAutoDocUpdate.h" #include "xpcpublic.h" #include "mozilla/StyleSheet.h" #include "mozilla/StyleSheetInlines.h" #include "nsIConsoleService.h" @@ -1278,32 +1280,29 @@ XULDocument::OnStreamComplete(nsIStreamL !mOffThreadCompileStringBuf), "XULDocument can't load multiple scripts at once"); rv = ScriptLoader::ConvertToUTF16(channel, string, stringLen, EmptyString(), this, mOffThreadCompileStringBuf, mOffThreadCompileStringLength); if (NS_SUCCEEDED(rv)) { - // Attempt to give ownership of the buffer to the JS engine. If - // we hit offthread compilation, however, we will have to take it - // back below in order to keep the memory alive until compilation - // completes. - JS::SourceBufferHolder srcBuf(mOffThreadCompileStringBuf, - mOffThreadCompileStringLength, - JS::SourceBufferHolder::GiveOwnership); - mOffThreadCompileStringBuf = nullptr; - mOffThreadCompileStringLength = 0; + // Pass ownership of the buffer, carefully emptying the existing + // fields in the process. Note that the |Compile| function called + // below always takes ownership of the buffer. + char16_t* units = nullptr; + size_t unitsLength = 0; - rv = mCurrentScriptProto->Compile(srcBuf, uri, 1, this, this); + std::swap(units, mOffThreadCompileStringBuf); + std::swap(unitsLength, mOffThreadCompileStringLength); + + rv = mCurrentScriptProto->Compile(units, unitsLength, + JS::SourceOwnership::TakeOwnership, + uri, 1, this, this); if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->HasScriptObject()) { - // We will be notified via OnOffThreadCompileComplete when the - // compile finishes. The JS engine has taken ownership of the - // source buffer. - MOZ_RELEASE_ASSERT(!srcBuf.ownsChars()); mOffThreadCompiling = true; BlockOnload(); return NS_OK; } } } return OnScriptCompileComplete(mCurrentScriptProto->GetScriptObject(), rv);
--- a/dom/xul/nsXULContentSink.cpp +++ b/dom/xul/nsXULContentSink.cpp @@ -500,19 +500,21 @@ XULContentSinkImpl::HandleEndElement(con nsXULPrototypeScript* script = static_cast<nsXULPrototypeScript*>(node.get()); // If given a src= attribute, we must ignore script tag content. if (!script->mSrcURI && !script->HasScriptObject()) { nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument); script->mOutOfLine = false; - if (doc) - script->Compile(mText, mTextLength, mDocumentURL, + if (doc) { + script->Compile(mText, mTextLength, + JS::SourceOwnership::Borrowed, mDocumentURL, script->mLineNo, doc); + } } FlushText(false); } break; default: NS_ERROR("didn't expect that");
--- a/dom/xul/nsXULElement.cpp +++ b/dom/xul/nsXULElement.cpp @@ -14,17 +14,17 @@ #include "nsIDOMXULSelectCntrlItemEl.h" #include "nsIDocument.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/EventListenerManager.h" #include "mozilla/EventStateManager.h" #include "mozilla/EventStates.h" #include "mozilla/DeclarationBlock.h" #include "js/CompilationAndEvaluation.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "nsFocusManager.h" #include "nsHTMLStyleSheet.h" #include "nsNameSpaceManager.h" #include "nsIObjectInputStream.h" #include "nsIObjectOutputStream.h" #include "nsIPresShell.h" #include "nsIPrincipal.h" #include "nsIScriptContext.h" @@ -2273,28 +2273,41 @@ OffThreadScriptReceiverCallback(JS::OffT // may be invoked off the main thread. nsIOffThreadScriptReceiver* aReceiver = static_cast<nsIOffThreadScriptReceiver*>(aCallbackData); RefPtr<NotifyOffThreadScriptCompletedRunnable> notify = new NotifyOffThreadScriptCompletedRunnable(aReceiver, aToken); NS_DispatchToMainThread(notify); } nsresult -nsXULPrototypeScript::Compile(JS::SourceBufferHolder& aSrcBuf, +nsXULPrototypeScript::Compile(const char16_t* aText, + size_t aTextLength, + JS::SourceOwnership aOwnership, nsIURI* aURI, uint32_t aLineNo, nsIDocument* aDocument, nsIOffThreadScriptReceiver *aOffThreadReceiver /* = nullptr */) { // We'll compile the script in the compilation scope. AutoJSAPI jsapi; if (!jsapi.Init(xpc::CompilationScope())) { + if (aOwnership == JS::SourceOwnership::TakeOwnership) { + // In this early-exit case -- before the |srcBuf.init| call will + // own |aText| -- we must relinquish ownership manually. + js_free(const_cast<char16_t*>(aText)); + } + return NS_ERROR_UNEXPECTED; } JSContext* cx = jsapi.cx(); + JS::SourceText<char16_t> srcBuf; + if (NS_WARN_IF(!srcBuf.init(cx, aText, aTextLength, aOwnership))) { + return NS_ERROR_FAILURE; + } + nsAutoCString urlspec; nsresult rv = aURI->GetSpec(urlspec); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Ok, compile it to create a prototype script object! JS::CompileOptions options(cx); @@ -2304,46 +2317,32 @@ nsXULPrototypeScript::Compile(JS::Source // Function.prototype.toSource(). If it's out of line, we retrieve the // source from the files on demand. options.setSourceIsLazy(mOutOfLine); JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx)); if (scope) { JS::ExposeObjectToActiveJS(scope); } - if (aOffThreadReceiver && JS::CanCompileOffThread(cx, options, aSrcBuf.length())) { - if (!JS::CompileOffThread(cx, options, - aSrcBuf, + if (aOffThreadReceiver && JS::CanCompileOffThread(cx, options, aTextLength)) { + if (!JS::CompileOffThread(cx, options, srcBuf, OffThreadScriptReceiverCallback, static_cast<void*>(aOffThreadReceiver))) { return NS_ERROR_OUT_OF_MEMORY; } NotifyOffThreadScriptCompletedRunnable::NoteReceiver(aOffThreadReceiver); } else { JS::Rooted<JSScript*> script(cx); - if (!JS::Compile(cx, options, aSrcBuf, &script)) + if (!JS::Compile(cx, options, srcBuf, &script)) return NS_ERROR_OUT_OF_MEMORY; Set(script); } return NS_OK; } -nsresult -nsXULPrototypeScript::Compile(const char16_t* aText, - int32_t aTextLength, - nsIURI* aURI, - uint32_t aLineNo, - nsIDocument* aDocument, - nsIOffThreadScriptReceiver *aOffThreadReceiver /* = nullptr */) -{ - JS::SourceBufferHolder srcBuf(aText, aTextLength, - JS::SourceBufferHolder::NoOwnership); - return Compile(srcBuf, aURI, aLineNo, aDocument, aOffThreadReceiver); -} - void nsXULPrototypeScript::UnlinkJSObjects() { if (mScriptObject) { mScriptObject = nullptr; mozilla::DropJSObjects(this); } }
--- a/dom/xul/nsXULElement.h +++ b/dom/xul/nsXULElement.h @@ -7,16 +7,17 @@ The base XUL element class and associates. */ #ifndef nsXULElement_h__ #define nsXULElement_h__ +#include "js/SourceText.h" #include "js/TracingAPI.h" #include "mozilla/Attributes.h" #include "nsIServiceManager.h" #include "nsAtom.h" #include "mozilla/dom/NodeInfo.h" #include "nsIControllers.h" #include "nsIDOMXULMultSelectCntrlEl.h" #include "nsIURI.h" @@ -47,20 +48,16 @@ class StyleRule; } // namespace css namespace dom { class BoxObject; class HTMLIFrameElement; enum class CallerType : uint32_t; } // namespace dom } // namespace mozilla -namespace JS { -class SourceBufferHolder; -} // namespace JS - //////////////////////////////////////////////////////////////////////// #ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING #define XUL_PROTOTYPE_ATTRIBUTE_METER(counter) (nsXULPrototypeAttribute::counter++) #else #define XUL_PROTOTYPE_ATTRIBUTE_METER(counter) ((void) 0) #endif @@ -213,22 +210,18 @@ public: nsXULPrototypeDocument* aProtoDoc); virtual nsresult Deserialize(nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc, nsIURI* aDocumentURI, const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) override; nsresult DeserializeOutOfLine(nsIObjectInputStream* aInput, nsXULPrototypeDocument* aProtoDoc); - nsresult Compile(JS::SourceBufferHolder& aSrcBuf, - nsIURI* aURI, uint32_t aLineNo, - nsIDocument* aDocument, - nsIOffThreadScriptReceiver *aOffThreadReceiver = nullptr); - - nsresult Compile(const char16_t* aText, int32_t aTextLength, + nsresult Compile(const char16_t* aText, size_t aTextLength, + JS::SourceOwnership aOwnership, nsIURI* aURI, uint32_t aLineNo, nsIDocument* aDocument, nsIOffThreadScriptReceiver *aOffThreadReceiver = nullptr); void UnlinkJSObjects(); void Set(JSScript* aObject);
--- a/ipc/testshell/XPCShellEnvironment.cpp +++ b/ipc/testshell/XPCShellEnvironment.cpp @@ -13,17 +13,17 @@ #include <unistd.h> /* for isatty() */ #endif #include "base/basictypes.h" #include "jsapi.h" #include "js/CharacterEncoding.h" #include "js/CompilationAndEvaluation.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "xpcpublic.h" #include "XPCShellEnvironment.h" #include "mozilla/XPCOM.h" #include "nsIChannel.h" @@ -487,19 +487,24 @@ XPCShellEnvironment::EvaluateString(cons { AutoEntryScript aes(GetGlobalObject(), "ipc XPCShellEnvironment::EvaluateString"); JSContext* cx = aes.cx(); JS::CompileOptions options(cx); options.setFileAndLine("typein", 0); + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(cx, aString.get(), aString.Length(), + JS::SourceOwnership::Borrowed)) + { + return false; + } + JS::Rooted<JSScript*> script(cx); - JS::SourceBufferHolder srcBuf(aString.get(), aString.Length(), - JS::SourceBufferHolder::NoOwnership); if (!JS::Compile(cx, options, srcBuf, &script)) { return false; } if (aResult) { aResult->Truncate(); }
--- a/js/public/CharacterEncoding.h +++ b/js/public/CharacterEncoding.h @@ -77,16 +77,22 @@ class UTF8Chars : public mozilla::Range< UTF8Chars() : Base() {} UTF8Chars(char* aBytes, size_t aLength) : Base(reinterpret_cast<unsigned char*>(aBytes), aLength) {} UTF8Chars(const char* aBytes, size_t aLength) : Base(reinterpret_cast<unsigned char*>(const_cast<char*>(aBytes)), aLength) {} + UTF8Chars(mozilla::Utf8Unit* aUnits, size_t aLength) + : UTF8Chars(reinterpret_cast<char*>(aUnits), aLength) + {} + UTF8Chars(const mozilla::Utf8Unit* aUnits, size_t aLength) + : UTF8Chars(reinterpret_cast<const char*>(aUnits), aLength) + {} }; /* * SpiderMonkey also deals directly with UTF-8 encoded text in some places. */ class UTF8CharsZ : public mozilla::RangedPtr<unsigned char> { typedef mozilla::RangedPtr<unsigned char> Base; @@ -103,16 +109,20 @@ class UTF8CharsZ : public mozilla::Range } UTF8CharsZ(unsigned char* aBytes, size_t aLength) : Base(aBytes, aLength) { MOZ_ASSERT(aBytes[aLength] == '\0'); } + UTF8CharsZ(mozilla::Utf8Unit* aUnits, size_t aLength) + : UTF8CharsZ(reinterpret_cast<char*>(aUnits), aLength) + {} + using Base::operator=; char* c_str() { return reinterpret_cast<char*>(get()); } }; /* * A wrapper for a "const char*" that is encoded using UTF-8. * This class does not manage ownership of the data; that is left
--- a/js/public/CompilationAndEvaluation.h +++ b/js/public/CompilationAndEvaluation.h @@ -21,17 +21,17 @@ struct JSContext; class JSFunction; class JSObject; class JSScript; namespace JS { template<typename T> class AutoVector; -class SourceBufferHolder; +template<typename UnitT> class SourceText; } // namespace JS /** * Given a buffer, return false if the buffer might become a valid JavaScript * script with the addition of more lines, or true if the validity of such a * script is conclusively known (because it's the prefix of a valid script -- * and possibly the entirety of such a script). @@ -102,26 +102,26 @@ extern JS_PUBLIC_API(bool) CloneAndExecuteScript(JSContext* cx, AutoVector<JSObject*>& envChain, Handle<JSScript*> script, MutableHandle<Value> rval); /** * Evaluate the given source buffer in the scope of the current global of cx. */ extern JS_PUBLIC_API(bool) Evaluate(JSContext* cx, const ReadOnlyCompileOptions& options, - SourceBufferHolder& srcBuf, MutableHandle<Value> rval); + SourceText<char16_t>& srcBuf, MutableHandle<Value> rval); /** * As above, but providing an explicit scope chain. envChain must not include * the global object on it; that's implicit. It needs to contain the other * objects that should end up on the script's scope chain. */ extern JS_PUBLIC_API(bool) Evaluate(JSContext* cx, AutoVector<JSObject*>& envChain, const ReadOnlyCompileOptions& options, - SourceBufferHolder& srcBuf, MutableHandle<Value> rval); + SourceText<char16_t>& srcBuf, MutableHandle<Value> rval); /** * Evaluate the provided UTF-8 data in the scope of the current global of |cx|, * and return the completion value in |rval|. If the data contains invalid * UTF-8, an error is reported. */ extern JS_PUBLIC_API(bool) EvaluateUtf8(JSContext* cx, const ReadOnlyCompileOptions& options, @@ -149,29 +149,43 @@ extern JS_PUBLIC_API(bool) EvaluateUtf8Path(JSContext* cx, const ReadOnlyCompileOptions& options, const char* filename, MutableHandle<Value> rval); /** * |script| will always be set. On failure, it will be set to nullptr. */ extern JS_PUBLIC_API(bool) Compile(JSContext* cx, const ReadOnlyCompileOptions& options, - SourceBufferHolder& srcBuf, MutableHandle<JSScript*> script); + SourceText<char16_t>& srcBuf, MutableHandle<JSScript*> script); /** * Compile the provided UTF-8 data into a script. If the data contains invalid * UTF-8, an error is reported. * * |script| is always set to the compiled script or to null in case of error. */ extern JS_PUBLIC_API(bool) CompileUtf8(JSContext* cx, const ReadOnlyCompileOptions& options, const char* bytes, size_t length, MutableHandle<JSScript*> script); /** + * Compile the provided UTF-8 data into a script. If the data contains invalid + * UTF-8, an error is reported. + * + * |script| is always set to the compiled script or to null in case of error. + * + * NOTE: This function DOES NOT INFLATE the UTF-8 bytes to UTF-16 before + * compiling them. UTF-8 compilation is currently experimental and has + * known bugs. Use only if you're willing to tolerate unspecified bugs! + */ +extern JS_PUBLIC_API(bool) +CompileUtf8DontInflate(JSContext* cx, const ReadOnlyCompileOptions& options, + const char* bytes, size_t length, MutableHandle<JSScript*> script); + +/** * Compile the provided Latin-1 data (i.e. each byte directly corresponds to * the same Unicode code point) into a script. * * This function may eventually be removed, such that *only* bytes containing * UTF-8 source text may be directly compiled. Avoid using it if you can. * * |script| is always set to the compiled script or to null in case of error. */ @@ -185,29 +199,43 @@ CompileLatin1(JSContext* cx, const ReadO * * |script| is always set to the compiled script or to null in case of error. */ extern JS_PUBLIC_API(bool) CompileUtf8File(JSContext* cx, const ReadOnlyCompileOptions& options, FILE* file, MutableHandle<JSScript*> script); /** + * Compile the UTF-8 contents of the given file into a script. If the contents + * contain any malformed UTF-8, an error is reported. + * + * |script| is always set to the compiled script or to null in case of error. + * + * NOTE: This function DOES NOT INFLATE the UTF-8 bytes to UTF-16 before + * compiling them. UTF-8 compilation is currently experimental and has + * known bugs. Use only if you're willing to tolerate unspecified bugs! + */ +extern JS_PUBLIC_API(bool) +CompileUtf8FileDontInflate(JSContext* cx, const ReadOnlyCompileOptions& options, + FILE* file, MutableHandle<JSScript*> script); + +/** * Compile the UTF-8 contents of the file at the given path into a script. * (The path itself is in the system encoding, not [necessarily] UTF-8.) If * the contents contain any malformed UTF-8, an error is reported. * * |script| is always set to the compiled script or to null in case of error. */ extern JS_PUBLIC_API(bool) CompileUtf8Path(JSContext* cx, const ReadOnlyCompileOptions& options, const char* filename, MutableHandle<JSScript*> script); extern JS_PUBLIC_API(bool) CompileForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& options, - SourceBufferHolder& srcBuf, MutableHandle<JSScript*> script); + SourceText<char16_t>& srcBuf, MutableHandle<JSScript*> script); /** * Compile the given Latin-1 data for non-syntactic scope. * * There is no way to compile UTF-8 data for non-syntactic scope because no * user currently needs it. Such way could be added in the future if it's ever * needed. */ @@ -222,17 +250,17 @@ CompileLatin1ForNonSyntacticScope(JSCont * scope chain used for the function will consist of With wrappers for those * objects, followed by the current global of the compartment cx is in. This * global must not be explicitly included in the scope chain. */ extern JS_PUBLIC_API(bool) CompileFunction(JSContext* cx, AutoVector<JSObject*>& envChain, const ReadOnlyCompileOptions& options, const char* name, unsigned nargs, const char* const* argnames, - SourceBufferHolder& srcBuf, MutableHandle<JSFunction*> fun); + SourceText<char16_t>& srcBuf, MutableHandle<JSFunction*> fun); /** * Same as above, but taking UTF-8 encoded const char* for the function body. */ extern JS_PUBLIC_API(bool) CompileFunctionUtf8(JSContext* cx, AutoVector<JSObject*>& envChain, const ReadOnlyCompileOptions& options, const char* name, unsigned nargs, const char* const* argnames,
--- a/js/public/OffThreadScriptCompilation.h +++ b/js/public/OffThreadScriptCompilation.h @@ -22,17 +22,17 @@ #include "js/GCVector.h" // JS::GCVector #include "js/Transcoding.h" // JS::TranscodeSource struct JSContext; class JSScript; namespace JS { -class SourceBufferHolder; +template<typename UnitT> class SourceText; } // namespace JS namespace JS { class OffThreadToken; using OffThreadCompileCallback = void (*)(OffThreadToken* token, void* callbackData); @@ -56,28 +56,29 @@ CanDecodeOffThread(JSContext* cx, const * - CancelOffThreadScript, to free the resources without creating a script. * * The characters passed in to CompileOffThread must remain live until the * callback is invoked, and the resulting script will be rooted until the call * to FinishOffThreadScript. */ extern JS_PUBLIC_API(bool) -CompileOffThread(JSContext* cx, const ReadOnlyCompileOptions& options, SourceBufferHolder& srcBuf, - OffThreadCompileCallback callback, void* callbackData); +CompileOffThread(JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf, OffThreadCompileCallback callback, + void* callbackData); extern JS_PUBLIC_API(JSScript*) FinishOffThreadScript(JSContext* cx, OffThreadToken* token); extern JS_PUBLIC_API(void) CancelOffThreadScript(JSContext* cx, OffThreadToken* token); extern JS_PUBLIC_API(bool) CompileOffThreadModule(JSContext* cx, const ReadOnlyCompileOptions& options, - SourceBufferHolder& srcBuf, OffThreadCompileCallback callback, + SourceText<char16_t>& srcBuf, OffThreadCompileCallback callback, void* callbackData); extern JS_PUBLIC_API(JSObject*) FinishOffThreadModule(JSContext* cx, OffThreadToken* token); extern JS_PUBLIC_API(void) CancelOffThreadModule(JSContext* cx, OffThreadToken* token);
rename from js/public/SourceBufferHolder.h rename to js/public/SourceText.h --- a/js/public/SourceBufferHolder.h +++ b/js/public/SourceText.h @@ -1,141 +1,282 @@ /* -*- Mode: C++; 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/. */ /* - * SourceBufferHolder groups buffer and length values and provides a way to - * optionally pass ownership of the buffer to the JS engine without copying. + * SourceText encapsulates a count of char16_t (UTF-16) or Utf8Unit (UTF-8) + * code units (note: code *units*, not bytes or code points) and those code + * units ("source units"). (Latin-1 is not supported: all places where Latin-1 + * must be compiled first convert to a supported encoding.) + * + * A SourceText either observes without owning, or takes ownership of, source + * units passed to |SourceText::init|. Thus SourceText can be used to + * efficiently avoid copying. * * Rules for use: * - * 1) The data array must be allocated with js_malloc() or js_realloc() if - * ownership is being granted to the SourceBufferHolder. - * 2) If ownership is not given to the SourceBufferHolder, then the memory - * must be kept alive until the JS compilation is complete. - * 3) Any code calling SourceBufferHolder::take() must guarantee to keep the - * memory alive until JS compilation completes. Normally only the JS - * engine should be calling take(). + * 1) The passed-in source units must be allocated with js_malloc(), + * js_calloc(), or js_realloc() if |SourceText::init| is instructed to take + * ownership of the source units. + * 2) If |SourceText::init| merely borrows the source units, the user must + * keep them alive until associated JS compilation is complete. + * 3) Code that calls |SourceText::take{Chars,Units}()| must keep the source + * units alive until JS compilation completes. Normally only the JS engine + * should call |SourceText::take{Chars,Units}()|. + * 4) Use the appropriate SourceText parameterization depending on the source + * units encoding. * * Example use: * * size_t length = 512; * char16_t* chars = js_pod_malloc<char16_t>(length); - * JS::SourceBufferHolder srcBuf(chars, length, JS::SourceBufferHolder::GiveOwnership); - * JS::Compile(cx, options, srcBuf); + * if (!chars) { + * JS_ReportOutOfMemory(cx); + * return false; + * } + * JS::SourceText<char16_t> srcBuf; + * if (!srcBuf.init(cx, chars, length, JS::SourceOwnership::TakeOwnership)) { + * return false; + * } + * JS::Rooted<JSScript*> script(cx); + * if (!JS::Compile(cx, options, srcBuf, &script)) { + * return false; + * } */ -#ifndef js_SourceBufferHolder_h -#define js_SourceBufferHolder_h +#ifndef js_SourceText_h +#define js_SourceText_h #include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_COLD, MOZ_IS_CLASS_INIT, MOZ_MUST_USE +#include "mozilla/Likely.h" // MOZ_UNLIKELY +#include "mozilla/Utf8.h" // mozilla::Utf8Unit #include <stddef.h> // size_t +#include <stdint.h> // UINT32_MAX +#include <type_traits> // std::conditional, std::is_same -#include "js/Utility.h" // JS::UniqueTwoByteChars +#include "js/UniquePtr.h" // js::UniquePtr +#include "js/Utility.h" // JS::FreePolicy namespace JS { -class SourceBufferHolder final +namespace detail { + +MOZ_COLD extern JS_PUBLIC_API(void) +ReportSourceTooLong(JSContext* cx); + +} // namespace detail + +enum class SourceOwnership +{ + Borrowed, + TakeOwnership, +}; + +template<typename Unit> +class SourceText final { private: - const char16_t* data_; - size_t length_; - bool ownsChars_; + static_assert(std::is_same<Unit, mozilla::Utf8Unit>::value || + std::is_same<Unit, char16_t>::value, + "Unit must be either char16_t or Utf8Unit for " + "SourceText<Unit>"); + + /** |char16_t| or |Utf8Unit| source units of uncertain validity. */ + const Unit* units_ = nullptr; + + /** The length in code units of |units_|. */ + uint32_t length_ = 0; + + /** + * Whether this owns |units_| or merely observes source units owned by some + * other object. + */ + bool ownsUnits_ = false; + + public: + // A C++ character type that can represent the source units -- suitable for + // passing to C++ string functions. + using CharT = + typename std::conditional<std::is_same<Unit, char16_t>::value, + char16_t, + char>::type; - private: - void fixEmptyBuffer() { - // Ensure that null buffers properly return an unowned, empty, - // null-terminated string. - static const char16_t NullChar_ = 0; - if (!data_) { - data_ = &NullChar_; - length_ = 0; - ownsChars_ = false; + public: + /** + * Construct a SourceText. It must be initialized using |init()| before it + * can be used as compilation source text. + */ + SourceText() = default; + + /** + * Construct a SourceText from contents extracted from |other|. This + * SourceText will then act exactly as |other| would have acted, had it + * not been passed to this function. |other| will return to its default- + * constructed state and must have |init()| called on it to use it. + */ + SourceText(SourceText&& other) + : units_(other.units_), + length_(other.length_), + ownsUnits_(other.ownsUnits_) + { + other.units_ = nullptr; + other.length_ = 0; + other.ownsUnits_ = false; + } + + ~SourceText() { + if (ownsUnits_) { + js_free(const_cast<Unit*>(units_)); } } - public: - enum Ownership { - NoOwnership, - GiveOwnership - }; + /** + * Initialize this with source unit data: |char16_t| for UTF-16 source + * units, or |Utf8Unit| for UTF-8 source units. + * + * If |ownership == TakeOwnership|, *this function* takes ownership of + * |units|, *even if* this function fails, and you MUST NOT free |units| + * yourself. This single-owner-friendly approach reduces risk of leaks on + * failure. + * + * |units| may be null if |unitsLength == 0|; if so, this will silently be + * initialized using non-null, unowned units. + */ + MOZ_IS_CLASS_INIT MOZ_MUST_USE bool + init(JSContext* cx, const Unit* units, size_t unitsLength, SourceOwnership ownership) { + MOZ_ASSERT_IF(units == nullptr, unitsLength == 0); + + // Ideally we'd use |Unit| and not cast below, but the risk of a static + // initializer is too great. + static const CharT emptyString[] = { '\0' }; - SourceBufferHolder(const char16_t* data, size_t dataLength, Ownership ownership) - : data_(data), - length_(dataLength), - ownsChars_(ownership == GiveOwnership) - { - fixEmptyBuffer(); - } + // Initialize all fields *before* checking length. This ensures that + // if |ownership == SourceOwnership::TakeOwnership|, |units| will be + // freed when |this|'s destructor is called. + if (units) { + units_ = units; + length_ = static_cast<uint32_t>(unitsLength); + ownsUnits_ = ownership == SourceOwnership::TakeOwnership; + } else { + units_ = reinterpret_cast<const Unit*>(emptyString); + length_ = 0; + ownsUnits_ = false; + } - SourceBufferHolder(UniqueTwoByteChars&& data, size_t dataLength) - : data_(data.release()), - length_(dataLength), - ownsChars_(true) - { - fixEmptyBuffer(); + // IMPLEMENTATION DETAIL, DO NOT RELY ON: This limit is used so we can + // store offsets in |JSScript|s as |uint32_t|. It could be lifted + // fairly easily if desired, as the compiler uses |size_t| internally. + if (MOZ_UNLIKELY(unitsLength > UINT32_MAX)) { + detail::ReportSourceTooLong(cx); + return false; + } + + return true; } - SourceBufferHolder(SourceBufferHolder&& other) - : data_(other.data_), - length_(other.length_), - ownsChars_(other.ownsChars_) + /** + * Exactly identical to the |init()| overload above that accepts + * |const Unit*|, but instead takes character data: |const CharT*|. + * + * (We can't just write this to accept |const CharT*|, because then in the + * UTF-16 case this overload and the one above would be identical. So we + * use SFINAE to expose the |CharT| overload only if it's different.) + */ + template<typename Char, + typename = + typename std::enable_if<std::is_same<Char, CharT>::value && + !std::is_same<Char, Unit>::value>::type> + MOZ_IS_CLASS_INIT MOZ_MUST_USE bool + init(JSContext* cx, const Char* chars, size_t charsLength, SourceOwnership ownership) { + return init(cx, reinterpret_cast<const Unit*>(chars), charsLength, ownership); + } + + /** + * Initialize this using source units transferred out of |data|. + */ + MOZ_MUST_USE bool init(JSContext* cx, + js::UniquePtr<CharT[], JS::FreePolicy> data, + size_t dataLength) { - other.data_ = nullptr; - other.length_ = 0; - other.ownsChars_ = false; + return init(cx, data.release(), dataLength, SourceOwnership::TakeOwnership); } - ~SourceBufferHolder() { - if (ownsChars_) { - js_free(const_cast<char16_t*>(data_)); - } + /** + * Access the encapsulated data using a code unit type. + * + * This function is useful for code that wants to interact with source text + * as *code units*, not as string data. This doesn't matter for UTF-16, + * but it's a crucial distinction for UTF-8. When UTF-8 source text is + * encapsulated, |Unit| being |mozilla::Utf8Unit| unambiguously indicates + * that the code units are UTF-8. In contrast |const char*| returned by + * |get()| below could hold UTF-8 (or its ASCII subset) or Latin-1 or (in + * particularly cursed embeddings) EBCDIC or some other legacy character + * set. Prefer this function to |get()| wherever possible. + */ + const Unit* units() const { return units_; } + + /** + * Access the encapsulated data using a character type. + * + * This function is useful for interactions with character-centric actions + * like interacting with UniqueChars/UniqueTwoByteChars or printing out + * text in a debugger, that only work with |CharT|. But as |CharT| loses + * encoding specificity when UTF-8 source text is encapsulated, prefer + * |units()| to this function. + */ + const CharT* get() const { return reinterpret_cast<const CharT*>(units_); } + + /** + * Returns true if this owns the source units and will free them on + * destruction. If true, it is legal to call |take{Chars,Units}()|. + */ + bool ownsUnits() const { + return ownsUnits_; } - /** Access the underlying source buffer without affecting ownership. */ - const char16_t* get() const { - return data_; - } - - /** Length of the source buffer in char16_t code units (not bytes). */ - size_t length() const { + /** + * Count of the underlying source units -- code units, not bytes or code + * points -- in this. + */ + uint32_t length() const { return length_; } /** - * Returns true if the SourceBufferHolder owns the buffer and will free it - * upon destruction. If true, it is legal to call take(). - */ - bool ownsChars() const { - return ownsChars_; - } - - /** - * Retrieve and take ownership of the underlying data buffer. The caller + * Retrieve and take ownership of the underlying source units. The caller * is now responsible for calling js_free() on the returned value, *but * only after JS script compilation has completed*. * - * After the buffer has been taken the SourceBufferHolder functions as if - * it had been constructed on an unowned buffer; get() and length() still - * work. In order for this to be safe the taken buffer must be kept alive - * until after JS script compilation completes as noted above. + * After underlying source units have been taken, this will continue to + * refer to the same data -- it just won't own the data. get() and + * length() will return the same values, but ownsUnits() will be false. + * The taken source units must be kept alive until after JS script + * compilation completes, as noted above, for this to be safe. * - * It's the caller's responsibility to check ownsChars() before taking the - * buffer. Taking and then free'ing an unowned buffer will have dire - * consequences. + * The caller must check ownsUnits() before calling takeUnits(). Taking + * and then free'ing an unowned buffer will have dire consequences. */ - char16_t* take() { - MOZ_ASSERT(ownsChars_); - ownsChars_ = false; - return const_cast<char16_t*>(data_); + Unit* takeUnits() { + MOZ_ASSERT(ownsUnits_); + ownsUnits_ = false; + return const_cast<Unit*>(units_); + } + + /** + * Akin to |takeUnits()| in all respects, but returns characters rather + * than units. + */ + CharT* takeChars() { + return reinterpret_cast<CharT*>(takeUnits()); } private: - SourceBufferHolder(SourceBufferHolder&) = delete; - SourceBufferHolder& operator=(SourceBufferHolder&) = delete; + SourceText(const SourceText&) = delete; + void operator=(const SourceText&) = delete; }; } // namespace JS -#endif /* js_SourceBufferHolder_h */ +#endif /* js_SourceText_h */
--- a/js/rust/build.rs +++ b/js/rust/build.rs @@ -222,17 +222,17 @@ const WHITELIST_TYPES: &'static [&'stati "JS::Rooted", "JS::RootedObject", "JS::RootingContext", "JS::RootKind", "js::Scalar::Type", "JS::ServoSizes", "js::shadow::Object", "js::shadow::ObjectGroup", - "JS::SourceBufferHolder", + "JS::SourceText", "js::StackFormat", "JSStructuredCloneCallbacks", "JS::Symbol", "JS::SymbolCode", "JS::TraceKind", "JS::TransferableOwnership", "JS::Value", "JS::WarningReporter",
--- a/js/rust/etc/wrapper.hpp +++ b/js/rust/etc/wrapper.hpp @@ -13,16 +13,16 @@ typedef uint32_t HashNumber; #include "jsapi.h" #include "jsfriendapi.h" #include "js/CompilationAndEvaluation.h" #include "js/CompileOptions.h" #include "js/Conversions.h" #include "js/Initialization.h" #include "js/MemoryMetrics.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "js/StructuredClone.h" // Replacements for types that are too difficult for rust-bindgen. /// <div rustbindgen replaces="JS::detail::MaybeWrapped" /> template <typename T> using replaces_MaybeWrapped = T;
--- a/js/rust/src/rust.rs +++ b/js/rust/src/rust.rs @@ -227,20 +227,21 @@ impl Runtime { } else { (script_utf16.as_ptr(), script_utf16.len() as c_uint) }; assert!(!ptr.is_null()); unsafe { let _ar = AutoRealm::with_obj(self.cx(), glob.get()); let options = CompileOptionsWrapper::new(self.cx(), filename_cstr.as_ptr(), line_num); - let mut srcBuf = JS::SourceBufferHolder { - data_: ptr, + let mut srcBuf = JS::SourceText { + units_: ptr, length_: len as _, - ownsChars_: false + ownsUnits_: false, + _phantom_0: marker::PhantomData }; if !JS::Evaluate(self.cx(), options.ptr, &mut srcBuf, rval) { debug!("...err!"); panic::maybe_resume_unwind(); Err(()) } else { // we could return the script result but then we'd have // to root it and so forth and, really, who cares?
--- a/js/src/NamespaceImports.h +++ b/js/src/NamespaceImports.h @@ -33,17 +33,17 @@ class UTF8CharsZ; using AutoValueVector = AutoVector<Value>; using AutoIdVector = AutoVector<jsid>; using AutoObjectVector = AutoVector<JSObject*>; using ValueVector = JS::GCVector<JS::Value>; using IdVector = JS::GCVector<jsid>; using ScriptVector = JS::GCVector<JSScript*>; -class SourceBufferHolder; +template<typename UnitT> class SourceText; class HandleValueArray; class ObjectOpResult; class PropertyResult; enum class SymbolCode: uint32_t;
--- a/js/src/builtin/Eval.cpp +++ b/js/src/builtin/Eval.cpp @@ -4,19 +4,19 @@ * 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/Eval.h" #include "mozilla/HashFunctions.h" #include "mozilla/Range.h" -#include "frontend/BytecodeCompiler.h" +#include "frontend/BytecodeCompilation.h" #include "gc/HashUtil.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "js/StableStringChars.h" #include "vm/Debugger.h" #include "vm/GlobalObject.h" #include "vm/JSContext.h" #include "vm/JSONParser.h" #include "vm/Interpreter-inl.h" @@ -24,17 +24,18 @@ using namespace js; using mozilla::AddToHash; using mozilla::HashString; using mozilla::RangedPtr; using JS::AutoCheckCannotGC; using JS::AutoStableStringChars; using JS::CompileOptions; -using JS::SourceBufferHolder; +using JS::SourceOwnership; +using JS::SourceText; // We should be able to assert this for *any* fp->environmentChain(). static void AssertInnerizedEnvironmentChain(JSContext* cx, JSObject& env) { #ifdef DEBUG RootedObject obj(cx); for (obj = &env; obj; obj = obj->enclosingEnvironment()) { @@ -312,22 +313,28 @@ EvalKernel(JSContext* cx, HandleValue v, options.setIntroductionType("eval"); } AutoStableStringChars linearChars(cx); if (!linearChars.initTwoByte(cx, linearStr)) { return false; } + SourceText<char16_t> srcBuf; + const char16_t* chars = linearChars.twoByteRange().begin().get(); - SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller() - ? SourceBufferHolder::GiveOwnership - : SourceBufferHolder::NoOwnership; - SourceBufferHolder srcBuf(chars, linearStr->length(), ownership); - JSScript* compiled = frontend::CompileEvalScript(cx, env, enclosing, options, srcBuf); + SourceOwnership ownership = linearChars.maybeGiveOwnershipToCaller() + ? SourceOwnership::TakeOwnership + : SourceOwnership::Borrowed; + if (!srcBuf.init(cx, chars, linearStr->length(), ownership)) { + return false; + } + + frontend::EvalScriptInfo info(cx, options, env, enclosing); + JSScript* compiled = frontend::CompileEvalScript(info, srcBuf); if (!compiled) { return false; } esg.setNewScript(compiled); } // Look up the newTarget from the frame iterator. @@ -396,22 +403,28 @@ js::DirectEvalStringFromIon(JSContext* c options.setIntroductionType("eval"); } AutoStableStringChars linearChars(cx); if (!linearChars.initTwoByte(cx, linearStr)) { return false; } + SourceText<char16_t> srcBuf; + const char16_t* chars = linearChars.twoByteRange().begin().get(); - SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller() - ? SourceBufferHolder::GiveOwnership - : SourceBufferHolder::NoOwnership; - SourceBufferHolder srcBuf(chars, linearStr->length(), ownership); - JSScript* compiled = frontend::CompileEvalScript(cx, env, enclosing, options, srcBuf); + SourceOwnership ownership = linearChars.maybeGiveOwnershipToCaller() + ? SourceOwnership::TakeOwnership + : SourceOwnership::Borrowed; + if (!srcBuf.init(cx, chars, linearStr->length(), ownership)) { + return false; + } + + frontend::EvalScriptInfo info(cx, options, env, enclosing); + JSScript* compiled = frontend::CompileEvalScript(info, srcBuf); if (!compiled) { return false; } esg.setNewScript(compiled); } return ExecuteKernel(cx, esg.script(), *env, newTargetValue,
--- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -1282,20 +1282,20 @@ GlobalObject::initModuleProto(JSContext* #undef DEFINE_GETTER_FUNCTIONS #undef DEFINE_STRING_ACCESSOR_METHOD #undef DEFINE_ARRAY_SLOT_ACCESSOR /////////////////////////////////////////////////////////////////////////// // ModuleBuilder ModuleBuilder::ModuleBuilder(JSContext* cx, HandleModuleObject module, - const frontend::TokenStreamAnyChars& tokenStream) + const frontend::EitherParser& eitherParser) : cx_(cx), module_(cx, module), - tokenStream_(tokenStream), + eitherParser_(eitherParser), requestedModuleSpecifiers_(cx, AtomSet(cx)), requestedModules_(cx, RequestedModuleVector(cx)), importEntries_(cx, ImportEntryMap(cx)), exportEntries_(cx, ExportEntryVector(cx)), exportNames_(cx, AtomSet(cx)), localExportEntries_(cx, ExportEntryVector(cx)), indirectExportEntries_(cx, ExportEntryVector(cx)), starExportEntries_(cx, ExportEntryVector(cx)) @@ -1413,17 +1413,17 @@ ModuleBuilder::processImport(frontend::B NameNode* localNameNode = &spec->right()->as<NameNode>(); importName = importNameNode->atom(); localName = localNameNode->atom(); uint32_t line; uint32_t column; - tokenStream_.lineAndColumnAt(importNameNode->pn_pos.begin, &line, &column); + eitherParser_.computeLineAndColumn(importNameNode->pn_pos.begin, &line, &column); RootedImportEntryObject importEntry(cx_); importEntry = ImportEntryObject::create(cx_, module, importName, localName, line, column); if (!importEntry || !appendImportEntryObject(importEntry)) { return false; } } @@ -1681,32 +1681,32 @@ ModuleBuilder::hasExportedName(JSAtom* n bool ModuleBuilder::appendExportEntry(HandleAtom exportName, HandleAtom localName, frontend::ParseNode* node) { uint32_t line = 0; uint32_t column = 0; if (node) { - tokenStream_.lineAndColumnAt(node->pn_pos.begin, &line, &column); + eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column); } Rooted<ExportEntryObject*> exportEntry(cx_); exportEntry = ExportEntryObject::create(cx_, exportName, nullptr, nullptr, localName, line, column); return exportEntry && appendExportEntryObject(exportEntry); } bool ModuleBuilder::appendExportFromEntry(HandleAtom exportName, HandleAtom moduleRequest, HandleAtom importName, frontend::ParseNode* node) { uint32_t line; uint32_t column; - tokenStream_.lineAndColumnAt(node->pn_pos.begin, &line, &column); + eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column); Rooted<ExportEntryObject*> exportEntry(cx_); exportEntry = ExportEntryObject::create(cx_, exportName, moduleRequest, importName, nullptr, line, column); return exportEntry && appendExportEntryObject(exportEntry); } bool @@ -1724,17 +1724,17 @@ bool ModuleBuilder::maybeAppendRequestedModule(HandleAtom specifier, frontend::ParseNode* node) { if (requestedModuleSpecifiers_.has(specifier)) { return true; } uint32_t line; uint32_t column; - tokenStream_.lineAndColumnAt(node->pn_pos.begin, &line, &column); + eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column); JSContext* cx = cx_; RootedRequestedModuleObject req(cx, RequestedModuleObject::create(cx, specifier, line, column)); if (!req) { return false; } return FreezeObject(cx, req) &&
--- a/js/src/builtin/ModuleObject.h +++ b/js/src/builtin/ModuleObject.h @@ -7,16 +7,17 @@ #ifndef builtin_ModuleObject_h #define builtin_ModuleObject_h #include "mozilla/Maybe.h" #include "jsapi.h" #include "builtin/SelfHostingDefines.h" +#include "frontend/EitherParser.h" #include "gc/Zone.h" #include "js/GCVector.h" #include "js/Id.h" #include "js/UniquePtr.h" #include "vm/JSAtom.h" #include "vm/NativeObject.h" #include "vm/ProxyObject.h" @@ -24,17 +25,16 @@ namespace js { class ModuleEnvironmentObject; class ModuleObject; namespace frontend { class BinaryNode; class ListNode; class ParseNode; -class TokenStreamAnyChars; } /* namespace frontend */ typedef Rooted<ModuleObject*> RootedModuleObject; typedef Handle<ModuleObject*> HandleModuleObject; typedef Rooted<ModuleEnvironmentObject*> RootedModuleEnvironmentObject; typedef Handle<ModuleEnvironmentObject*> HandleModuleEnvironmentObject; class ImportEntryObject : public NativeObject @@ -350,19 +350,24 @@ class ModuleObject : public NativeObject bool hasImportBindings() const; FunctionDeclarationVector* functionDeclarations(); }; // Process a module's parse tree to collate the import and export data used when // creating a ModuleObject. class MOZ_STACK_CLASS ModuleBuilder { + explicit ModuleBuilder(JSContext* cx, HandleModuleObject module, + const frontend::EitherParser& eitherParser); + public: - explicit ModuleBuilder(JSContext* cx, HandleModuleObject module, - const frontend::TokenStreamAnyChars& tokenStream); + template<class Parser> + explicit ModuleBuilder(JSContext* cx, HandleModuleObject module, Parser* parser) + : ModuleBuilder(cx, module, frontend::EitherParser(parser)) + {} bool processImport(frontend::BinaryNode* importNode); bool processExport(frontend::ParseNode* exportNode); bool processExportFrom(frontend::BinaryNode* exportNode); bool hasExportedName(JSAtom* name) const; using ExportEntryVector = GCVector<ExportEntryObject*>; @@ -379,17 +384,17 @@ class MOZ_STACK_CLASS ModuleBuilder using ImportEntryMap = GCHashMap<JSAtom*, ImportEntryObject*>; using RootedExportEntryVector = JS::Rooted<ExportEntryVector>; using RootedRequestedModuleVector = JS::Rooted<RequestedModuleVector>; using RootedAtomSet = JS::Rooted<AtomSet>; using RootedImportEntryMap = JS::Rooted<ImportEntryMap>; JSContext* cx_; RootedModuleObject module_; - const frontend::TokenStreamAnyChars& tokenStream_; + frontend::EitherParser eitherParser_; RootedAtomSet requestedModuleSpecifiers_; RootedRequestedModuleVector requestedModules_; RootedImportEntryMap importEntries_; RootedExportEntryVector exportEntries_; RootedAtomSet exportNames_; RootedExportEntryVector localExportEntries_; RootedExportEntryVector indirectExportEntries_; RootedExportEntryVector starExportEntries_;
--- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -11,19 +11,19 @@ #include "mozilla/Move.h" #include <stdlib.h> #include "jspubtd.h" #include "builtin/Array.h" #include "builtin/Reflect.h" +#include "frontend/ModuleSharedContext.h" #include "frontend/ParseNode.h" #include "frontend/Parser.h" -#include "frontend/TokenStream.h" #include "js/CharacterEncoding.h" #include "js/StableStringChars.h" #include "vm/JSAtom.h" #include "vm/JSObject.h" #include "vm/RegExpObject.h" #include "vm/JSObject-inl.h" @@ -233,27 +233,32 @@ enum class GeneratorStyle * * Bug 569487: generalize builder interface */ class NodeBuilder { typedef AutoValueArray<AST_LIMIT> CallbackArray; JSContext* cx; - TokenStreamAnyChars* tokenStream; + frontend::Parser<frontend::FullParseHandler, char16_t>* parser; bool saveLoc; /* save source location information? */ char const* src; /* source filename or null */ RootedValue srcval; /* source filename JS value or null */ CallbackArray callbacks; /* user-specified callbacks */ RootedValue userv; /* user-specified builder object or null */ public: NodeBuilder(JSContext* c, bool l, char const* s) - : cx(c), tokenStream(nullptr), saveLoc(l), src(s), srcval(c), callbacks(cx), - userv(c) + : cx(c), + parser(nullptr), + saveLoc(l), + src(s), + srcval(c), + callbacks(cx), + userv(c) {} MOZ_MUST_USE bool init(HandleObject userobj = nullptr) { if (src) { if (!atomValue(src, &srcval)) { return false; } } else { @@ -294,18 +299,18 @@ class NodeBuilder } callbacks[i].set(funv); } return true; } - void setTokenStream(TokenStreamAnyChars* ts) { - tokenStream = ts; + void setParser(frontend::Parser<frontend::FullParseHandler, char16_t>* p) { + parser = p; } private: MOZ_MUST_USE bool callbackHelper(HandleValue fun, const InvokeArgs& args, size_t i, TokenPos* pos, MutableHandleValue dst) { // The end of the implementation of callback(). All arguments except // loc have already been stored in range [0, i). @@ -701,18 +706,18 @@ NodeBuilder::newNodeLoc(TokenPos* pos, M if (!newObject(&loc)) { return false; } dst.setObject(*loc); uint32_t startLineNum, startColumnIndex; uint32_t endLineNum, endColumnIndex; - tokenStream->srcCoords.lineNumAndColumnIndex(pos->begin, &startLineNum, &startColumnIndex); - tokenStream->srcCoords.lineNumAndColumnIndex(pos->end, &endLineNum, &endColumnIndex); + parser->anyChars.srcCoords.lineNumAndColumnIndex(pos->begin, &startLineNum, &startColumnIndex); + parser->anyChars.srcCoords.lineNumAndColumnIndex(pos->end, &endLineNum, &endColumnIndex); if (!newObject(&to)) { return false; } val.setObject(*to); if (!defineProperty(loc, "start", val)) { return false; } @@ -1828,19 +1833,19 @@ class ASTSerializer , lineno(ln) #endif {} bool init(HandleObject userobj) { return builder.init(userobj); } - void setParser(Parser<FullParseHandler, char16_t>* p) { + void setParser(frontend::Parser<frontend::FullParseHandler, char16_t>* p) { parser = p; - builder.setTokenStream(&p->anyChars); + builder.setParser(p); } bool program(ListNode* node, MutableHandleValue dst); }; } /* anonymous namespace */ AssignmentOperator @@ -2012,17 +2017,22 @@ ASTSerializer::blockStatement(ListNode* NodeVector stmts(cx); return statements(node, stmts) && builder.blockStatement(stmts, &node->pn_pos, dst); } bool ASTSerializer::program(ListNode* node, MutableHandleValue dst) { - MOZ_ASSERT(parser->anyChars.srcCoords.lineNum(node->pn_pos.begin) == lineno); +#ifdef DEBUG + { + const auto& srcCoords = parser->anyChars.srcCoords; + MOZ_ASSERT(srcCoords.lineNum(node->pn_pos.begin) == lineno); + } +#endif NodeVector stmts(cx); return statements(node, stmts) && builder.program(stmts, &node->pn_pos, dst); } bool ASTSerializer::sourceElement(ParseNode* pn, MutableHandleValue dst) @@ -3813,17 +3823,17 @@ reflect_parse(JSContext* cx, uint32_t ar return false; } Rooted<ModuleObject*> module(cx, ModuleObject::create(cx)); if (!module) { return false; } - ModuleBuilder builder(cx, module, parser.anyChars); + ModuleBuilder builder(cx, module, &parser); ModuleSharedContext modulesc(cx, module, &cx->global()->emptyGlobalScope(), builder); pn = parser.moduleBody(&modulesc); if (!pn) { return false; } MOZ_ASSERT(pn->getKind() == ParseNodeKind::Module);
--- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -42,17 +42,17 @@ #include "jit/InlinableNatives.h" #include "jit/JitRealm.h" #include "js/CharacterEncoding.h" #include "js/CompilationAndEvaluation.h" #include "js/CompileOptions.h" #include "js/Debug.h" #include "js/HashTable.h" #include "js/LocaleSensitive.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "js/StableStringChars.h" #include "js/StructuredClone.h" #include "js/UbiNode.h" #include "js/UbiNodeBreadthFirst.h" #include "js/UbiNodeShortestPaths.h" #include "js/UniquePtr.h" #include "js/Vector.h" #include "js/Wrapper.h" @@ -88,17 +88,18 @@ using namespace js; using mozilla::ArrayLength; using mozilla::Maybe; using JS::AutoStableStringChars; using JS::CompileOptions; -using JS::SourceBufferHolder; +using JS::SourceOwnership; +using JS::SourceText; // If fuzzingSafe is set, remove functionality that could cause problems with // fuzzers. Set this via the environment variable MOZ_FUZZING_SAFE. mozilla::Atomic<bool> fuzzingSafe(false); // If disableOOMFunctions is set, disable functionality that causes artificial // OOM conditions. static mozilla::Atomic<bool> disableOOMFunctions(false); @@ -4028,17 +4029,21 @@ EvalReturningScope(JSContext* cx, unsign unsigned lineno; JS::DescribeScriptedCaller(cx, &filename, &lineno); JS::CompileOptions options(cx); options.setFileAndLine(filename.get(), lineno); options.setNoScriptRval(true); - JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership); + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(cx, src, srclen, SourceOwnership::Borrowed)) { + return false; + } + RootedScript script(cx); if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script)) { return false; } if (global) { global = CheckedUnwrap(global); if (!global) { @@ -4129,17 +4134,21 @@ ShellCloneAndExecuteScript(JSContext* cx unsigned lineno; JS::DescribeScriptedCaller(cx, &filename, &lineno); JS::CompileOptions options(cx); options.setFileAndLine(filename.get(), lineno); options.setNoScriptRval(true); - JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership); + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(cx, src, srclen, SourceOwnership::Borrowed)) { + return false; + } + RootedScript script(cx); if (!JS::Compile(cx, options, srcBuf, &script)) { return false; } global = CheckedUnwrap(global); if (!global) { JS_ReportErrorASCII(cx, "Permission denied to access global"); @@ -5553,19 +5562,23 @@ js::TestingFunctionArgumentToScript(JSCo } size_t len = GetLinearStringLength(linearStr); AutoStableStringChars linearChars(cx); if (!linearChars.initTwoByte(cx, linearStr)) { return nullptr; } const char16_t* chars = linearChars.twoByteRange().begin().get(); + SourceText<char16_t> source; + if (!source.init(cx, chars, len, SourceOwnership::Borrowed)) { + return nullptr; + } + RootedScript script(cx); CompileOptions options(cx); - SourceBufferHolder source(chars, len, SourceBufferHolder::NoOwnership); if (!JS::Compile(cx, options, source, &script)) { return nullptr; } return script; } RootedFunction fun(cx, JS_ValueToFunction(cx, v)); if (!fun) {
copy from js/src/frontend/BytecodeCompiler.cpp copy to js/src/frontend/BytecodeCompilation.h --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompilation.h @@ -1,1133 +1,189 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 "frontend/BytecodeCompiler.h" +#ifndef frontend_BytecodeCompilation_h +#define frontend_BytecodeCompilation_h -#include "mozilla/IntegerPrintfMacros.h" -#include "mozilla/Maybe.h" +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_MUST_USE, MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Nothing +#include "mozilla/Utf8.h" // mozilla::Utf8Unit + +#include <stddef.h> // size_t +#include <stdint.h> // uint32_t -#include "builtin/ModuleObject.h" -#if defined(JS_BUILD_BINAST) -# include "frontend/BinSource.h" -#endif // JS_BUILD_BINAST -#include "frontend/BytecodeEmitter.h" -#include "frontend/ErrorReporter.h" -#include "frontend/FoldConstants.h" -#include "frontend/Parser.h" -#include "js/SourceBufferHolder.h" -#include "vm/GlobalObject.h" -#include "vm/JSContext.h" -#include "vm/JSScript.h" -#include "vm/TraceLogging.h" -#include "wasm/AsmJS.h" +#include "frontend/EitherParser.h" // js::frontend::EitherParser +#include "frontend/ParseContext.h" // js::frontend::UsedNameTracker +#include "frontend/SharedContext.h" // js::frontend::Directives, js::frontend::{,Eval,Global}SharedContext +#include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions +#include "js/RootingAPI.h" // JS::{,Mutable}Handle, JS::Rooted +#include "js/SourceText.h" // JS::SourceText +#include "vm/JSContext.h" // js::AutoKeepAtoms +#include "vm/JSScript.h" // js::{FunctionAsync,Generator}Kind, js::LazyScript, JSScript, js::ScriptSource, js::ScriptSourceObject +#include "vm/Scope.h" // js::ScopeKind -#include "vm/EnvironmentObject-inl.h" -#include "vm/GeckoProfiler-inl.h" -#include "vm/JSContext-inl.h" +class JSFunction; +class JSObject; -using namespace js; -using namespace js::frontend; +namespace js { -using mozilla::Maybe; -using mozilla::Nothing; +namespace frontend { -using JS::CompileOptions; -using JS::ReadOnlyCompileOptions; -using JS::SourceBufferHolder; +template<typename Unit> class SourceAwareCompiler; +template<typename Unit> class ScriptCompiler; +template<typename Unit> class ModuleCompiler; +template<typename Unit> class StandaloneFunctionCompiler; // The BytecodeCompiler class contains resources common to compiling scripts and // function bodies. class MOZ_STACK_CLASS BytecodeCompiler { protected: AutoKeepAtoms keepAtoms; JSContext* cx; - const ReadOnlyCompileOptions& options; - SourceBufferHolder& sourceBuffer; + const JS::ReadOnlyCompileOptions& options; - RootedScriptSourceObject sourceObject; - ScriptSource* scriptSource; + JS::Rooted<ScriptSourceObject*> sourceObject; + ScriptSource* scriptSource = nullptr; - Maybe<UsedNameTracker> usedNames; - Maybe<Parser<SyntaxParseHandler, char16_t>> syntaxParser; - Maybe<Parser<FullParseHandler, char16_t>> parser; + mozilla::Maybe<UsedNameTracker> usedNames; Directives directives; - RootedScript script; + JS::Rooted<JSScript*> script; + + protected: + BytecodeCompiler(JSContext* cx, const JS::ReadOnlyCompileOptions& options); + + template<typename Unit> friend class SourceAwareCompiler; + template<typename Unit> friend class ScriptCompiler; + template<typename Unit> friend class ModuleCompiler; + template<typename Unit> friend class StandaloneFunctionCompiler; public: - // Construct an object passing mandatory arguments. - BytecodeCompiler(JSContext* cx, - const ReadOnlyCompileOptions& options, - SourceBufferHolder& sourceBuffer); + JSContext* context() const { + return cx; + } ScriptSourceObject* sourceObjectPtr() const { return sourceObject.get(); } - // Call this before calling compile{Global,Eval}Script. - MOZ_MUST_USE bool prepareScriptParse() { - return createSourceAndParser(ParseGoal::Script) && createCompleteScript(); - } - - JSScript* compileGlobalScript(ScopeKind scopeKind); - JSScript* compileEvalScript(HandleObject environment, HandleScope enclosingScope); - - // Call this before calling compileModule. - MOZ_MUST_USE bool prepareModuleParse() { - return createSourceAndParser(ParseGoal::Module) && createCompleteScript(); - } - - ModuleObject* compileModule(HandleScope enclosingScope); - - // Call this before calling parseStandaloneFunction. - MOZ_MUST_USE bool prepareStandaloneFunctionParse(const Maybe<uint32_t>& parameterListEnd) { - return createSourceAndParser(ParseGoal::Script, parameterListEnd); - } - - // Call this before calling compileStandaloneFunction. - CodeNode* parseStandaloneFunction(MutableHandleFunction fun, GeneratorKind generatorKind, - FunctionAsyncKind asyncKind, - const Maybe<uint32_t>& parameterListEnd, - HandleScope enclosingScope); - - bool compileStandaloneFunction(CodeNode* parsedFunction, MutableHandleFunction fun); - - private: - void assertSourceAndParserCreated() const { + protected: + void assertSourceCreated() const { MOZ_ASSERT(sourceObject != nullptr); MOZ_ASSERT(scriptSource != nullptr); - MOZ_ASSERT(usedNames.isSome()); - MOZ_ASSERT(parser.isSome()); - } - - void assertSourceParserAndScriptCreated() const { - assertSourceAndParserCreated(); - MOZ_ASSERT(script != nullptr); } - JSScript* compileScript(HandleObject environment, SharedContext* sc); - bool checkLength(); - bool createScriptSource(const Maybe<uint32_t>& parameterListEnd); - bool canLazilyParse(); - bool createParser(ParseGoal goal); - bool createSourceAndParser(ParseGoal goal, - const Maybe<uint32_t>& parameterListEnd = Nothing()); - - // This assumes the created script's offsets in the source used to parse it - // are the same as are used to compute its Function.prototype.toString() - // value. - bool createCompleteScript(); - - // This uses explicitly-provided toString offsets as the created script's - // offsets in the source. - bool createScript(uint32_t toStringStart, uint32_t toStringEnd); - - using TokenStreamPosition = frontend::TokenStreamPosition<char16_t>; - - bool emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext); - bool handleParseFailure(const Directives& newDirectives, TokenStreamPosition& startPosition); - bool deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObject environment); -}; - -AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx, const TraceLoggerTextId id, - const ErrorReporter& errorReporter) -#ifdef JS_TRACE_LOGGING - : logger_(TraceLoggerForCurrentThread(cx)) -{ - // If the tokenizer hasn't yet gotten any tokens, use the line and column - // numbers from CompileOptions. - uint32_t line, column; - if (errorReporter.hasTokenizationStarted()) { - line = errorReporter.options().lineno; - column = errorReporter.options().column; - } else { - errorReporter.currentLineAndColumn(&line, &column); - } - frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(), line, column); - frontendLog_.emplace(logger_, *frontendEvent_); - typeLog_.emplace(logger_, id); -} -#else -{ } -#endif - -AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx, const TraceLoggerTextId id, - const ErrorReporter& errorReporter, - FunctionBox* funbox) -#ifdef JS_TRACE_LOGGING - : logger_(TraceLoggerForCurrentThread(cx)) -{ - frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(), - funbox->startLine, funbox->startColumn); - frontendLog_.emplace(logger_, *frontendEvent_); - typeLog_.emplace(logger_, id); -} -#else -{ } -#endif - -AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx, const TraceLoggerTextId id, - const ErrorReporter& errorReporter, ParseNode* pn) -#ifdef JS_TRACE_LOGGING - : logger_(TraceLoggerForCurrentThread(cx)) -{ - uint32_t line, column; - errorReporter.lineAndColumnAt(pn->pn_pos.begin, &line, &column); - frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(), line, column); - frontendLog_.emplace(logger_, *frontendEvent_); - typeLog_.emplace(logger_, id); -} -#else -{ } -#endif - -BytecodeCompiler::BytecodeCompiler(JSContext* cx, - const ReadOnlyCompileOptions& options, - SourceBufferHolder& sourceBuffer) - : keepAtoms(cx), - cx(cx), - options(options), - sourceBuffer(sourceBuffer), - sourceObject(cx), - scriptSource(nullptr), - directives(options.strictOption), - script(cx) -{ - MOZ_ASSERT(sourceBuffer.get()); -} + MOZ_MUST_USE bool createScriptSource(const mozilla::Maybe<uint32_t>& parameterListEnd); -bool -BytecodeCompiler::checkLength() -{ - // Note this limit is simply so we can store sourceStart and sourceEnd in - // JSScript as 32-bits. It could be lifted fairly easily, since the compiler - // is using size_t internally already. - if (sourceBuffer.length() > UINT32_MAX) { - if (!cx->helperThread()) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, - JSMSG_SOURCE_TOO_LONG); - } - return false; - } - return true; -} - -bool -BytecodeCompiler::createScriptSource(const Maybe<uint32_t>& parameterListEnd) -{ - if (!checkLength()) { - return false; - } - - sourceObject = CreateScriptSourceObject(cx, options, parameterListEnd); - if (!sourceObject) { - return false; - } - - scriptSource = sourceObject->source(); - - if (!cx->realm()->behaviors().discardSource()) { - if (options.sourceIsLazy) { - scriptSource->setSourceRetrievable(); - } else if (!scriptSource->setSourceCopy(cx, sourceBuffer)) { - return false; - } - } - - return true; -} - -bool -BytecodeCompiler::canLazilyParse() -{ - return options.canLazilyParse && - !cx->realm()->behaviors().disableLazyParsing() && - !cx->realm()->behaviors().discardSource() && - !options.sourceIsLazy && - !cx->lcovEnabled() && - // Disabled during record/replay. The replay debugger requires - // scripts to be constructed in a consistent order, which might not - // happen with lazy parsing. - !mozilla::recordreplay::IsRecordingOrReplaying(); -} - -bool -BytecodeCompiler::createParser(ParseGoal goal) -{ - usedNames.emplace(cx); - - if (canLazilyParse()) { - syntaxParser.emplace(cx, cx->tempLifoAlloc(), options, - sourceBuffer.get(), sourceBuffer.length(), - /* foldConstants = */ false, *usedNames, nullptr, nullptr, - sourceObject, goal); - if (!syntaxParser->checkOptions()) { - return false; - } - } - - parser.emplace(cx, cx->tempLifoAlloc(), options, sourceBuffer.get(), sourceBuffer.length(), - /* foldConstants = */ true, *usedNames, syntaxParser.ptrOr(nullptr), nullptr, - sourceObject, goal); - parser->ss = scriptSource; - return parser->checkOptions(); -} - -bool -BytecodeCompiler::createSourceAndParser(ParseGoal goal, - const Maybe<uint32_t>& parameterListEnd /* = Nothing() */) -{ - return createScriptSource(parameterListEnd) && - createParser(goal); -} - -bool -BytecodeCompiler::createCompleteScript() -{ - return createScript(0, sourceBuffer.length()); -} - -bool -BytecodeCompiler::createScript(uint32_t toStringStart, uint32_t toStringEnd) -{ - script = JSScript::Create(cx, options, - sourceObject, /* sourceStart = */ 0, sourceBuffer.length(), - toStringStart, toStringEnd); - return script != nullptr; -} - -bool -BytecodeCompiler::emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext) -{ - BytecodeEmitter::EmitterMode emitterMode = - options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal; - emitter.emplace(/* parent = */ nullptr, parser.ptr(), sharedContext, script, - /* lazyScript = */ nullptr, options.lineno, emitterMode); - return emitter->init(); -} - -bool -BytecodeCompiler::handleParseFailure(const Directives& newDirectives, - TokenStreamPosition& startPosition) -{ - if (parser->hadAbortedSyntaxParse()) { - // Hit some unrecoverable ambiguity during an inner syntax parse. - // Syntax parsing has now been disabled in the parser, so retry - // the parse. - parser->clearAbortedSyntaxParse(); - } else if (parser->anyChars.hadError() || directives == newDirectives) { - return false; + void createUsedNames() { + usedNames.emplace(cx); } - parser->tokenStream.seek(startPosition); - - // Assignment must be monotonic to prevent reparsing iloops - MOZ_ASSERT_IF(directives.strict(), newDirectives.strict()); - MOZ_ASSERT_IF(directives.asmJS(), newDirectives.asmJS()); - directives = newDirectives; - return true; -} - -bool -BytecodeCompiler::deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObject environment) -{ - RootedObject env(cx, environment); - while (env->is<EnvironmentObject>() || env->is<DebugEnvironmentProxy>()) { - if (env->is<CallObject>()) { - RootedFunction fun(cx, &env->as<CallObject>().callee()); - RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun)); - if (!script) { - return false; - } - if (script->argumentsHasVarBinding()) { - if (!JSScript::argumentsOptimizationFailed(cx, script)) { - return false; - } - } - } - env = env->enclosingEnvironment(); - } - - return true; -} + // Create a script for source of the given length, using the explicitly- + // provided toString offsets as the created script's offsets in the source. + MOZ_MUST_USE bool internalCreateScript(uint32_t toStringStart, uint32_t toStringEnd, + uint32_t sourceBufferLength); -JSScript* -BytecodeCompiler::compileScript(HandleObject environment, SharedContext* sc) -{ - assertSourceParserAndScriptCreated(); - - TokenStreamPosition startPosition(keepAtoms, parser->tokenStream); - - Maybe<BytecodeEmitter> emitter; - if (!emplaceEmitter(emitter, sc)) { - return nullptr; - } + MOZ_MUST_USE bool emplaceEmitter(mozilla::Maybe<BytecodeEmitter>& emitter, + const EitherParser& parser, SharedContext* sharedContext); - for (;;) { - ParseNode* pn; - { - AutoGeckoProfilerEntry pseudoFrame(cx, "script parsing"); - if (sc->isEvalContext()) { - pn = parser->evalBody(sc->asEvalContext()); - } else { - pn = parser->globalBody(sc->asGlobalContext()); - } - } + // This function lives here, not in SourceAwareCompiler, because it mostly + // uses fields in *this* class. + template<typename Unit> + MOZ_MUST_USE bool assignSource(JS::SourceText<Unit>& sourceBuffer); - // Successfully parsed. Emit the script. - AutoGeckoProfilerEntry pseudoFrame(cx, "script emit"); - if (pn) { - if (sc->isEvalContext() && sc->hasDebuggerStatement() && !cx->helperThread()) { - // If the eval'ed script contains any debugger statement, force construction - // of arguments objects for the caller script and any other scripts it is - // transitively nested inside. The debugger can access any variable on the - // scope chain. - if (!deoptimizeArgumentsInEnclosingScripts(cx, environment)) { - return nullptr; - } - } - if (!emitter->emitScript(pn)) { - return nullptr; - } - break; - } + bool canLazilyParse() const; - // Maybe we aborted a syntax parse. See if we can try again. - if (!handleParseFailure(directives, startPosition)) { - return nullptr; - } - - // Reset UsedNameTracker state before trying again. - usedNames->reset(); - } - - // We have just finished parsing the source. Inform the source so that we - // can compute statistics (e.g. how much time our functions remain lazy). - script->scriptSource()->recordParseEnded(); - - // Enqueue an off-thread source compression task after finishing parsing. - if (!scriptSource->tryCompressOffThread(cx)) { - return nullptr; - } - - MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending()); - - return script; -} - -JSScript* -BytecodeCompiler::compileGlobalScript(ScopeKind scopeKind) -{ - GlobalSharedContext globalsc(cx, scopeKind, directives, options.extraWarningsOption); - - if (!prepareScriptParse()) { - return nullptr; - } - - return compileScript(nullptr, &globalsc); -} + MOZ_MUST_USE bool + deoptimizeArgumentsInEnclosingScripts(JSContext* cx, JS::Handle<JSObject*> environment); +}; -JSScript* -BytecodeCompiler::compileEvalScript(HandleObject environment, HandleScope enclosingScope) +class MOZ_STACK_CLASS GlobalScriptInfo final + : public BytecodeCompiler { - EvalSharedContext evalsc(cx, environment, enclosingScope, - directives, options.extraWarningsOption); - - if (!prepareScriptParse()) { - return nullptr; - } - - return compileScript(environment, &evalsc); -} - -ModuleObject* -BytecodeCompiler::compileModule(HandleScope enclosingScope) -{ - assertSourceParserAndScriptCreated(); + GlobalSharedContext globalsc_; - Rooted<ModuleObject*> module(cx, ModuleObject::create(cx)); - if (!module) { - return nullptr; - } - - module->init(script); - - ModuleBuilder builder(cx, module, parser->anyChars); - - ModuleSharedContext modulesc(cx, module, enclosingScope, builder); - ParseNode* pn = parser->moduleBody(&modulesc); - if (!pn) { - return nullptr; - } - - Maybe<BytecodeEmitter> emitter; - if (!emplaceEmitter(emitter, &modulesc)) { - return nullptr; - } - if (!emitter->emitScript(pn->as<CodeNode>().body())) { - return nullptr; + public: + GlobalScriptInfo(JSContext* cx, const JS::ReadOnlyCompileOptions& options, ScopeKind scopeKind) + : BytecodeCompiler(cx, options), + globalsc_(cx, scopeKind, directives, options.extraWarningsOption) + { + MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic); } - if (!builder.initModule()) { - return nullptr; - } - - RootedModuleEnvironmentObject env(cx, ModuleEnvironmentObject::create(cx, module)); - if (!env) { - return nullptr; - } - - module->setInitialEnvironment(env); - - // Enqueue an off-thread source compression task after finishing parsing. - if (!scriptSource->tryCompressOffThread(cx)) { - return nullptr; - } - - MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending()); - return module; -} - -// Parse a standalone JS function, which might appear as the value of an -// event handler attribute in an HTML <INPUT> tag, or in a Function() -// constructor. -CodeNode* -BytecodeCompiler::parseStandaloneFunction(MutableHandleFunction fun, - GeneratorKind generatorKind, - FunctionAsyncKind asyncKind, - const Maybe<uint32_t>& parameterListEnd, - HandleScope enclosingScope) -{ - MOZ_ASSERT(fun); - MOZ_ASSERT(fun->isTenured()); - - assertSourceAndParserCreated(); - - TokenStreamPosition startPosition(keepAtoms, parser->tokenStream); - - // Speculatively parse using the default directives implied by the context. - // If a directive is encountered (e.g., "use strict") that changes how the - // function should have been parsed, we backup and reparse with the new set - // of directives. - - ParseNode* fn; - do { - Directives newDirectives = directives; - fn = parser->standaloneFunction(fun, enclosingScope, parameterListEnd, generatorKind, - asyncKind, directives, &newDirectives); - if (!fn && !handleParseFailure(newDirectives, startPosition)) { - return nullptr; - } - } while (!fn); - - return &fn->as<CodeNode>(); -} - -// Compile a standalone JS function. -bool -BytecodeCompiler::compileStandaloneFunction(CodeNode* parsedFunction, MutableHandleFunction fun) -{ - FunctionBox* funbox = parsedFunction->funbox(); - if (funbox->function()->isInterpreted()) { - MOZ_ASSERT(fun == funbox->function()); - - if (!createScript(funbox->toStringStart, funbox->toStringEnd)) { - return false; - } - - Maybe<BytecodeEmitter> emitter; - if (!emplaceEmitter(emitter, funbox)) { - return false; - } - if (!emitter->emitFunctionScript(parsedFunction, BytecodeEmitter::TopLevelFunction::Yes)) { - return false; - } - } else { - fun.set(funbox->function()); - MOZ_ASSERT(IsAsmJSModule(fun)); - } - - // Enqueue an off-thread source compression task after finishing parsing. - return scriptSource->tryCompressOffThread(cx); -} - -ScriptSourceObject* -frontend::CreateScriptSourceObject(JSContext* cx, const ReadOnlyCompileOptions& options, - const Maybe<uint32_t>& parameterListEnd /* = Nothing() */) -{ - ScriptSource* ss = cx->new_<ScriptSource>(); - if (!ss) { - return nullptr; - } - ScriptSourceHolder ssHolder(ss); - - if (!ss->initFromOptions(cx, options, parameterListEnd)) { - return nullptr; - } - - RootedScriptSourceObject sso(cx, ScriptSourceObject::create(cx, ss)); - if (!sso) { - return nullptr; - } - - // Off-thread compilations do all their GC heap allocation, including the - // SSO, in a temporary compartment. Hence, for the SSO to refer to the - // gc-heap-allocated values in |options|, it would need cross-compartment - // wrappers from the temporary compartment to the real compartment --- which - // would then be inappropriate once we merged the temporary and real - // compartments. - // - // Instead, we put off populating those SSO slots in off-thread compilations - // until after we've merged compartments. - if (!cx->helperThread()) { - if (!ScriptSourceObject::initFromOptions(cx, sso, options)) { - return nullptr; - } - } - - return sso; -} - -// CompileScript independently returns the ScriptSourceObject (SSO) for the -// compile. This is used by off-thread script compilation (OT-SC). -// -// OT-SC cannot initialize the SSO when it is first constructed because the -// SSO is allocated initially in a separate compartment. -// -// After OT-SC, the separate compartment is merged with the main compartment, -// at which point the JSScripts created become observable by the debugger via -// memory-space scanning. -// -// Whatever happens to the top-level script compilation (even if it fails and -// returns null), we must finish initializing the SSO. This is because there -// may be valid inner scripts observable by the debugger which reference the -// partially-initialized SSO. -class MOZ_STACK_CLASS AutoInitializeSourceObject -{ - BytecodeCompiler& compiler_; - ScriptSourceObject** sourceObjectOut_; - - public: - AutoInitializeSourceObject(BytecodeCompiler& compiler, - ScriptSourceObject** sourceObjectOut) - : compiler_(compiler), - sourceObjectOut_(sourceObjectOut) - { } - - ~AutoInitializeSourceObject() { - if (sourceObjectOut_) { - *sourceObjectOut_ = compiler_.sourceObjectPtr(); - } + GlobalSharedContext* sharedContext() { + return &globalsc_; } }; -// RAII class to check the frontend reports an exception when it fails to -// compile a script. -class MOZ_RAII AutoAssertReportedException +extern JSScript* +CompileGlobalScript(GlobalScriptInfo& info, JS::SourceText<char16_t>& srcBuf, + ScriptSourceObject** sourceObjectOut = nullptr); + +extern JSScript* +CompileGlobalScript(GlobalScriptInfo& info, JS::SourceText<mozilla::Utf8Unit>& srcBuf, + ScriptSourceObject** sourceObjectOut = nullptr); + +class MOZ_STACK_CLASS EvalScriptInfo final + : public BytecodeCompiler { -#ifdef DEBUG - JSContext* cx_; - bool check_; + JS::Handle<JSObject*> environment_; + EvalSharedContext evalsc_; public: - explicit AutoAssertReportedException(JSContext* cx) - : cx_(cx), - check_(true) + EvalScriptInfo(JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::Handle<JSObject*> environment, JS::Handle<Scope*> enclosingScope) + : BytecodeCompiler(cx, options), + environment_(environment), + evalsc_(cx, environment_, enclosingScope, directives, options.extraWarningsOption) {} - void reset() { - check_ = false; - } - ~AutoAssertReportedException() { - if (!check_) { - return; - } - - if (!cx_->helperThread()) { - MOZ_ASSERT(cx_->isExceptionPending()); - return; - } - - ParseTask* task = cx_->helperThread()->parseTask(); - MOZ_ASSERT(task->outOfMemory || - task->overRecursed || - !task->errors.empty()); - } -#else - public: - explicit AutoAssertReportedException(JSContext*) {} - void reset() {} -#endif -}; - -JSScript* -frontend::CompileGlobalScript(JSContext* cx, ScopeKind scopeKind, - const ReadOnlyCompileOptions& options, - SourceBufferHolder& srcBuf, - ScriptSourceObject** sourceObjectOut) -{ - MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic); - AutoAssertReportedException assertException(cx); - BytecodeCompiler compiler(cx, options, srcBuf); - AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut); - JSScript* script = compiler.compileGlobalScript(scopeKind); - if (!script) { - return nullptr; - } - assertException.reset(); - return script; -} - -#if defined(JS_BUILD_BINAST) -JSScript* -frontend::CompileGlobalBinASTScript(JSContext* cx, LifoAlloc& alloc, const ReadOnlyCompileOptions& options, - const uint8_t* src, size_t len, ScriptSourceObject** sourceObjectOut) -{ - AutoAssertReportedException assertException(cx); - - frontend::UsedNameTracker usedNames(cx); - - RootedScriptSourceObject sourceObj(cx, CreateScriptSourceObject(cx, options)); - - if (!sourceObj) { - return nullptr; - } - - if (!sourceObj->source()->setBinASTSourceCopy(cx, src, len)) { - return nullptr; - } - - RootedScript script(cx, JSScript::Create(cx, options, sourceObj, 0, len, 0, len)); - - if (!script) { - return nullptr; - } - - Directives directives(options.strictOption); - GlobalSharedContext globalsc(cx, ScopeKind::Global, directives, options.extraWarningsOption); - - frontend::BinASTParser<BinTokenReaderMultipart> parser(cx, alloc, usedNames, options, sourceObj); - - // Metadata stores internal pointers, so we must use the same buffer every time, including for lazy parses - ScriptSource* ss = sourceObj->source(); - BinASTSourceMetadata* metadata = nullptr; - auto parsed = parser.parse(&globalsc, ss->binASTSource(), ss->length(), &metadata); - - if (parsed.isErr()) { - return nullptr; - } - - sourceObj->source()->setBinASTSourceMetadata(metadata); - - BytecodeEmitter bce(nullptr, &parser, &globalsc, script, nullptr, 0); - - if (!bce.init()) { - return nullptr; - } - - ParseNode *pn = parsed.unwrap(); - if (!bce.emitScript(pn)) { - return nullptr; - } - - if (sourceObjectOut) { - *sourceObjectOut = sourceObj; + HandleObject environment() { + return environment_; } - assertException.reset(); - return script; -} - -#endif // JS_BUILD_BINAST - -JSScript* -frontend::CompileEvalScript(JSContext* cx, HandleObject environment, - HandleScope enclosingScope, - const ReadOnlyCompileOptions& options, - SourceBufferHolder& srcBuf, - ScriptSourceObject** sourceObjectOut) -{ - AutoAssertReportedException assertException(cx); - BytecodeCompiler compiler(cx, options, srcBuf); - AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut); - JSScript* script = compiler.compileEvalScript(environment, enclosingScope); - if (!script) { - return nullptr; - } - assertException.reset(); - return script; - -} - -ModuleObject* -frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& optionsInput, - SourceBufferHolder& srcBuf, - ScriptSourceObject** sourceObjectOut) -{ - MOZ_ASSERT(srcBuf.get()); - MOZ_ASSERT_IF(sourceObjectOut, *sourceObjectOut == nullptr); - - AutoAssertReportedException assertException(cx); - - CompileOptions options(cx, optionsInput); - options.maybeMakeStrictMode(true); // ES6 10.2.1 Module code is always strict mode code. - options.setIsRunOnce(true); - options.allowHTMLComments = false; - - BytecodeCompiler compiler(cx, options, srcBuf); - AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut); - - if (!compiler.prepareModuleParse()) { - return nullptr; - } - - RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - ModuleObject* module = compiler.compileModule(emptyGlobalScope); - if (!module) { - return nullptr; - } - - assertException.reset(); - return module; -} - -ModuleObject* -frontend::CompileModule(JSContext* cx, const JS::ReadOnlyCompileOptions& options, - SourceBufferHolder& srcBuf) -{ - AutoAssertReportedException assertException(cx); - - if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global())) { - return nullptr; - } - - RootedModuleObject module(cx, CompileModule(cx, options, srcBuf, nullptr)); - if (!module) { - return nullptr; - } - - // This happens in GlobalHelperThreadState::finishModuleParseTask() when a - // module is compiled off thread. - if (!ModuleObject::Freeze(cx, module)) { - return nullptr; - } - - assertException.reset(); - return module; -} - -// When leaving this scope, the given function should either: -// * be linked to a fully compiled script -// * remain linking to a lazy script -class MOZ_STACK_CLASS AutoAssertFunctionDelazificationCompletion -{ -#ifdef DEBUG - RootedFunction fun_; -#endif - - public: - AutoAssertFunctionDelazificationCompletion(JSContext* cx, HandleFunction fun) -#ifdef DEBUG - : fun_(cx, fun) -#endif - { - MOZ_ASSERT(fun_->isInterpretedLazy()); - MOZ_ASSERT(!fun_->lazyScript()->hasScript()); - } - - ~AutoAssertFunctionDelazificationCompletion() { -#ifdef DEBUG - if (!fun_) { - return; - } -#endif - - // If fun_ is not nullptr, it means delazification doesn't complete. - // Assert that the function keeps linking to lazy script - MOZ_ASSERT(fun_->isInterpretedLazy()); - MOZ_ASSERT(!fun_->lazyScript()->hasScript()); - } - - void complete() { - // Assert the completion of delazification and forget the function. - MOZ_ASSERT(fun_->hasScript()); - MOZ_ASSERT(!fun_->hasUncompletedScript()); - -#ifdef DEBUG - fun_ = nullptr; -#endif + EvalSharedContext* sharedContext() { + return &evalsc_; } }; -bool -frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length) -{ - MOZ_ASSERT(cx->compartment() == lazy->functionNonDelazifying()->compartment()); - - // We can only compile functions whose parents have previously been - // compiled, because compilation requires full information about the - // function's immediately enclosing scope. - MOZ_ASSERT(lazy->enclosingScriptHasEverBeenCompiled()); - - MOZ_ASSERT(!lazy->isBinAST()); - - AutoAssertReportedException assertException(cx); - Rooted<JSFunction*> fun(cx, lazy->functionNonDelazifying()); - AutoAssertFunctionDelazificationCompletion delazificationCompletion(cx, fun); - - JS::CompileOptions options(cx); - options.setMutedErrors(lazy->mutedErrors()) - .setFileAndLine(lazy->filename(), lazy->lineno()) - .setColumn(lazy->column()) - .setScriptSourceOffset(lazy->sourceStart()) - .setNoScriptRval(false) - .setSelfHostingMode(false); - - // Update statistics to find out if we are delazifying just after having - // lazified. Note that we are interested in the delta between end of - // syntax parsing and start of full parsing, so we do this now rather than - // after parsing below. - if (!lazy->scriptSource()->parseEnded().IsNull()) { - const mozilla::TimeDuration delta = ReallyNow() - - lazy->scriptSource()->parseEnded(); - - // Differentiate between web-facing and privileged code, to aid - // with optimization. Due to the number of calls to this function, - // we use `cx->runningWithTrustedPrincipals`, which is fast but - // will classify addons alongside with web-facing code. - const int HISTOGRAM = cx->runningWithTrustedPrincipals() - ? JS_TELEMETRY_PRIVILEGED_PARSER_COMPILE_LAZY_AFTER_MS - : JS_TELEMETRY_WEB_PARSER_COMPILE_LAZY_AFTER_MS; - cx->runtime()->addTelemetry(HISTOGRAM, delta.ToMilliseconds()); - } - - UsedNameTracker usedNames(cx); - - RootedScriptSourceObject sourceObject(cx, &lazy->sourceObject()); - Parser<FullParseHandler, char16_t> parser(cx, cx->tempLifoAlloc(), options, chars, length, - /* foldConstants = */ true, usedNames, nullptr, - lazy, sourceObject, lazy->parseGoal()); - if (!parser.checkOptions()) { - return false; - } - - ParseNode* pn = parser.standaloneLazyFunction(fun, lazy->toStringStart(), - lazy->strict(), lazy->generatorKind(), - lazy->asyncKind()); - if (!pn) { - return false; - } - - Rooted<JSScript*> script(cx, JSScript::Create(cx, options, sourceObject, - lazy->sourceStart(), lazy->sourceEnd(), - lazy->toStringStart(), lazy->toStringEnd())); - if (!script) { - return false; - } +extern JSScript* +CompileEvalScript(EvalScriptInfo& info, JS::SourceText<char16_t>& srcBuf); - if (lazy->isLikelyConstructorWrapper()) { - script->setLikelyConstructorWrapper(); - } - if (lazy->hasBeenCloned()) { - script->setHasBeenCloned(); - } - - BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->as<CodeNode>().funbox(), script, lazy, - pn->pn_pos, BytecodeEmitter::LazyFunction); - if (!bce.init()) { - return false; - } - - if (!bce.emitFunctionScript(&pn->as<CodeNode>(), BytecodeEmitter::TopLevelFunction::Yes)) { - return false; - } - - delazificationCompletion.complete(); - assertException.reset(); - return true; -} - -#ifdef JS_BUILD_BINAST - -bool -frontend::CompileLazyBinASTFunction(JSContext* cx, Handle<LazyScript*> lazy, const uint8_t* buf, size_t length) +class MOZ_STACK_CLASS ModuleInfo final + : public BytecodeCompiler { - MOZ_ASSERT(cx->compartment() == lazy->functionNonDelazifying()->compartment()); - - // We can only compile functions whose parents have previously been - // compiled, because compilation requires full information about the - // function's immediately enclosing scope. - MOZ_ASSERT(lazy->enclosingScriptHasEverBeenCompiled()); - MOZ_ASSERT(lazy->isBinAST()); - - AutoAssertReportedException assertException(cx); - Rooted<JSFunction*> fun(cx, lazy->functionNonDelazifying()); - AutoAssertFunctionDelazificationCompletion delazificationCompletion(cx, fun); - - CompileOptions options(cx); - options.setMutedErrors(lazy->mutedErrors()) - .setFileAndLine(lazy->filename(), lazy->lineno()) - .setColumn(lazy->column()) - .setScriptSourceOffset(lazy->sourceStart()) - .setNoScriptRval(false) - .setSelfHostingMode(false); - - UsedNameTracker usedNames(cx); - - RootedScriptSourceObject sourceObj(cx, &lazy->sourceObject()); - MOZ_ASSERT(sourceObj); - - RootedScript script(cx, JSScript::Create(cx, options, sourceObj, lazy->sourceStart(), lazy->sourceEnd(), - lazy->sourceStart(), lazy->sourceEnd())); - - if (!script) { - return false; - } - - if (lazy->hasBeenCloned()) { - script->setHasBeenCloned(); - } - - frontend::BinASTParser<BinTokenReaderMultipart> parser(cx, cx->tempLifoAlloc(), - usedNames, options, sourceObj, - lazy); + public: + ModuleInfo(JSContext* cx, const JS::ReadOnlyCompileOptions& options) + : BytecodeCompiler(cx, options) + {} +}; - auto parsed = parser.parseLazyFunction(lazy->scriptSource(), lazy->sourceStart()); - - if (parsed.isErr()) { - return false; - } - - ParseNode *pn = parsed.unwrap(); - - BytecodeEmitter bce(nullptr, &parser, pn->as<CodeNode>().funbox(), script, - lazy, pn->pn_pos, BytecodeEmitter::LazyFunction); - - if (!bce.init()) { - return false; - } - - if (!bce.emitFunctionScript(&pn->as<CodeNode>(), BytecodeEmitter::TopLevelFunction::Yes)) { - return false; - } - - delazificationCompletion.complete(); - assertException.reset(); - return script; -} - -#endif // JS_BUILD_BINAST - -bool -frontend::CompileStandaloneFunction(JSContext* cx, MutableHandleFunction fun, - const JS::ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf, - const Maybe<uint32_t>& parameterListEnd, - HandleScope enclosingScope /* = nullptr */) +class MOZ_STACK_CLASS StandaloneFunctionInfo final + : public BytecodeCompiler { - AutoAssertReportedException assertException(cx); - - BytecodeCompiler compiler(cx, options, srcBuf); - if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) { - return false; - } - - RootedScope scope(cx, enclosingScope); - if (!scope) { - scope = &cx->global()->emptyGlobalScope(); - } - - CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::NotGenerator, - FunctionAsyncKind::SyncFunction, - parameterListEnd, scope); - if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) { - return false; - } - - assertException.reset(); - return true; -} - -bool -frontend::CompileStandaloneGenerator(JSContext* cx, MutableHandleFunction fun, - const JS::ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf, - const Maybe<uint32_t>& parameterListEnd) -{ - AutoAssertReportedException assertException(cx); + public: + StandaloneFunctionInfo(JSContext* cx, const JS::ReadOnlyCompileOptions& options) + : BytecodeCompiler(cx, options) + {} +}; - BytecodeCompiler compiler(cx, options, srcBuf); - if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) { - return false; - } - - RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::Generator, - FunctionAsyncKind::SyncFunction, - parameterListEnd, - emptyGlobalScope); - if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) { - return false; - } - - assertException.reset(); - return true; -} - -bool -frontend::CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf, - const Maybe<uint32_t>& parameterListEnd) -{ - AutoAssertReportedException assertException(cx); - - BytecodeCompiler compiler(cx, options, srcBuf); - if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) { - return false; - } +extern MOZ_MUST_USE bool +CompileLazyFunction(JSContext* cx, JS::Handle<LazyScript*> lazy, + const char16_t* units, size_t length); - RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::NotGenerator, - FunctionAsyncKind::AsyncFunction, - parameterListEnd, - emptyGlobalScope); - if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) { - return false; - } - - assertException.reset(); - return true; -} +extern MOZ_MUST_USE bool +CompileLazyFunction(JSContext* cx, JS::Handle<LazyScript*> lazy, + const mozilla::Utf8Unit* units, size_t length); -bool -frontend::CompileStandaloneAsyncGenerator(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf, - const Maybe<uint32_t>& parameterListEnd) -{ - AutoAssertReportedException assertException(cx); - - BytecodeCompiler compiler(cx, options, srcBuf); - if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) { - return false; - } +} // namespace frontend - RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::Generator, - FunctionAsyncKind::AsyncFunction, - parameterListEnd, - emptyGlobalScope); - if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) { - return false; - } +} // namespace js - assertException.reset(); - return true; -} +#endif // frontend_BytecodeCompilation_h
--- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -1,143 +1,336 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 "frontend/BytecodeCompiler.h" +#include "mozilla/Attributes.h" #include "mozilla/IntegerPrintfMacros.h" #include "mozilla/Maybe.h" +#include "mozilla/Utf8.h" #include "builtin/ModuleObject.h" #if defined(JS_BUILD_BINAST) # include "frontend/BinSource.h" #endif // JS_BUILD_BINAST +#include "frontend/BytecodeCompilation.h" #include "frontend/BytecodeEmitter.h" +#include "frontend/EitherParser.h" #include "frontend/ErrorReporter.h" #include "frontend/FoldConstants.h" +#include "frontend/ModuleSharedContext.h" #include "frontend/Parser.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "vm/GlobalObject.h" #include "vm/JSContext.h" #include "vm/JSScript.h" #include "vm/TraceLogging.h" #include "wasm/AsmJS.h" #include "vm/EnvironmentObject-inl.h" #include "vm/GeckoProfiler-inl.h" #include "vm/JSContext-inl.h" using namespace js; using namespace js::frontend; using mozilla::Maybe; using mozilla::Nothing; +using mozilla::Utf8Unit; using JS::CompileOptions; using JS::ReadOnlyCompileOptions; -using JS::SourceBufferHolder; +using JS::SourceText; + +// CompileScript independently returns the ScriptSourceObject (SSO) for the +// compile. This is used by off-thread script compilation (OT-SC). +// +// OT-SC cannot initialize the SSO when it is first constructed because the +// SSO is allocated initially in a separate compartment. +// +// After OT-SC, the separate compartment is merged with the main compartment, +// at which point the JSScripts created become observable by the debugger via +// memory-space scanning. +// +// Whatever happens to the top-level script compilation (even if it fails and +// returns null), we must finish initializing the SSO. This is because there +// may be valid inner scripts observable by the debugger which reference the +// partially-initialized SSO. +class MOZ_STACK_CLASS AutoInitializeSourceObject +{ + BytecodeCompiler& compiler_; + ScriptSourceObject** sourceObjectOut_; + + public: + AutoInitializeSourceObject(BytecodeCompiler& compiler, + ScriptSourceObject** sourceObjectOut) + : compiler_(compiler), + sourceObjectOut_(sourceObjectOut) + { } + + inline ~AutoInitializeSourceObject() { + if (sourceObjectOut_) { + *sourceObjectOut_ = compiler_.sourceObjectPtr(); + } + } +}; -// The BytecodeCompiler class contains resources common to compiling scripts and -// function bodies. -class MOZ_STACK_CLASS BytecodeCompiler +// RAII class to check the frontend reports an exception when it fails to +// compile a script. +class MOZ_RAII AutoAssertReportedException +{ +#ifdef DEBUG + JSContext* cx_; + bool check_; + + public: + explicit AutoAssertReportedException(JSContext* cx) + : cx_(cx), + check_(true) + {} + void reset() { + check_ = false; + } + ~AutoAssertReportedException() { + if (!check_) { + return; + } + + if (!cx_->helperThread()) { + MOZ_ASSERT(cx_->isExceptionPending()); + return; + } + + ParseTask* task = cx_->helperThread()->parseTask(); + MOZ_ASSERT(task->outOfMemory || + task->overRecursed || + !task->errors.empty()); + } +#else + public: + explicit AutoAssertReportedException(JSContext*) {} + void reset() {} +#endif +}; + +template<typename Unit> +class MOZ_STACK_CLASS frontend::SourceAwareCompiler { protected: - AutoKeepAtoms keepAtoms; - - JSContext* cx; - const ReadOnlyCompileOptions& options; - SourceBufferHolder& sourceBuffer; + SourceText<Unit>& sourceBuffer_; - RootedScriptSourceObject sourceObject; - ScriptSource* scriptSource; - - Maybe<UsedNameTracker> usedNames; - Maybe<Parser<SyntaxParseHandler, char16_t>> syntaxParser; - Maybe<Parser<FullParseHandler, char16_t>> parser; + Maybe<Parser<SyntaxParseHandler, Unit>> syntaxParser; + Maybe<Parser<FullParseHandler, Unit>> parser; - Directives directives; - - RootedScript script; + using TokenStreamPosition = frontend::TokenStreamPosition<Unit>; - public: - // Construct an object passing mandatory arguments. - BytecodeCompiler(JSContext* cx, - const ReadOnlyCompileOptions& options, - SourceBufferHolder& sourceBuffer); - - ScriptSourceObject* sourceObjectPtr() const { - return sourceObject.get(); + protected: + explicit SourceAwareCompiler(SourceText<Unit>& sourceBuffer) + : sourceBuffer_(sourceBuffer) + { + MOZ_ASSERT(sourceBuffer_.get() != nullptr); } // Call this before calling compile{Global,Eval}Script. - MOZ_MUST_USE bool prepareScriptParse() { - return createSourceAndParser(ParseGoal::Script) && createCompleteScript(); - } - - JSScript* compileGlobalScript(ScopeKind scopeKind); - JSScript* compileEvalScript(HandleObject environment, HandleScope enclosingScope); - - // Call this before calling compileModule. - MOZ_MUST_USE bool prepareModuleParse() { - return createSourceAndParser(ParseGoal::Module) && createCompleteScript(); + MOZ_MUST_USE bool prepareScriptParse(BytecodeCompiler& info) { + return createSourceAndParser(info, ParseGoal::Script) && createCompleteScript(info); } - ModuleObject* compileModule(HandleScope enclosingScope); - - // Call this before calling parseStandaloneFunction. - MOZ_MUST_USE bool prepareStandaloneFunctionParse(const Maybe<uint32_t>& parameterListEnd) { - return createSourceAndParser(ParseGoal::Script, parameterListEnd); - } - - // Call this before calling compileStandaloneFunction. - CodeNode* parseStandaloneFunction(MutableHandleFunction fun, GeneratorKind generatorKind, - FunctionAsyncKind asyncKind, - const Maybe<uint32_t>& parameterListEnd, - HandleScope enclosingScope); - - bool compileStandaloneFunction(CodeNode* parsedFunction, MutableHandleFunction fun); - - private: - void assertSourceAndParserCreated() const { - MOZ_ASSERT(sourceObject != nullptr); - MOZ_ASSERT(scriptSource != nullptr); - MOZ_ASSERT(usedNames.isSome()); + void assertSourceAndParserCreated(BytecodeCompiler& info) const { + info.assertSourceCreated(); + MOZ_ASSERT(info.usedNames.isSome()); MOZ_ASSERT(parser.isSome()); } - void assertSourceParserAndScriptCreated() const { - assertSourceAndParserCreated(); - MOZ_ASSERT(script != nullptr); + void assertSourceParserAndScriptCreated(BytecodeCompiler& info) { + assertSourceAndParserCreated(info); + MOZ_ASSERT(info.script != nullptr); } - JSScript* compileScript(HandleObject environment, SharedContext* sc); - bool checkLength(); - bool createScriptSource(const Maybe<uint32_t>& parameterListEnd); - bool canLazilyParse(); - bool createParser(ParseGoal goal); - bool createSourceAndParser(ParseGoal goal, - const Maybe<uint32_t>& parameterListEnd = Nothing()); + MOZ_MUST_USE bool emplaceEmitter(BytecodeCompiler& info, Maybe<BytecodeEmitter>& emitter, + SharedContext* sharedContext) + { + return info.emplaceEmitter(emitter, EitherParser(parser.ptr()), sharedContext); + } + + MOZ_MUST_USE bool createSourceAndParser(BytecodeCompiler& compiler, ParseGoal goal, + const Maybe<uint32_t>& parameterListEnd = Nothing()); // This assumes the created script's offsets in the source used to parse it // are the same as are used to compute its Function.prototype.toString() // value. - bool createCompleteScript(); + MOZ_MUST_USE bool createCompleteScript(BytecodeCompiler& info) { + uint32_t toStringStart = 0; + uint32_t len = sourceBuffer_.length(); + uint32_t toStringEnd = len; + return info.internalCreateScript(toStringStart, toStringEnd, len); + } + + MOZ_MUST_USE bool + handleParseFailure(BytecodeCompiler& compiler, const Directives& newDirectives, + TokenStreamPosition& startPosition); +}; + +template<typename Unit> +class MOZ_STACK_CLASS frontend::ScriptCompiler + : public SourceAwareCompiler<Unit> +{ + using Base = SourceAwareCompiler<Unit>; + + protected: + using Base::parser; + using Base::sourceBuffer_; + + using Base::assertSourceParserAndScriptCreated; + using Base::emplaceEmitter; + using Base::handleParseFailure; + + using typename Base::TokenStreamPosition; + + public: + explicit ScriptCompiler(SourceText<Unit>& srcBuf) + : Base(srcBuf) + {} + + MOZ_MUST_USE bool prepareScriptParse(BytecodeCompiler& compiler) { + return Base::prepareScriptParse(compiler); + } - // This uses explicitly-provided toString offsets as the created script's - // offsets in the source. - bool createScript(uint32_t toStringStart, uint32_t toStringEnd); + JSScript* compileScript(BytecodeCompiler& compiler, HandleObject environment, + SharedContext* sc); +}; + +template<typename Unit> +static JSScript* +CreateGlobalScript(GlobalScriptInfo& info, JS::SourceText<Unit>& srcBuf, + ScriptSourceObject** sourceObjectOut = nullptr) +{ + AutoAssertReportedException assertException(info.context()); + + frontend::ScriptCompiler<Unit> compiler(srcBuf); + AutoInitializeSourceObject autoSSO(info, sourceObjectOut); + + if (!compiler.prepareScriptParse(info)) { + return nullptr; + } + + JSScript* script = compiler.compileScript(info, nullptr, info.sharedContext()); + if (!script) { + return nullptr; + } + + assertException.reset(); + return script; +} + +JSScript* +frontend::CompileGlobalScript(GlobalScriptInfo& info, JS::SourceText<char16_t>& srcBuf, + ScriptSourceObject** sourceObjectOut /* = nullptr */) +{ + return CreateGlobalScript(info, srcBuf, sourceObjectOut); +} + +JSScript* +frontend::CompileGlobalScript(GlobalScriptInfo& info, JS::SourceText<Utf8Unit>& srcBuf, + ScriptSourceObject** sourceObjectOut /* = nullptr */) +{ + return CreateGlobalScript(info, srcBuf, sourceObjectOut); +} - using TokenStreamPosition = frontend::TokenStreamPosition<char16_t>; +template<typename Unit> +static JSScript* +CreateEvalScript(frontend::EvalScriptInfo& info, SourceText<Unit>& srcBuf) +{ + AutoAssertReportedException assertException(info.context()); + + frontend::ScriptCompiler<Unit> compiler(srcBuf); + if (!compiler.prepareScriptParse(info)) { + return nullptr; + } + + JSScript* script = compiler.compileScript(info, info.environment(), info.sharedContext()); + if (!script) { + return nullptr; + } + + assertException.reset(); + return script; +} + +JSScript* +frontend::CompileEvalScript(EvalScriptInfo& info, JS::SourceText<char16_t>& srcBuf) +{ + return CreateEvalScript(info, srcBuf); +} + +template<typename Unit> +class MOZ_STACK_CLASS frontend::ModuleCompiler final + : public SourceAwareCompiler<Unit> +{ + using Base = SourceAwareCompiler<Unit>; + + using Base::assertSourceParserAndScriptCreated; + using Base::createCompleteScript; + using Base::createSourceAndParser; + using Base::emplaceEmitter; + using Base::parser; + + public: + explicit ModuleCompiler(SourceText<Unit>& srcBuf) + : Base(srcBuf) + {} - bool emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext); - bool handleParseFailure(const Directives& newDirectives, TokenStreamPosition& startPosition); - bool deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObject environment); + ModuleObject* compile(ModuleInfo& info); +}; + +template<typename Unit> +class MOZ_STACK_CLASS frontend::StandaloneFunctionCompiler final + : public SourceAwareCompiler<Unit> +{ + using Base = SourceAwareCompiler<Unit>; + + using Base::assertSourceAndParserCreated; + using Base::createSourceAndParser; + using Base::emplaceEmitter; + using Base::handleParseFailure; + using Base::parser; + using Base::sourceBuffer_; + + using typename Base::TokenStreamPosition; + + public: + explicit StandaloneFunctionCompiler(SourceText<Unit>& srcBuf) + : Base(srcBuf) + {} + + MOZ_MUST_USE bool prepare(StandaloneFunctionInfo& info, + const Maybe<uint32_t>& parameterListEnd) + { + return createSourceAndParser(info, ParseGoal::Script, parameterListEnd); + } + + CodeNode* parse(StandaloneFunctionInfo& info, HandleFunction fun, + HandleScope enclosingScope, GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, const Maybe<uint32_t>& parameterListEnd); + + MOZ_MUST_USE bool compile(MutableHandleFunction fun, StandaloneFunctionInfo& info, + CodeNode* parsedFunction); + + private: + // Create a script for a function with the given toString offsets in source + // text. + MOZ_MUST_USE bool createFunctionScript(StandaloneFunctionInfo& info, + uint32_t toStringStart, uint32_t toStringEnd) + { + return info.internalCreateScript(toStringStart, toStringEnd, sourceBuffer_.length()); + } }; AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx, const TraceLoggerTextId id, const ErrorReporter& errorReporter) #ifdef JS_TRACE_LOGGING : logger_(TraceLoggerForCurrentThread(cx)) { // If the tokenizer hasn't yet gotten any tokens, use the line and column @@ -182,160 +375,139 @@ AutoFrontendTraceLog::AutoFrontendTraceL frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(), line, column); frontendLog_.emplace(logger_, *frontendEvent_); typeLog_.emplace(logger_, id); } #else { } #endif -BytecodeCompiler::BytecodeCompiler(JSContext* cx, - const ReadOnlyCompileOptions& options, - SourceBufferHolder& sourceBuffer) +BytecodeCompiler::BytecodeCompiler(JSContext* cx, const ReadOnlyCompileOptions& options) : keepAtoms(cx), cx(cx), options(options), - sourceBuffer(sourceBuffer), sourceObject(cx), - scriptSource(nullptr), directives(options.strictOption), script(cx) -{ - MOZ_ASSERT(sourceBuffer.get()); -} - -bool -BytecodeCompiler::checkLength() -{ - // Note this limit is simply so we can store sourceStart and sourceEnd in - // JSScript as 32-bits. It could be lifted fairly easily, since the compiler - // is using size_t internally already. - if (sourceBuffer.length() > UINT32_MAX) { - if (!cx->helperThread()) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, - JSMSG_SOURCE_TOO_LONG); - } - return false; - } - return true; -} +{} bool BytecodeCompiler::createScriptSource(const Maybe<uint32_t>& parameterListEnd) { - if (!checkLength()) { - return false; - } - sourceObject = CreateScriptSourceObject(cx, options, parameterListEnd); if (!sourceObject) { return false; } scriptSource = sourceObject->source(); + return true; +} +template<typename Unit> +bool +BytecodeCompiler::assignSource(SourceText<Unit>& sourceBuffer) +{ if (!cx->realm()->behaviors().discardSource()) { if (options.sourceIsLazy) { scriptSource->setSourceRetrievable(); } else if (!scriptSource->setSourceCopy(cx, sourceBuffer)) { return false; } } return true; } bool -BytecodeCompiler::canLazilyParse() +BytecodeCompiler::canLazilyParse() const { return options.canLazilyParse && !cx->realm()->behaviors().disableLazyParsing() && !cx->realm()->behaviors().discardSource() && !options.sourceIsLazy && !cx->lcovEnabled() && // Disabled during record/replay. The replay debugger requires // scripts to be constructed in a consistent order, which might not // happen with lazy parsing. !mozilla::recordreplay::IsRecordingOrReplaying(); } +template<typename Unit> bool -BytecodeCompiler::createParser(ParseGoal goal) +frontend::SourceAwareCompiler<Unit>::createSourceAndParser(BytecodeCompiler& info, ParseGoal goal, + const Maybe<uint32_t>& parameterListEnd /* = Nothing() */) { - usedNames.emplace(cx); + if (!info.createScriptSource(parameterListEnd)) { + return false; + } - if (canLazilyParse()) { - syntaxParser.emplace(cx, cx->tempLifoAlloc(), options, - sourceBuffer.get(), sourceBuffer.length(), - /* foldConstants = */ false, *usedNames, nullptr, nullptr, - sourceObject, goal); + if (!info.assignSource(sourceBuffer_)) { + return false; + } + + info.createUsedNames(); + + if (info.canLazilyParse()) { + syntaxParser.emplace(info.cx, info.cx->tempLifoAlloc(), info.options, + sourceBuffer_.units(), sourceBuffer_.length(), + /* foldConstants = */ false, *info.usedNames, nullptr, nullptr, + info.sourceObject, goal); if (!syntaxParser->checkOptions()) { return false; } } - parser.emplace(cx, cx->tempLifoAlloc(), options, sourceBuffer.get(), sourceBuffer.length(), - /* foldConstants = */ true, *usedNames, syntaxParser.ptrOr(nullptr), nullptr, - sourceObject, goal); - parser->ss = scriptSource; + parser.emplace(info.cx, info.cx->tempLifoAlloc(), info.options, + sourceBuffer_.units(), sourceBuffer_.length(), /* foldConstants = */ true, + *info.usedNames, syntaxParser.ptrOr(nullptr), nullptr, info.sourceObject, goal); + parser->ss = info.scriptSource; return parser->checkOptions(); } bool -BytecodeCompiler::createSourceAndParser(ParseGoal goal, - const Maybe<uint32_t>& parameterListEnd /* = Nothing() */) +BytecodeCompiler::internalCreateScript(uint32_t toStringStart, uint32_t toStringEnd, + uint32_t sourceBufferLength) { - return createScriptSource(parameterListEnd) && - createParser(goal); -} - -bool -BytecodeCompiler::createCompleteScript() -{ - return createScript(0, sourceBuffer.length()); -} - -bool -BytecodeCompiler::createScript(uint32_t toStringStart, uint32_t toStringEnd) -{ - script = JSScript::Create(cx, options, - sourceObject, /* sourceStart = */ 0, sourceBuffer.length(), + script = JSScript::Create(cx, options, sourceObject, /* sourceStart = */ 0, sourceBufferLength, toStringStart, toStringEnd); return script != nullptr; } bool -BytecodeCompiler::emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext) +BytecodeCompiler::emplaceEmitter(Maybe<BytecodeEmitter>& emitter, + const EitherParser& parser, SharedContext* sharedContext) { BytecodeEmitter::EmitterMode emitterMode = options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal; - emitter.emplace(/* parent = */ nullptr, parser.ptr(), sharedContext, script, + emitter.emplace(/* parent = */ nullptr, parser, sharedContext, script, /* lazyScript = */ nullptr, options.lineno, emitterMode); return emitter->init(); } +template<typename Unit> bool -BytecodeCompiler::handleParseFailure(const Directives& newDirectives, - TokenStreamPosition& startPosition) +frontend::SourceAwareCompiler<Unit>::handleParseFailure(BytecodeCompiler& info, + const Directives& newDirectives, + TokenStreamPosition& startPosition) { if (parser->hadAbortedSyntaxParse()) { // Hit some unrecoverable ambiguity during an inner syntax parse. // Syntax parsing has now been disabled in the parser, so retry // the parse. parser->clearAbortedSyntaxParse(); - } else if (parser->anyChars.hadError() || directives == newDirectives) { + } else if (parser->anyChars.hadError() || info.directives == newDirectives) { return false; } parser->tokenStream.seek(startPosition); // Assignment must be monotonic to prevent reparsing iloops - MOZ_ASSERT_IF(directives.strict(), newDirectives.strict()); - MOZ_ASSERT_IF(directives.asmJS(), newDirectives.asmJS()); - directives = newDirectives; + MOZ_ASSERT_IF(info.directives.strict(), newDirectives.strict()); + MOZ_ASSERT_IF(info.directives.asmJS(), newDirectives.asmJS()); + info.directives = newDirectives; return true; } bool BytecodeCompiler::deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObject environment) { RootedObject env(cx, environment); while (env->is<EnvironmentObject>() || env->is<DebugEnvironmentProxy>()) { @@ -352,28 +524,32 @@ BytecodeCompiler::deoptimizeArgumentsInE } } env = env->enclosingEnvironment(); } return true; } +template<typename Unit> JSScript* -BytecodeCompiler::compileScript(HandleObject environment, SharedContext* sc) +frontend::ScriptCompiler<Unit>::compileScript(BytecodeCompiler& info, HandleObject environment, + SharedContext* sc) { - assertSourceParserAndScriptCreated(); + assertSourceParserAndScriptCreated(info); - TokenStreamPosition startPosition(keepAtoms, parser->tokenStream); + TokenStreamPosition startPosition(info.keepAtoms, parser->tokenStream); Maybe<BytecodeEmitter> emitter; - if (!emplaceEmitter(emitter, sc)) { + if (!emplaceEmitter(info, emitter, sc)) { return nullptr; } + JSContext* cx = info.cx; + for (;;) { ParseNode* pn; { AutoGeckoProfilerEntry pseudoFrame(cx, "script parsing"); if (sc->isEvalContext()) { pn = parser->evalBody(sc->asEvalContext()); } else { pn = parser->globalBody(sc->asGlobalContext()); @@ -383,96 +559,77 @@ BytecodeCompiler::compileScript(HandleOb // Successfully parsed. Emit the script. AutoGeckoProfilerEntry pseudoFrame(cx, "script emit"); if (pn) { if (sc->isEvalContext() && sc->hasDebuggerStatement() && !cx->helperThread()) { // If the eval'ed script contains any debugger statement, force construction // of arguments objects for the caller script and any other scripts it is // transitively nested inside. The debugger can access any variable on the // scope chain. - if (!deoptimizeArgumentsInEnclosingScripts(cx, environment)) { + if (!info.deoptimizeArgumentsInEnclosingScripts(cx, environment)) { return nullptr; } } if (!emitter->emitScript(pn)) { return nullptr; } break; } // Maybe we aborted a syntax parse. See if we can try again. - if (!handleParseFailure(directives, startPosition)) { + if (!handleParseFailure(info, info.directives, startPosition)) { return nullptr; } // Reset UsedNameTracker state before trying again. - usedNames->reset(); + info.usedNames->reset(); } // We have just finished parsing the source. Inform the source so that we // can compute statistics (e.g. how much time our functions remain lazy). - script->scriptSource()->recordParseEnded(); + info.script->scriptSource()->recordParseEnded(); // Enqueue an off-thread source compression task after finishing parsing. - if (!scriptSource->tryCompressOffThread(cx)) { + if (!info.scriptSource->tryCompressOffThread(cx)) { return nullptr; } MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending()); - return script; + return info.script; } -JSScript* -BytecodeCompiler::compileGlobalScript(ScopeKind scopeKind) +template<typename Unit> +ModuleObject* +frontend::ModuleCompiler<Unit>::compile(ModuleInfo& info) { - GlobalSharedContext globalsc(cx, scopeKind, directives, options.extraWarningsOption); - - if (!prepareScriptParse()) { + if (!createSourceAndParser(info, ParseGoal::Module) || !createCompleteScript(info)) { return nullptr; } - return compileScript(nullptr, &globalsc); -} - -JSScript* -BytecodeCompiler::compileEvalScript(HandleObject environment, HandleScope enclosingScope) -{ - EvalSharedContext evalsc(cx, environment, enclosingScope, - directives, options.extraWarningsOption); - - if (!prepareScriptParse()) { - return nullptr; - } - - return compileScript(environment, &evalsc); -} - -ModuleObject* -BytecodeCompiler::compileModule(HandleScope enclosingScope) -{ - assertSourceParserAndScriptCreated(); + JSContext* cx = info.cx; Rooted<ModuleObject*> module(cx, ModuleObject::create(cx)); if (!module) { return nullptr; } - module->init(script); + module->init(info.script); - ModuleBuilder builder(cx, module, parser->anyChars); + ModuleBuilder builder(cx, module, parser.ptr()); + RootedScope enclosingScope(cx, &cx->global()->emptyGlobalScope()); ModuleSharedContext modulesc(cx, module, enclosingScope, builder); ParseNode* pn = parser->moduleBody(&modulesc); if (!pn) { return nullptr; } Maybe<BytecodeEmitter> emitter; - if (!emplaceEmitter(emitter, &modulesc)) { + if (!emplaceEmitter(info, emitter, &modulesc)) { return nullptr; } if (!emitter->emitScript(pn->as<CodeNode>().body())) { return nullptr; } if (!builder.initModule()) { return nullptr; @@ -481,85 +638,90 @@ BytecodeCompiler::compileModule(HandleSc RootedModuleEnvironmentObject env(cx, ModuleEnvironmentObject::create(cx, module)); if (!env) { return nullptr; } module->setInitialEnvironment(env); // Enqueue an off-thread source compression task after finishing parsing. - if (!scriptSource->tryCompressOffThread(cx)) { + if (!info.scriptSource->tryCompressOffThread(cx)) { return nullptr; } MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending()); return module; } // Parse a standalone JS function, which might appear as the value of an // event handler attribute in an HTML <INPUT> tag, or in a Function() // constructor. +template<typename Unit> CodeNode* -BytecodeCompiler::parseStandaloneFunction(MutableHandleFunction fun, - GeneratorKind generatorKind, - FunctionAsyncKind asyncKind, - const Maybe<uint32_t>& parameterListEnd, - HandleScope enclosingScope) +frontend::StandaloneFunctionCompiler<Unit>::parse(StandaloneFunctionInfo& info, + HandleFunction fun, + HandleScope enclosingScope, + GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, + const Maybe<uint32_t>& parameterListEnd) { MOZ_ASSERT(fun); MOZ_ASSERT(fun->isTenured()); - assertSourceAndParserCreated(); + assertSourceAndParserCreated(info); - TokenStreamPosition startPosition(keepAtoms, parser->tokenStream); + TokenStreamPosition startPosition(info.keepAtoms, parser->tokenStream); // Speculatively parse using the default directives implied by the context. // If a directive is encountered (e.g., "use strict") that changes how the // function should have been parsed, we backup and reparse with the new set // of directives. ParseNode* fn; do { - Directives newDirectives = directives; + Directives newDirectives = info.directives; fn = parser->standaloneFunction(fun, enclosingScope, parameterListEnd, generatorKind, - asyncKind, directives, &newDirectives); - if (!fn && !handleParseFailure(newDirectives, startPosition)) { + asyncKind, info.directives, &newDirectives); + if (!fn && !handleParseFailure(info, newDirectives, startPosition)) { return nullptr; } } while (!fn); return &fn->as<CodeNode>(); } // Compile a standalone JS function. +template<typename Unit> bool -BytecodeCompiler::compileStandaloneFunction(CodeNode* parsedFunction, MutableHandleFunction fun) +frontend::StandaloneFunctionCompiler<Unit>::compile(MutableHandleFunction fun, + StandaloneFunctionInfo& info, + CodeNode* parsedFunction) { FunctionBox* funbox = parsedFunction->funbox(); if (funbox->function()->isInterpreted()) { MOZ_ASSERT(fun == funbox->function()); - if (!createScript(funbox->toStringStart, funbox->toStringEnd)) { + if (!createFunctionScript(info, funbox->toStringStart, funbox->toStringEnd)) { return false; } Maybe<BytecodeEmitter> emitter; - if (!emplaceEmitter(emitter, funbox)) { + if (!emplaceEmitter(info, emitter, funbox)) { return false; } if (!emitter->emitFunctionScript(parsedFunction, BytecodeEmitter::TopLevelFunction::Yes)) { return false; } } else { fun.set(funbox->function()); MOZ_ASSERT(IsAsmJSModule(fun)); } // Enqueue an off-thread source compression task after finishing parsing. - return scriptSource->tryCompressOffThread(cx); + return info.scriptSource->tryCompressOffThread(info.cx); } ScriptSourceObject* frontend::CreateScriptSourceObject(JSContext* cx, const ReadOnlyCompileOptions& options, const Maybe<uint32_t>& parameterListEnd /* = Nothing() */) { ScriptSource* ss = cx->new_<ScriptSource>(); if (!ss) { @@ -589,105 +751,16 @@ frontend::CreateScriptSourceObject(JSCon if (!ScriptSourceObject::initFromOptions(cx, sso, options)) { return nullptr; } } return sso; } -// CompileScript independently returns the ScriptSourceObject (SSO) for the -// compile. This is used by off-thread script compilation (OT-SC). -// -// OT-SC cannot initialize the SSO when it is first constructed because the -// SSO is allocated initially in a separate compartment. -// -// After OT-SC, the separate compartment is merged with the main compartment, -// at which point the JSScripts created become observable by the debugger via -// memory-space scanning. -// -// Whatever happens to the top-level script compilation (even if it fails and -// returns null), we must finish initializing the SSO. This is because there -// may be valid inner scripts observable by the debugger which reference the -// partially-initialized SSO. -class MOZ_STACK_CLASS AutoInitializeSourceObject -{ - BytecodeCompiler& compiler_; - ScriptSourceObject** sourceObjectOut_; - - public: - AutoInitializeSourceObject(BytecodeCompiler& compiler, - ScriptSourceObject** sourceObjectOut) - : compiler_(compiler), - sourceObjectOut_(sourceObjectOut) - { } - - ~AutoInitializeSourceObject() { - if (sourceObjectOut_) { - *sourceObjectOut_ = compiler_.sourceObjectPtr(); - } - } -}; - -// RAII class to check the frontend reports an exception when it fails to -// compile a script. -class MOZ_RAII AutoAssertReportedException -{ -#ifdef DEBUG - JSContext* cx_; - bool check_; - - public: - explicit AutoAssertReportedException(JSContext* cx) - : cx_(cx), - check_(true) - {} - void reset() { - check_ = false; - } - ~AutoAssertReportedException() { - if (!check_) { - return; - } - - if (!cx_->helperThread()) { - MOZ_ASSERT(cx_->isExceptionPending()); - return; - } - - ParseTask* task = cx_->helperThread()->parseTask(); - MOZ_ASSERT(task->outOfMemory || - task->overRecursed || - !task->errors.empty()); - } -#else - public: - explicit AutoAssertReportedException(JSContext*) {} - void reset() {} -#endif -}; - -JSScript* -frontend::CompileGlobalScript(JSContext* cx, ScopeKind scopeKind, - const ReadOnlyCompileOptions& options, - SourceBufferHolder& srcBuf, - ScriptSourceObject** sourceObjectOut) -{ - MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic); - AutoAssertReportedException assertException(cx); - BytecodeCompiler compiler(cx, options, srcBuf); - AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut); - JSScript* script = compiler.compileGlobalScript(scopeKind); - if (!script) { - return nullptr; - } - assertException.reset(); - return script; -} - #if defined(JS_BUILD_BINAST) JSScript* frontend::CompileGlobalBinASTScript(JSContext* cx, LifoAlloc& alloc, const ReadOnlyCompileOptions& options, const uint8_t* src, size_t len, ScriptSourceObject** sourceObjectOut) { AutoAssertReportedException assertException(cx); @@ -741,70 +814,54 @@ frontend::CompileGlobalBinASTScript(JSCo } assertException.reset(); return script; } #endif // JS_BUILD_BINAST -JSScript* -frontend::CompileEvalScript(JSContext* cx, HandleObject environment, - HandleScope enclosingScope, - const ReadOnlyCompileOptions& options, - SourceBufferHolder& srcBuf, - ScriptSourceObject** sourceObjectOut) -{ - AutoAssertReportedException assertException(cx); - BytecodeCompiler compiler(cx, options, srcBuf); - AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut); - JSScript* script = compiler.compileEvalScript(environment, enclosingScope); - if (!script) { - return nullptr; - } - assertException.reset(); - return script; - -} - -ModuleObject* -frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& optionsInput, - SourceBufferHolder& srcBuf, - ScriptSourceObject** sourceObjectOut) +template<typename Unit> +static ModuleObject* +CreateModule(JSContext* cx, const ReadOnlyCompileOptions& optionsInput, + SourceText<Unit>& srcBuf, ScriptSourceObject** sourceObjectOut) { MOZ_ASSERT(srcBuf.get()); MOZ_ASSERT_IF(sourceObjectOut, *sourceObjectOut == nullptr); AutoAssertReportedException assertException(cx); CompileOptions options(cx, optionsInput); options.maybeMakeStrictMode(true); // ES6 10.2.1 Module code is always strict mode code. options.setIsRunOnce(true); options.allowHTMLComments = false; - BytecodeCompiler compiler(cx, options, srcBuf); - AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut); + ModuleInfo info(cx, options); + AutoInitializeSourceObject autoSSO(info, sourceObjectOut); - if (!compiler.prepareModuleParse()) { - return nullptr; - } - - RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - ModuleObject* module = compiler.compileModule(emptyGlobalScope); + ModuleCompiler<char16_t> compiler(srcBuf); + ModuleObject* module = compiler.compile(info); if (!module) { return nullptr; } assertException.reset(); return module; } ModuleObject* -frontend::CompileModule(JSContext* cx, const JS::ReadOnlyCompileOptions& options, - SourceBufferHolder& srcBuf) +frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& optionsInput, + SourceText<char16_t>& srcBuf, ScriptSourceObject** sourceObjectOut) +{ + return CreateModule(cx, optionsInput, srcBuf, sourceObjectOut); +} + +template<typename Unit> +static ModuleObject* +CreateModule(JSContext* cx, const JS::ReadOnlyCompileOptions& options, SourceText<Unit>& srcBuf) { AutoAssertReportedException assertException(cx); if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global())) { return nullptr; } RootedModuleObject module(cx, CompileModule(cx, options, srcBuf, nullptr)); @@ -817,16 +874,23 @@ frontend::CompileModule(JSContext* cx, c if (!ModuleObject::Freeze(cx, module)) { return nullptr; } assertException.reset(); return module; } +ModuleObject* +frontend::CompileModule(JSContext* cx, const JS::ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf) +{ + return CreateModule(cx, options, srcBuf); +} + // When leaving this scope, the given function should either: // * be linked to a fully compiled script // * remain linking to a lazy script class MOZ_STACK_CLASS AutoAssertFunctionDelazificationCompletion { #ifdef DEBUG RootedFunction fun_; #endif @@ -860,18 +924,19 @@ class MOZ_STACK_CLASS AutoAssertFunction MOZ_ASSERT(!fun_->hasUncompletedScript()); #ifdef DEBUG fun_ = nullptr; #endif } }; -bool -frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length) +template<typename Unit> +static bool +CompileLazyFunctionImpl(JSContext* cx, Handle<LazyScript*> lazy, const Unit* units, size_t length) { MOZ_ASSERT(cx->compartment() == lazy->functionNonDelazifying()->compartment()); // We can only compile functions whose parents have previously been // compiled, because compilation requires full information about the // function's immediately enclosing scope. MOZ_ASSERT(lazy->enclosingScriptHasEverBeenCompiled()); @@ -905,19 +970,19 @@ frontend::CompileLazyFunction(JSContext* ? JS_TELEMETRY_PRIVILEGED_PARSER_COMPILE_LAZY_AFTER_MS : JS_TELEMETRY_WEB_PARSER_COMPILE_LAZY_AFTER_MS; cx->runtime()->addTelemetry(HISTOGRAM, delta.ToMilliseconds()); } UsedNameTracker usedNames(cx); RootedScriptSourceObject sourceObject(cx, &lazy->sourceObject()); - Parser<FullParseHandler, char16_t> parser(cx, cx->tempLifoAlloc(), options, chars, length, - /* foldConstants = */ true, usedNames, nullptr, - lazy, sourceObject, lazy->parseGoal()); + Parser<FullParseHandler, Unit> parser(cx, cx->tempLifoAlloc(), options, units, length, + /* foldConstants = */ true, usedNames, nullptr, + lazy, sourceObject, lazy->parseGoal()); if (!parser.checkOptions()) { return false; } ParseNode* pn = parser.standaloneLazyFunction(fun, lazy->toStringStart(), lazy->strict(), lazy->generatorKind(), lazy->asyncKind()); if (!pn) { @@ -948,16 +1013,30 @@ frontend::CompileLazyFunction(JSContext* return false; } delazificationCompletion.complete(); assertException.reset(); return true; } +bool +frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, + const char16_t* units, size_t length) +{ + return CompileLazyFunctionImpl(cx, lazy, units, length); +} + +bool +frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, + const Utf8Unit* units, size_t length) +{ + return CompileLazyFunctionImpl(cx, lazy, units, length); +} + #ifdef JS_BUILD_BINAST bool frontend::CompileLazyBinASTFunction(JSContext* cx, Handle<LazyScript*> lazy, const uint8_t* buf, size_t length) { MOZ_ASSERT(cx->compartment() == lazy->functionNonDelazifying()->compartment()); // We can only compile functions whose parents have previously been @@ -1022,112 +1101,116 @@ frontend::CompileLazyBinASTFunction(JSCo return script; } #endif // JS_BUILD_BINAST bool frontend::CompileStandaloneFunction(JSContext* cx, MutableHandleFunction fun, const JS::ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf, + JS::SourceText<char16_t>& srcBuf, const Maybe<uint32_t>& parameterListEnd, HandleScope enclosingScope /* = nullptr */) { AutoAssertReportedException assertException(cx); - BytecodeCompiler compiler(cx, options, srcBuf); - if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) { + StandaloneFunctionInfo info(cx, options); + + StandaloneFunctionCompiler<char16_t> compiler(srcBuf); + if (!compiler.prepare(info, parameterListEnd)) { return false; } RootedScope scope(cx, enclosingScope); if (!scope) { scope = &cx->global()->emptyGlobalScope(); } - CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::NotGenerator, - FunctionAsyncKind::SyncFunction, - parameterListEnd, scope); - if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) { + CodeNode* parsedFunction = compiler.parse(info ,fun, scope, GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction, parameterListEnd); + if (!parsedFunction || !compiler.compile(fun, info, parsedFunction)) { return false; } assertException.reset(); return true; } bool frontend::CompileStandaloneGenerator(JSContext* cx, MutableHandleFunction fun, const JS::ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf, + JS::SourceText<char16_t>& srcBuf, const Maybe<uint32_t>& parameterListEnd) { AutoAssertReportedException assertException(cx); - BytecodeCompiler compiler(cx, options, srcBuf); - if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) { + StandaloneFunctionInfo info(cx, options); + + StandaloneFunctionCompiler<char16_t> compiler(srcBuf); + if (!compiler.prepare(info, parameterListEnd)) { return false; } RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::Generator, - FunctionAsyncKind::SyncFunction, - parameterListEnd, - emptyGlobalScope); - if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) { + CodeNode* parsedFunction = + compiler.parse(info, fun, emptyGlobalScope, GeneratorKind::Generator, + FunctionAsyncKind::SyncFunction, parameterListEnd); + if (!parsedFunction || !compiler.compile(fun, info, parsedFunction)) { return false; } assertException.reset(); return true; } bool frontend::CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun, const ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf, + JS::SourceText<char16_t>& srcBuf, const Maybe<uint32_t>& parameterListEnd) { AutoAssertReportedException assertException(cx); - BytecodeCompiler compiler(cx, options, srcBuf); - if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) { + StandaloneFunctionInfo info(cx, options); + + StandaloneFunctionCompiler<char16_t> compiler(srcBuf); + if (!compiler.prepare(info, parameterListEnd)) { return false; } RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::NotGenerator, - FunctionAsyncKind::AsyncFunction, - parameterListEnd, - emptyGlobalScope); - if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) { + CodeNode* parsedFunction = + compiler.parse(info, fun, emptyGlobalScope, GeneratorKind::NotGenerator, + FunctionAsyncKind::AsyncFunction, parameterListEnd); + if (!parsedFunction || !compiler.compile(fun, info, parsedFunction)) { return false; } assertException.reset(); return true; } bool frontend::CompileStandaloneAsyncGenerator(JSContext* cx, MutableHandleFunction fun, const ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf, + JS::SourceText<char16_t>& srcBuf, const Maybe<uint32_t>& parameterListEnd) { AutoAssertReportedException assertException(cx); - BytecodeCompiler compiler(cx, options, srcBuf); - if (!compiler.prepareStandaloneFunctionParse(parameterListEnd)) { + StandaloneFunctionInfo info(cx, options); + + StandaloneFunctionCompiler<char16_t> compiler(srcBuf); + if (!compiler.prepare(info, parameterListEnd)) { return false; } RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - CodeNode* parsedFunction = compiler.parseStandaloneFunction(fun, GeneratorKind::Generator, - FunctionAsyncKind::AsyncFunction, - parameterListEnd, - emptyGlobalScope); - if (!parsedFunction || !compiler.compileStandaloneFunction(parsedFunction, fun)) { + CodeNode* parsedFunction = + compiler.parse(info, fun, emptyGlobalScope, GeneratorKind::Generator, + FunctionAsyncKind::AsyncFunction, parameterListEnd); + if (!parsedFunction || !compiler.compile(fun, info, parsedFunction)) { return false; } assertException.reset(); return true; }
--- a/js/src/frontend/BytecodeCompiler.h +++ b/js/src/frontend/BytecodeCompiler.h @@ -7,16 +7,17 @@ #ifndef frontend_BytecodeCompiler_h #define frontend_BytecodeCompiler_h #include "mozilla/Maybe.h" #include "NamespaceImports.h" #include "js/CompileOptions.h" +#include "js/SourceText.h" #include "vm/Scope.h" #include "vm/TraceLogging.h" class JSLinearString; namespace js { class LazyScript; @@ -24,89 +25,73 @@ class ModuleObject; class ScriptSourceObject; namespace frontend { class ErrorReporter; class FunctionBox; class ParseNode; -JSScript* -CompileGlobalScript(JSContext* cx, ScopeKind scopeKind, - const JS::ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf, - ScriptSourceObject** sourceObjectOut = nullptr); - #if defined(JS_BUILD_BINAST) JSScript* CompileGlobalBinASTScript(JSContext *cx, LifoAlloc& alloc, const JS::ReadOnlyCompileOptions& options, const uint8_t* src, size_t len, ScriptSourceObject** sourceObjectOut = nullptr); MOZ_MUST_USE bool CompileLazyBinASTFunction(JSContext* cx, Handle<LazyScript*> lazy, const uint8_t* buf, size_t length); #endif // JS_BUILD_BINAST -JSScript* -CompileEvalScript(JSContext* cx, HandleObject environment, - HandleScope enclosingScope, - const JS::ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf, - ScriptSourceObject** sourceObjectOut = nullptr); +ModuleObject* +CompileModule(JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf); ModuleObject* CompileModule(JSContext* cx, const JS::ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf); - -ModuleObject* -CompileModule(JSContext* cx, const JS::ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf, + JS::SourceText<char16_t>& srcBuf, ScriptSourceObject** sourceObjectOut); -MOZ_MUST_USE bool -CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length); - // // Compile a single function. The source in srcBuf must match the ECMA-262 // FunctionExpression production. // // If nonzero, parameterListEnd is the offset within srcBuf where the parameter // list is expected to end. During parsing, if we find that it ends anywhere // else, it's a SyntaxError. This is used to implement the Function constructor; // it's how we detect that these weird cases are SyntaxErrors: // // Function("/*", "*/x) {") // Function("x){ if (3", "return x;}") // MOZ_MUST_USE bool CompileStandaloneFunction(JSContext* cx, MutableHandleFunction fun, const JS::ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf, + JS::SourceText<char16_t>& srcBuf, const mozilla::Maybe<uint32_t>& parameterListEnd, HandleScope enclosingScope = nullptr); MOZ_MUST_USE bool CompileStandaloneGenerator(JSContext* cx, MutableHandleFunction fun, const JS::ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf, + JS::SourceText<char16_t>& srcBuf, const mozilla::Maybe<uint32_t>& parameterListEnd); MOZ_MUST_USE bool CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun, const JS::ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf, + JS::SourceText<char16_t>& srcBuf, const mozilla::Maybe<uint32_t>& parameterListEnd); MOZ_MUST_USE bool CompileStandaloneAsyncGenerator(JSContext* cx, MutableHandleFunction fun, const JS::ReadOnlyCompileOptions& options, - JS::SourceBufferHolder& srcBuf, + JS::SourceText<char16_t>& srcBuf, const mozilla::Maybe<uint32_t>& parameterListEnd); ScriptSourceObject* CreateScriptSourceObject(JSContext* cx, const JS::ReadOnlyCompileOptions& options, const mozilla::Maybe<uint32_t>& parameterListEnd = mozilla::Nothing()); /* * True if str consists of an IdentifierStart character, followed by one or
--- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -30,16 +30,17 @@ #include "frontend/DoWhileEmitter.h" #include "frontend/ElemOpEmitter.h" #include "frontend/EmitterScope.h" #include "frontend/ExpressionStatementEmitter.h" #include "frontend/ForInEmitter.h" #include "frontend/ForOfEmitter.h" #include "frontend/ForOfLoopControl.h" #include "frontend/IfEmitter.h" +#include "frontend/ModuleSharedContext.h" #include "frontend/NameOpEmitter.h" #include "frontend/ParseNode.h" #include "frontend/Parser.h" #include "frontend/PropOpEmitter.h" #include "frontend/SwitchEmitter.h" #include "frontend/TDZCheckCache.h" #include "frontend/TryEmitter.h" #include "frontend/WhileEmitter.h"
--- a/js/src/frontend/EitherParser.h +++ b/js/src/frontend/EitherParser.h @@ -11,16 +11,17 @@ #ifndef frontend_EitherParser_h #define frontend_EitherParser_h #include "mozilla/Attributes.h" #include "mozilla/Move.h" #include "mozilla/Tuple.h" #include "mozilla/TypeTraits.h" +#include "mozilla/Utf8.h" #include "mozilla/Variant.h" #include <utility> #include "frontend/BCEParserHandle.h" #include "frontend/Parser.h" #include "frontend/TokenStream.h" @@ -94,21 +95,29 @@ struct ParserNewObjectBox } }; // Generic matchers. struct ParseHandlerMatcher { template<class Parser> - frontend::FullParseHandler& match(Parser *parser) { + frontend::FullParseHandler& match(Parser* parser) { return parser->handler; } }; +struct AnyCharsMatcher +{ + template<class Parser> + frontend::TokenStreamAnyChars& match(Parser* parser) { + return parser->anyChars; + } +}; + struct ParserBaseMatcher { template<class Parser> frontend::ParserBase& match(Parser* parser) { return *static_cast<frontend::ParserBase*>(parser); } }; @@ -122,17 +131,18 @@ struct ErrorReporterMatcher } // namespace detail namespace frontend { class EitherParser : public BCEParserHandle { // Leave this as a variant, to promote good form until 8-bit parser integration. - mozilla::Variant<Parser<FullParseHandler, char16_t>* const> parser; + mozilla::Variant<Parser<FullParseHandler, char16_t>* const, + Parser<FullParseHandler, mozilla::Utf8Unit>* const> parser; using Node = typename FullParseHandler::Node; template<template <class Parser> class GetThis, template <class This> class GetMemberFunction, typename... StoredArgs> using InvokeMemberFunction = detail::InvokeMemberFunction<GetThis, GetMemberFunction, StoredArgs...>; @@ -159,14 +169,21 @@ class EitherParser : public BCEParserHan ObjectBox* newObjectBox(JSObject* obj) final { InvokeMemberFunction<detail::GetParser, detail::ParserNewObjectBox, JSObject*> matcher { obj }; return parser.match(std::move(matcher)); } + const TokenStreamAnyChars& anyChars() const { + return parser.match(detail::AnyCharsMatcher()); + } + + void computeLineAndColumn(uint32_t offset, uint32_t* line, uint32_t* column) const { + return anyChars().lineAndColumnAt(offset, line, column); + } }; } /* namespace frontend */ } /* namespace js */ #endif /* frontend_EitherParser_h */
--- a/js/src/frontend/EmitterScope.cpp +++ b/js/src/frontend/EmitterScope.cpp @@ -2,16 +2,17 @@ * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 "frontend/EmitterScope.h" #include "frontend/BytecodeEmitter.h" +#include "frontend/ModuleSharedContext.h" #include "frontend/TDZCheckCache.h" #include "vm/GlobalObject.h" using namespace js; using namespace js::frontend; using mozilla::DebugOnly;
copy from js/src/frontend/SharedContext.h copy to js/src/frontend/ModuleSharedContext.h --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/ModuleSharedContext.h @@ -1,628 +1,48 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 frontend_SharedContext_h -#define frontend_SharedContext_h - -#include "jspubtd.h" -#include "jstypes.h" +#ifndef frontend_ModuleSharedContext_h +#define frontend_ModuleSharedContext_h -#include "builtin/ModuleObject.h" -#include "ds/InlineTable.h" -#include "frontend/ParseNode.h" -#include "frontend/TokenStream.h" -#include "vm/BytecodeUtil.h" -#include "vm/JSFunction.h" -#include "vm/JSScript.h" +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS + +#include "builtin/ModuleObject.h" // js::Module{Builder,Object} +#include "frontend/SharedContext.h" // js::frontend::SharedContext +#include "js/RootingAPI.h" // JS::Handle, JS::Rooted +#include "vm/Scope.h" // js::{Module,}Scope namespace js { namespace frontend { -class ParseContext; -class ParseNode; - -enum class StatementKind : uint8_t -{ - Label, - Block, - If, - Switch, - With, - Catch, - Try, - Finally, - ForLoopLexicalHead, - ForLoop, - ForInLoop, - ForOfLoop, - DoLoop, - WhileLoop, - Class, - - // Used only by BytecodeEmitter. - Spread -}; - -static inline bool -StatementKindIsLoop(StatementKind kind) -{ - return kind == StatementKind::ForLoop || - kind == StatementKind::ForInLoop || - kind == StatementKind::ForOfLoop || - kind == StatementKind::DoLoop || - kind == StatementKind::WhileLoop || - kind == StatementKind::Spread; -} - -static inline bool -StatementKindIsUnlabeledBreakTarget(StatementKind kind) -{ - return StatementKindIsLoop(kind) || kind == StatementKind::Switch; -} - -// List of directives that may be encountered in a Directive Prologue (ES5 15.1). -class Directives -{ - bool strict_; - bool asmJS_; - - public: - explicit Directives(bool strict) : strict_(strict), asmJS_(false) {} - explicit Directives(ParseContext* parent); - - void setStrict() { strict_ = true; } - bool strict() const { return strict_; } - - void setAsmJS() { asmJS_ = true; } - bool asmJS() const { return asmJS_; } - - Directives& operator=(Directives rhs) { - strict_ = rhs.strict_; - asmJS_ = rhs.asmJS_; - return *this; - } - bool operator==(const Directives& rhs) const { - return strict_ == rhs.strict_ && asmJS_ == rhs.asmJS_; - } - bool operator!=(const Directives& rhs) const { - return !(*this == rhs); - } -}; - -// The kind of this-binding for the current scope. Note that arrow functions -// have a lexical this-binding so their ThisBinding is the same as the -// ThisBinding of their enclosing scope and can be any value. -enum class ThisBinding : uint8_t { Global, Function, Module }; - -class GlobalSharedContext; -class EvalSharedContext; -class ModuleSharedContext; - -/* - * The struct SharedContext is part of the current parser context (see - * ParseContext). It stores information that is reused between the parser and - * the bytecode emitter. - */ -class SharedContext +class MOZ_STACK_CLASS ModuleSharedContext + : public SharedContext { - public: - JSContext* const context; - - protected: - enum class Kind : uint8_t { - FunctionBox, - Global, - Eval, - Module - }; - - Kind kind_; - - ThisBinding thisBinding_; - - public: - bool strictScript:1; - bool localStrict:1; - bool extraWarnings:1; - - protected: - bool allowNewTarget_:1; - bool allowSuperProperty_:1; - bool allowSuperCall_:1; - bool inWith_:1; - bool needsThisTDZChecks_:1; - - // True if "use strict"; appears in the body instead of being inherited. - bool hasExplicitUseStrict_:1; - - // The (static) bindings of this script need to support dynamic name - // read/write access. Here, 'dynamic' means dynamic dictionary lookup on - // the scope chain for a dynamic set of keys. The primary examples are: - // - direct eval - // - function:: - // - with - // since both effectively allow any name to be accessed. Non-examples are: - // - upvars of nested functions - // - function statement - // since the set of assigned name is known dynamically. - // - // Note: access through the arguments object is not considered dynamic - // binding access since it does not go through the normal name lookup - // mechanism. This is debatable and could be changed (although care must be - // taken not to turn off the whole 'arguments' optimization). To answer the - // more general "is this argument aliased" question, script->needsArgsObj - // should be tested (see JSScript::argIsAliased). - bool bindingsAccessedDynamically_:1; - - // Whether this script, or any of its inner scripts contains a debugger - // statement which could potentially read or write anywhere along the - // scope chain. - bool hasDebuggerStatement_:1; - - // A direct eval occurs in the body of the script. - bool hasDirectEval_:1; - - void computeAllowSyntax(Scope* scope); - void computeInWith(Scope* scope); - void computeThisBinding(Scope* scope); - - public: - SharedContext(JSContext* cx, Kind kind, Directives directives, bool extraWarnings) - : context(cx), - kind_(kind), - thisBinding_(ThisBinding::Global), - strictScript(directives.strict()), - localStrict(false), - extraWarnings(extraWarnings), - allowNewTarget_(false), - allowSuperProperty_(false), - allowSuperCall_(false), - inWith_(false), - needsThisTDZChecks_(false), - hasExplicitUseStrict_(false), - bindingsAccessedDynamically_(false), - hasDebuggerStatement_(false), - hasDirectEval_(false) - { } - - // If this is the outermost SharedContext, the Scope that encloses - // it. Otherwise nullptr. - virtual Scope* compilationEnclosingScope() const = 0; - - bool isFunctionBox() const { return kind_ == Kind::FunctionBox; } - inline FunctionBox* asFunctionBox(); - bool isModuleContext() const { return kind_ == Kind::Module; } - inline ModuleSharedContext* asModuleContext(); - bool isGlobalContext() const { return kind_ == Kind::Global; } - inline GlobalSharedContext* asGlobalContext(); - bool isEvalContext() const { return kind_ == Kind::Eval; } - inline EvalSharedContext* asEvalContext(); - - ThisBinding thisBinding() const { return thisBinding_; } - - bool allowNewTarget() const { return allowNewTarget_; } - bool allowSuperProperty() const { return allowSuperProperty_; } - bool allowSuperCall() const { return allowSuperCall_; } - bool inWith() const { return inWith_; } - bool needsThisTDZChecks() const { return needsThisTDZChecks_; } - - bool hasExplicitUseStrict() const { return hasExplicitUseStrict_; } - bool bindingsAccessedDynamically() const { return bindingsAccessedDynamically_; } - bool hasDebuggerStatement() const { return hasDebuggerStatement_; } - bool hasDirectEval() const { return hasDirectEval_; } - - void setExplicitUseStrict() { hasExplicitUseStrict_ = true; } - void setBindingsAccessedDynamically() { bindingsAccessedDynamically_ = true; } - void setHasDebuggerStatement() { hasDebuggerStatement_ = true; } - void setHasDirectEval() { hasDirectEval_ = true; } - - inline bool allBindingsClosedOver(); - - bool strict() const { - return strictScript || localStrict; - } - bool setLocalStrictMode(bool strict) { - bool retVal = localStrict; - localStrict = strict; - return retVal; - } - - // JSOPTION_EXTRA_WARNINGS warnings or strict mode errors. - bool needStrictChecks() const { - return strict() || extraWarnings; - } -}; - -class MOZ_STACK_CLASS GlobalSharedContext : public SharedContext -{ - ScopeKind scopeKind_; - - public: - Rooted<GlobalScope::Data*> bindings; - - GlobalSharedContext(JSContext* cx, ScopeKind scopeKind, Directives directives, - bool extraWarnings) - : SharedContext(cx, Kind::Global, directives, extraWarnings), - scopeKind_(scopeKind), - bindings(cx) - { - MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic); - thisBinding_ = ThisBinding::Global; - } - - Scope* compilationEnclosingScope() const override { - return nullptr; - } - - ScopeKind scopeKind() const { - return scopeKind_; - } -}; - -inline GlobalSharedContext* -SharedContext::asGlobalContext() -{ - MOZ_ASSERT(isGlobalContext()); - return static_cast<GlobalSharedContext*>(this); -} - -class MOZ_STACK_CLASS EvalSharedContext : public SharedContext -{ - RootedScope enclosingScope_; + JS::Rooted<ModuleObject*> module_; + JS::Rooted<Scope*> enclosingScope_; public: - Rooted<EvalScope::Data*> bindings; - - EvalSharedContext(JSContext* cx, JSObject* enclosingEnv, Scope* enclosingScope, - Directives directives, bool extraWarnings); - - Scope* compilationEnclosingScope() const override { - return enclosingScope_; - } -}; - -inline EvalSharedContext* -SharedContext::asEvalContext() -{ - MOZ_ASSERT(isEvalContext()); - return static_cast<EvalSharedContext*>(this); -} - -class FunctionBox : public ObjectBox, public SharedContext -{ - // The parser handles tracing the fields below via the ObjectBox linked - // list. - - // This field is used for two purposes: - // * If this FunctionBox refers to the function being compiled, this field - // holds its enclosing scope, used for compilation. - // * If this FunctionBox refers to a lazy child of the function being - // compiled, this field holds the child's immediately enclosing scope. - // Once compilation succeeds, we will store it in the child's - // LazyScript. (Debugger may become confused if LazyScripts refer to - // partially initialized enclosing scopes, so we must avoid storing the - // scope in the LazyScript until compilation has completed - // successfully.) - Scope* enclosingScope_; - - // Names from the named lambda scope, if a named lambda. - LexicalScope::Data* namedLambdaBindings_; - - // Names from the function scope. - FunctionScope::Data* functionScopeBindings_; - - // Names from the extra 'var' scope of the function, if the parameter list - // has expressions. - VarScope::Data* extraVarScopeBindings_; - - void initWithEnclosingScope(Scope* enclosingScope); - - public: - CodeNode* functionNode; /* back pointer used by asm.js for error messages */ - uint32_t bufStart; - uint32_t bufEnd; - uint32_t startLine; - uint32_t startColumn; - uint32_t toStringStart; - uint32_t toStringEnd; - uint16_t length; - - bool isGenerator_:1; /* generator function or async generator */ - bool isAsync_:1; /* async function or async generator */ - bool hasDestructuringArgs:1; /* parameter list contains destructuring expression */ - bool hasParameterExprs:1; /* parameter list contains expressions */ - bool hasDirectEvalInParameterExpr:1; /* parameter list contains direct eval */ - bool hasDuplicateParameters:1; /* parameter list contains duplicate names */ - bool useAsm:1; /* see useAsmOrInsideUseAsm */ - bool isAnnexB:1; /* need to emit a synthesized Annex B assignment */ - bool wasEmitted:1; /* Bytecode has been emitted for this function. */ - - // Fields for use in heuristics. - bool declaredArguments:1; /* the Parser declared 'arguments' */ - bool usesArguments:1; /* contains a free use of 'arguments' */ - bool usesApply:1; /* contains an f.apply() call */ - bool usesThis:1; /* contains 'this' */ - bool usesReturn:1; /* contains a 'return' statement */ - bool hasRest_:1; /* has rest parameter */ - bool hasExprBody_:1; /* arrow function with expression - * body like: () => 1 - * Only used by Reflect.parse */ - - // This function does something that can extend the set of bindings in its - // call objects --- it does a direct eval in non-strict code, or includes a - // function statement (as opposed to a function definition). - // - // This flag is *not* inherited by enclosed or enclosing functions; it - // applies only to the function in whose flags it appears. - // - bool hasExtensibleScope_:1; - - // Technically, every function has a binding named 'arguments'. Internally, - // this binding is only added when 'arguments' is mentioned by the function - // body. This flag indicates whether 'arguments' has been bound either - // through implicit use: - // function f() { return arguments } - // or explicit redeclaration: - // function f() { var arguments; return arguments } - // - // Note 1: overwritten arguments (function() { arguments = 3 }) will cause - // this flag to be set but otherwise require no special handling: - // 'arguments' is just a local variable and uses of 'arguments' will just - // read the local's current slot which may have been assigned. The only - // special semantics is that the initial value of 'arguments' is the - // arguments object (not undefined, like normal locals). - // - // Note 2: if 'arguments' is bound as a formal parameter, there will be an - // 'arguments' in Bindings, but, as the "LOCAL" in the name indicates, this - // flag will not be set. This is because, as a formal, 'arguments' will - // have no special semantics: the initial value is unconditionally the - // actual argument (or undefined if nactual < nformal). - // - bool argumentsHasLocalBinding_:1; - - // In many cases where 'arguments' has a local binding (as described above) - // we do not need to actually create an arguments object in the function - // prologue: instead we can analyze how 'arguments' is used (using the - // simple dataflow analysis in analyzeSSA) to determine that uses of - // 'arguments' can just read from the stack frame directly. However, the - // dataflow analysis only looks at how JSOP_ARGUMENTS is used, so it will - // be unsound in several cases. The frontend filters out such cases by - // setting this flag which eagerly sets script->needsArgsObj to true. - // - bool definitelyNeedsArgsObj_:1; - - bool needsHomeObject_:1; - bool isDerivedClassConstructor_:1; - - // Whether this function has a .this binding. If true, we need to emit - // JSOP_FUNCTIONTHIS in the prologue to initialize it. - bool hasThisBinding_:1; - - // Whether this function has nested functions. - bool hasInnerFunctions_:1; - - FunctionBox(JSContext* cx, ObjectBox* traceListHead, JSFunction* fun, - uint32_t toStringStart, Directives directives, bool extraWarnings, - GeneratorKind generatorKind, FunctionAsyncKind asyncKind); - -#ifdef DEBUG - bool atomsAreKept(); -#endif - - MutableHandle<LexicalScope::Data*> namedLambdaBindings() { - MOZ_ASSERT(atomsAreKept()); - return MutableHandle<LexicalScope::Data*>::fromMarkedLocation(&namedLambdaBindings_); - } - - MutableHandle<FunctionScope::Data*> functionScopeBindings() { - MOZ_ASSERT(atomsAreKept()); - return MutableHandle<FunctionScope::Data*>::fromMarkedLocation(&functionScopeBindings_); - } - - MutableHandle<VarScope::Data*> extraVarScopeBindings() { - MOZ_ASSERT(atomsAreKept()); - return MutableHandle<VarScope::Data*>::fromMarkedLocation(&extraVarScopeBindings_); - } - - void initFromLazyFunction(); - void initStandaloneFunction(Scope* enclosingScope); - void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind); - - inline bool isLazyFunctionWithoutEnclosingScope() const { - return function()->isInterpretedLazy() && - !function()->lazyScript()->hasEnclosingScope(); - } - void setEnclosingScopeForInnerLazyFunction(Scope* enclosingScope); - void finish(); - - JSFunction* function() const { return &object->as<JSFunction>(); } - - Scope* compilationEnclosingScope() const override { - // This method is used to distinguish the outermost SharedContext. If - // a FunctionBox is the outermost SharedContext, it must be a lazy - // function. - - // If the function is lazy and it has enclosing scope, the function is - // being delazified. In that case the enclosingScope_ field is copied - // from the lazy function at the beginning of delazification and should - // keep pointing the same scope. - MOZ_ASSERT_IF(function()->isInterpretedLazy() && - function()->lazyScript()->hasEnclosingScope(), - enclosingScope_ == function()->lazyScript()->enclosingScope()); - - // If this FunctionBox is a lazy child of the function we're actually - // compiling, then it is not the outermost SharedContext, so this - // method should return nullptr." - if (isLazyFunctionWithoutEnclosingScope()) { - return nullptr; - } - - return enclosingScope_; - } - - bool needsCallObjectRegardlessOfBindings() const { - return hasExtensibleScope() || - needsHomeObject() || - isDerivedClassConstructor() || - isGenerator() || - isAsync(); - } - - bool hasExtraBodyVarScope() const { - return hasParameterExprs && - (extraVarScopeBindings_ || - needsExtraBodyVarEnvironmentRegardlessOfBindings()); - } - - bool needsExtraBodyVarEnvironmentRegardlessOfBindings() const { - MOZ_ASSERT(hasParameterExprs); - return hasExtensibleScope() || needsDotGeneratorName(); - } - - bool isLikelyConstructorWrapper() const { - return usesArguments && usesApply && usesThis && !usesReturn; - } - - bool isGenerator() const { return isGenerator_; } - GeneratorKind generatorKind() const { - return isGenerator() ? GeneratorKind::Generator : GeneratorKind::NotGenerator; - } - - bool isAsync() const { return isAsync_; } - FunctionAsyncKind asyncKind() const { - return isAsync() ? FunctionAsyncKind::AsyncFunction : FunctionAsyncKind::SyncFunction; - } - - bool needsFinalYield() const { - return isGenerator() || isAsync(); - } - bool needsDotGeneratorName() const { - return isGenerator() || isAsync(); - } - bool needsIteratorResult() const { - return isGenerator(); - } - - bool isArrow() const { return function()->isArrow(); } - - bool hasRest() const { return hasRest_; } - void setHasRest() { - hasRest_ = true; - } - - bool hasExprBody() const { return hasExprBody_; } - void setHasExprBody() { - MOZ_ASSERT(isArrow()); - hasExprBody_ = true; - } - - bool hasExtensibleScope() const { return hasExtensibleScope_; } - bool hasThisBinding() const { return hasThisBinding_; } - bool argumentsHasLocalBinding() const { return argumentsHasLocalBinding_; } - bool definitelyNeedsArgsObj() const { return definitelyNeedsArgsObj_; } - bool needsHomeObject() const { return needsHomeObject_; } - bool isDerivedClassConstructor() const { return isDerivedClassConstructor_; } - bool hasInnerFunctions() const { return hasInnerFunctions_; } - - void setHasExtensibleScope() { hasExtensibleScope_ = true; } - void setHasThisBinding() { hasThisBinding_ = true; } - void setArgumentsHasLocalBinding() { argumentsHasLocalBinding_ = true; } - void setDefinitelyNeedsArgsObj() { MOZ_ASSERT(argumentsHasLocalBinding_); - definitelyNeedsArgsObj_ = true; } - void setNeedsHomeObject() { MOZ_ASSERT(function()->allowSuperProperty()); - needsHomeObject_ = true; } - void setDerivedClassConstructor() { MOZ_ASSERT(function()->isClassConstructor()); - isDerivedClassConstructor_ = true; } - void setHasInnerFunctions() { hasInnerFunctions_ = true; } - - bool hasSimpleParameterList() const { - return !hasRest() && !hasParameterExprs && !hasDestructuringArgs; - } - - bool hasMappedArgsObj() const { - return !strict() && hasSimpleParameterList(); - } - - // Return whether this or an enclosing function is being parsed and - // validated as asm.js. Note: if asm.js validation fails, this will be false - // while the function is being reparsed. This flag can be used to disable - // certain parsing features that are necessary in general, but unnecessary - // for validated asm.js. - bool useAsmOrInsideUseAsm() const { - return useAsm; - } - - void setStart(const TokenStreamAnyChars& anyChars) { - uint32_t offset = anyChars.currentToken().pos.begin; - setStart(anyChars, offset); - } - - void setStart(const TokenStreamAnyChars& anyChars, uint32_t offset) { - bufStart = offset; - anyChars.srcCoords.lineNumAndColumnIndex(offset, &startLine, &startColumn); - } - - void setEnd(const TokenStreamAnyChars& anyChars) { - // For all functions except class constructors, the buffer and - // toString ending positions are the same. Class constructors override - // the toString ending position with the end of the class definition. - uint32_t offset = anyChars.currentToken().pos.end; - bufEnd = offset; - toStringEnd = offset; - } - - void trace(JSTracer* trc) override; -}; - -inline FunctionBox* -SharedContext::asFunctionBox() -{ - MOZ_ASSERT(isFunctionBox()); - return static_cast<FunctionBox*>(this); -} - -class MOZ_STACK_CLASS ModuleSharedContext : public SharedContext -{ - RootedModuleObject module_; - RootedScope enclosingScope_; - - public: - Rooted<ModuleScope::Data*> bindings; + JS::Rooted<ModuleScope::Data*> bindings; ModuleBuilder& builder; ModuleSharedContext(JSContext* cx, ModuleObject* module, Scope* enclosingScope, ModuleBuilder& builder); - HandleModuleObject module() const { return module_; } + JS::Handle<ModuleObject*> module() const { return module_; } Scope* compilationEnclosingScope() const override { return enclosingScope_; } }; inline ModuleSharedContext* SharedContext::asModuleContext() { MOZ_ASSERT(isModuleContext()); return static_cast<ModuleSharedContext*>(this); } -// In generators, we treat all bindings as closed so that they get stored on -// the heap. This way there is less information to copy off the stack when -// suspending, and back on when resuming. It also avoids the need to create -// and invalidate DebugScope proxies for unaliased locals in a generator -// frame, as the generator frame will be copied out to the heap and released -// only by GC. -inline bool -SharedContext::allBindingsClosedOver() -{ - return bindingsAccessedDynamically() || - (isFunctionBox() && - (asFunctionBox()->isGenerator() || - asFunctionBox()->isAsync())); -} - } // namespace frontend } // namespace js -#endif /* frontend_SharedContext_h */ +#endif /* frontend_ModuleSharedContext_h */
--- a/js/src/frontend/ParseContext.h +++ b/js/src/frontend/ParseContext.h @@ -6,16 +6,17 @@ #ifndef frontend_ParseContext_h #define frontend_ParseContext_h #include "ds/Nestable.h" #include "frontend/BytecodeCompiler.h" #include "frontend/ErrorReporter.h" +#include "frontend/NameCollections.h" #include "frontend/SharedContext.h" namespace js { namespace frontend { class ParserBase;
--- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -32,16 +32,17 @@ #include "jsnum.h" #include "jstypes.h" #include "builtin/ModuleObject.h" #include "builtin/SelfHostingDefines.h" #include "frontend/BytecodeCompiler.h" #include "frontend/FoldConstants.h" +#include "frontend/ModuleSharedContext.h" #include "frontend/ParseNode.h" #include "frontend/TokenStream.h" #include "irregexp/RegExpParser.h" #include "vm/BytecodeUtil.h" #include "vm/JSAtom.h" #include "vm/JSContext.h" #include "vm/JSFunction.h" #include "vm/JSScript.h"
--- a/js/src/frontend/SharedContext.cpp +++ b/js/src/frontend/SharedContext.cpp @@ -1,15 +1,18 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 "frontend/SharedContext.h" + +#include "frontend/ModuleSharedContext.h" + #include "frontend/ParseContext-inl.h" #include "vm/EnvironmentObject-inl.h" namespace js { namespace frontend { void SharedContext::computeAllowSyntax(Scope* scope)
--- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -5,17 +5,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef frontend_SharedContext_h #define frontend_SharedContext_h #include "jspubtd.h" #include "jstypes.h" -#include "builtin/ModuleObject.h" #include "ds/InlineTable.h" #include "frontend/ParseNode.h" #include "frontend/TokenStream.h" #include "vm/BytecodeUtil.h" #include "vm/JSFunction.h" #include "vm/JSScript.h" namespace js { @@ -579,39 +578,16 @@ class FunctionBox : public ObjectBox, pu inline FunctionBox* SharedContext::asFunctionBox() { MOZ_ASSERT(isFunctionBox()); return static_cast<FunctionBox*>(this); } -class MOZ_STACK_CLASS ModuleSharedContext : public SharedContext -{ - RootedModuleObject module_; - RootedScope enclosingScope_; - - public: - Rooted<ModuleScope::Data*> bindings; - ModuleBuilder& builder; - - ModuleSharedContext(JSContext* cx, ModuleObject* module, Scope* enclosingScope, - ModuleBuilder& builder); - - HandleModuleObject module() const { return module_; } - Scope* compilationEnclosingScope() const override { return enclosingScope_; } -}; - -inline ModuleSharedContext* -SharedContext::asModuleContext() -{ - MOZ_ASSERT(isModuleContext()); - return static_cast<ModuleSharedContext*>(this); -} - // In generators, we treat all bindings as closed so that they get stored on // the heap. This way there is less information to copy off the stack when // suspending, and back on when resuming. It also avoids the need to create // and invalidate DebugScope proxies for unaliased locals in a generator // frame, as the generator frame will be copied out to the heap and released // only by GC. inline bool SharedContext::allBindingsClosedOver()
--- a/js/src/js-config.mozbuild +++ b/js/src/js-config.mozbuild @@ -3,33 +3,33 @@ # 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/. # Nightly-only features if CONFIG['NIGHTLY_BUILD']: DEFINES['ENABLE_BINARYDATA'] = True DEFINES['ENABLE_WASM_BULKMEM_OPS'] = True - DEFINES['ENABLE_WASM_THREAD_OPS'] = True DEFINES['ENABLE_WASM_GC'] = True DEFINES['ENABLE_WASM_GENERALIZED_TABLES'] = True DEFINES['WASM_PRIVATE_REFTYPES'] = True # Some huge-mapping optimization instead of bounds checks on supported # platforms. if CONFIG['JS_CODEGEN_X64'] or CONFIG['JS_CODEGEN_ARM64']: DEFINES['WASM_HUGE_MEMORY'] = True # Enables CACHEIR_LOGS to diagnose IC coverage. if CONFIG['MOZ_DEBUG'] or CONFIG['NIGHTLY_BUILD']: DEFINES['JS_CACHEIR_SPEW'] = True -# Build with SharedArrayBuffer code. +# Build with SharedArrayBuffer/wasm-thread-ops code. # NOTE: This Realm creation options decide if this is exposed to script. DEFINES['ENABLE_SHARED_ARRAY_BUFFER'] = True +DEFINES['ENABLE_WASM_THREAD_OPS'] = True # CTypes if CONFIG['JS_HAS_CTYPES']: DEFINES['JS_HAS_CTYPES'] = True if not CONFIG['MOZ_SYSTEM_FFI']: DEFINES['FFI_BUILDING'] = True # Forward MOZ_LINKER config
--- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -17,16 +17,17 @@ UNIFIED_SOURCES += [ 'testAtomicOperations.cpp', 'testBoundFunction.cpp', 'testBug604087.cpp', 'testCallArgs.cpp', 'testCallNonGenericMethodOnProxy.cpp', 'testChromeBuffer.cpp', 'testCloneScript.cpp', 'testCompileNonSyntactic.cpp', + 'testCompileUtf8.cpp', 'testDateToLocaleString.cpp', 'testDebugger.cpp', 'testDeepFreeze.cpp', 'testDefineGetterSetterNonEnumerable.cpp', 'testDefineProperty.cpp', 'testDefinePropertyIgnoredAttributes.cpp', 'testDeflateStringToUTF8Buffer.cpp', 'testDifferentNewTargetInvokeConstructor.cpp',
--- a/js/src/jsapi-tests/testCompileNonSyntactic.cpp +++ b/js/src/jsapi-tests/testCompileNonSyntactic.cpp @@ -1,15 +1,15 @@ /* 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 "gc/GCInternals.h" #include "js/CompilationAndEvaluation.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "jsapi-tests/tests.h" #include "vm/Monitor.h" #include "vm/MutexIDs.h" using namespace JS; using js::AutoLockMonitor; struct OffThreadTask @@ -69,53 +69,60 @@ testCompile(bool nonSyntactic) constexpr size_t length = sizeof(src) - 1; static_assert(sizeof(src_16) / sizeof(*src_16) - 1 == length, "Source buffers must be same length"); JS::CompileOptions options(cx); options.setNonSyntacticScope(nonSyntactic); - JS::RootedScript script(cx); + JS::SourceText<char16_t> buf; + CHECK(buf.init(cx, src_16, length, JS::SourceOwnership::Borrowed)); - JS::SourceBufferHolder buf(src_16, length, JS::SourceBufferHolder::NoOwnership); + JS::RootedScript script(cx); // Check explicit non-syntactic compilation first to make sure it doesn't // modify our options object. CHECK(CompileForNonSyntacticScope(cx, options, buf, &script)); CHECK_EQUAL(script->hasNonSyntacticScope(), true); CHECK(CompileLatin1ForNonSyntacticScope(cx, options, src, length, &script)); CHECK_EQUAL(script->hasNonSyntacticScope(), true); { - JS::SourceBufferHolder srcBuf(src_16, length, JS::SourceBufferHolder::NoOwnership); + JS::SourceText<char16_t> srcBuf; + CHECK(srcBuf.init(cx, src_16, length, JS::SourceOwnership::Borrowed)); + CHECK(CompileForNonSyntacticScope(cx, options, srcBuf, &script)); CHECK_EQUAL(script->hasNonSyntacticScope(), true); } CHECK(Compile(cx, options, buf, &script)); CHECK_EQUAL(script->hasNonSyntacticScope(), nonSyntactic); CHECK(CompileLatin1(cx, options, src, length, &script)); CHECK_EQUAL(script->hasNonSyntacticScope(), nonSyntactic); { - JS::SourceBufferHolder srcBuf(src_16, length, JS::SourceBufferHolder::NoOwnership); + JS::SourceText<char16_t> srcBuf; + CHECK(srcBuf.init(cx, src_16, length, JS::SourceOwnership::Borrowed)); + CHECK(Compile(cx, options, srcBuf, &script)); CHECK_EQUAL(script->hasNonSyntacticScope(), nonSyntactic); } options.forceAsync = true; OffThreadTask task; OffThreadToken* token; - JS::SourceBufferHolder srcBuf(src_16, length, JS::SourceBufferHolder::NoOwnership); + JS::SourceText<char16_t> srcBuf; + CHECK(srcBuf.init(cx, src_16, length, JS::SourceOwnership::Borrowed)); + CHECK(CompileOffThread(cx, options, srcBuf, task.OffThreadCallback, &task)); CHECK(token = task.waitUntilDone(cx)); CHECK(script = FinishOffThreadScript(cx, token)); CHECK_EQUAL(script->hasNonSyntacticScope(), nonSyntactic); return true; } END_TEST(testCompileScript);
new file mode 100644 --- /dev/null +++ b/js/src/jsapi-tests/testCompileUtf8.cpp @@ -0,0 +1,279 @@ +/* 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 "mozilla/ArrayUtils.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Utf8.h" + +#include <cstring> + +#include "jsfriendapi.h" + +#include "js/CharacterEncoding.h" +#include "js/CompilationAndEvaluation.h" +#include "js/SourceText.h" +#include "jsapi-tests/tests.h" +#include "vm/ErrorReporting.h" + +using mozilla::ArrayLength; +using mozilla::IsAsciiHexDigit; +using mozilla::Utf8Unit; + +BEGIN_TEST(testUtf8BadBytes) +{ + static const char badLeadingUnit[] = "var x = \x80"; + CHECK(testBadUtf8(badLeadingUnit, + JSMSG_BAD_LEADING_UTF8_UNIT, + [this](JS::ConstUTF8CharsZ message) { + const char* chars = message.c_str(); + CHECK(startsWith(chars, "0x80")); + CHECK(isBadLeadUnitMessage(chars)); + return true; + }, + "0x80")); + + static const char badSecondInTwoByte[] = "var x = \xDF\x20"; + CHECK(testBadUtf8(badSecondInTwoByte, + JSMSG_BAD_TRAILING_UTF8_UNIT, + [this](JS::ConstUTF8CharsZ message) { + const char* chars = message.c_str(); + CHECK(isBadTrailingBytesMessage(chars)); + CHECK(contains(chars, "0x20")); + return true; + }, + "0xDF 0x20")); + + static const char badSecondInThreeByte[] = "var x = \xEF\x17\xA7"; + CHECK(testBadUtf8(badSecondInThreeByte, + JSMSG_BAD_TRAILING_UTF8_UNIT, + [this](JS::ConstUTF8CharsZ message) { + const char* chars = message.c_str(); + CHECK(isBadTrailingBytesMessage(chars)); + CHECK(contains(chars, "0x17")); + return true; + }, + // Validating stops with the first invalid code unit and + // shouldn't go beyond that. + "0xEF 0x17")); + + static const char lengthTwoTooShort[] = "var x = \xDF"; + CHECK(testBadUtf8(lengthTwoTooShort, + JSMSG_NOT_ENOUGH_CODE_UNITS, + [this](JS::ConstUTF8CharsZ message) { + const char* chars = message.c_str(); + CHECK(isNotEnoughUnitsMessage(chars)); + CHECK(contains(chars, "0xDF")); + CHECK(contains(chars, " 1 byte, but 0 bytes were present")); + return true; + }, + "0xDF")); + + static const char forbiddenHighSurrogate[] = "var x = \xED\xA2\x87"; + CHECK(testBadUtf8(forbiddenHighSurrogate, + JSMSG_FORBIDDEN_UTF8_CODE_POINT, + [this](JS::ConstUTF8CharsZ message) { + const char* chars = message.c_str(); + CHECK(isSurrogateMessage(chars)); + CHECK(contains(chars, "0xD887")); + return true; + }, + "0xED 0xA2 0x87")); + + static const char forbiddenLowSurrogate[] = "var x = \xED\xB7\xAF"; + CHECK(testBadUtf8(forbiddenLowSurrogate, + JSMSG_FORBIDDEN_UTF8_CODE_POINT, + [this](JS::ConstUTF8CharsZ message) { + const char* chars = message.c_str(); + CHECK(isSurrogateMessage(chars)); + CHECK(contains(chars, "0xDDEF")); + return true; + }, + "0xED 0xB7 0xAF")); + + static const char oneTooBig[] = "var x = \xF4\x90\x80\x80"; + CHECK(testBadUtf8(oneTooBig, + JSMSG_FORBIDDEN_UTF8_CODE_POINT, + [this](JS::ConstUTF8CharsZ message) { + const char* chars = message.c_str(); + CHECK(isTooBigMessage(chars)); + CHECK(contains(chars, "0x110000")); + return true; + }, + "0xF4 0x90 0x80 0x80")); + + static const char notShortestFormZero[] = "var x = \xC0\x80"; + CHECK(testBadUtf8(notShortestFormZero, + JSMSG_FORBIDDEN_UTF8_CODE_POINT, + [this](JS::ConstUTF8CharsZ message) { + const char* chars = message.c_str(); + CHECK(isNotShortestFormMessage(chars)); + CHECK(startsWith(chars, "0x0 isn't ")); + return true; + }, + "0xC0 0x80")); + + static const char notShortestFormNonzero[] = "var x = \xE0\x87\x80"; + CHECK(testBadUtf8(notShortestFormNonzero, + JSMSG_FORBIDDEN_UTF8_CODE_POINT, + [this](JS::ConstUTF8CharsZ message) { + const char* chars = message.c_str(); + CHECK(isNotShortestFormMessage(chars)); + CHECK(startsWith(chars, "0x1C0 isn't ")); + return true; + }, + "0xE0 0x87 0x80")); + + return true; +} + +static constexpr size_t LengthOfByte = ArrayLength("0xFF") - 1; + +static bool +startsWithByte(const char* str) +{ + return str[0] == '0' && + str[1] == 'x' && + IsAsciiHexDigit(str[2]) && + IsAsciiHexDigit(str[3]); +} + +static bool +startsWith(const char* str, const char* prefix) +{ + return std::strncmp(prefix, str, strlen(prefix)) == 0; +} + +static bool +contains(const char* str, const char* substr) +{ + return std::strstr(str, substr) != nullptr; +} + +static bool +equals(const char* str, const char* expected) +{ + return std::strcmp(str, expected) == 0; +} + +static bool +isBadLeadUnitMessage(const char* str) +{ + return startsWithByte(str) && + equals(str + LengthOfByte, + " byte doesn't begin a valid UTF-8 code point"); +} + +static bool +isBadTrailingBytesMessage(const char* str) +{ + return startsWith(str, "bad trailing UTF-8 byte "); +} + +static bool +isNotEnoughUnitsMessage(const char* str) +{ + return startsWithByte(str) && + startsWith(str + LengthOfByte, + " byte in UTF-8 must be followed by "); +} + +static bool +isForbiddenCodePointMessage(const char* str) +{ + return contains(str, "isn't a valid code point because"); +} + +static bool +isSurrogateMessage(const char* str) +{ + return isForbiddenCodePointMessage(str) && + contains(str, " it's a UTF-16 surrogate"); +} + +static bool +isTooBigMessage(const char* str) +{ + return isForbiddenCodePointMessage(str) && + contains(str, "the maximum code point is U+10FFFF"); +} + +static bool +isNotShortestFormMessage(const char* str) +{ + return isForbiddenCodePointMessage(str) && + contains(str, "it wasn't encoded in shortest possible form"); +} + +bool +compileUtf8(const char* chars, size_t len, JS::MutableHandleScript script) +{ + JS::RealmOptions globalOptions; + JS::RootedObject global(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, + JS::FireOnNewGlobalHook, globalOptions)); + CHECK(global); + + JSAutoRealm ar(cx, global); + + JS::CompileOptions options(cx); + return JS::CompileUtf8DontInflate(cx, options, chars, len, script); +} + +template<size_t N, typename TestMessage> +bool +testBadUtf8(const char (&chars)[N], unsigned errorNumber, + TestMessage testMessage, const char* badBytes) +{ + JS::Rooted<JSScript*> script(cx); + CHECK(!compileUtf8(chars, N - 1, &script)); + + JS::RootedValue exn(cx); + CHECK(JS_GetPendingException(cx, &exn)); + JS_ClearPendingException(cx); + + js::ErrorReport report(cx); + CHECK(report.init(cx, exn, js::ErrorReport::WithSideEffects)); + + const auto* errorReport = report.report(); + + CHECK(errorReport->errorNumber == errorNumber); + + CHECK(testMessage(errorReport->message())); + + { + const auto& notes = errorReport->notes; + CHECK(notes != nullptr); + + auto iter = notes->begin(); + CHECK(iter != notes->end()); + + const char* noteMessage = (*iter)->message().c_str(); + + // The prefix ought always be the same. + static const char expectedPrefix[] = + "the code units comprising this invalid code point were: "; + constexpr size_t expectedPrefixLen = ArrayLength(expectedPrefix) - 1; + + CHECK(startsWith(noteMessage, expectedPrefix)); + + // The end of the prefix is the bad bytes. + CHECK(equals(noteMessage + expectedPrefixLen, badBytes)); + + ++iter; + CHECK(iter == notes->end()); + } + + static const char16_t expectedContext[] = u"var x = "; + constexpr size_t expectedContextLen = ArrayLength(expectedContext) - 1; + + const char16_t* lineOfContext = errorReport->linebuf(); + size_t lineOfContextLength = errorReport->linebufLength(); + + CHECK(lineOfContext[lineOfContextLength] == '\0'); + CHECK(lineOfContextLength == expectedContextLen); + + CHECK(std::memcmp(lineOfContext, expectedContext, expectedContextLen * sizeof(char16_t)) == 0); + + return true; +} +END_TEST(testUtf8BadBytes)
--- a/js/src/jsapi-tests/testErrorLineOfContext.cpp +++ b/js/src/jsapi-tests/testErrorLineOfContext.cpp @@ -1,16 +1,16 @@ /* 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 "jsfriendapi.h" #include "js/CompilationAndEvaluation.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "jsapi-tests/tests.h" #include "vm/ErrorReporting.h" BEGIN_TEST(testErrorLineOfContext) { static const char16_t fullLineR[] = u"\n var x = @; \r "; CHECK(testLineOfContextHasNoLineTerminator(fullLineR, ' ')); @@ -37,18 +37,21 @@ bool eval(const char16_t* chars, size_t len, JS::MutableHandleValue rval) { JS::RealmOptions globalOptions; JS::RootedObject global(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook, globalOptions)); CHECK(global); JSAutoRealm ar(cx, global); + + JS::SourceText<char16_t> srcBuf; + CHECK(srcBuf.init(cx, chars, len, JS::SourceOwnership::Borrowed)); + JS::CompileOptions options(cx); - JS::SourceBufferHolder srcBuf(chars, len, JS::SourceBufferHolder::NoOwnership); return JS::Evaluate(cx, options, srcBuf, rval); } template<size_t N> bool testLineOfContextHasNoLineTerminator(const char16_t (&chars)[N], char16_t expectedLast) { JS::RootedValue rval(cx);
--- a/js/src/jsapi-tests/testJSEvaluateScript.cpp +++ b/js/src/jsapi-tests/testJSEvaluateScript.cpp @@ -1,30 +1,33 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: */ #include "js/CompilationAndEvaluation.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "jsapi-tests/tests.h" using mozilla::ArrayLength; BEGIN_TEST(testJSEvaluateScript) { JS::RootedObject obj(cx, JS_NewPlainObject(cx)); CHECK(obj); static const char16_t src[] = u"var x = 5;"; JS::RootedValue retval(cx); JS::CompileOptions opts(cx); JS::AutoObjectVector scopeChain(cx); CHECK(scopeChain.append(obj)); - JS::SourceBufferHolder srcBuf(src, ArrayLength(src) - 1, JS::SourceBufferHolder::NoOwnership); + + JS::SourceText<char16_t> srcBuf; + CHECK(srcBuf.init(cx, src, ArrayLength(src) - 1, JS::SourceOwnership::Borrowed)); + CHECK(JS::Evaluate(cx, scopeChain, opts.setFileAndLine(__FILE__, __LINE__), srcBuf, &retval)); bool hasProp = true; CHECK(JS_AlreadyHasOwnProperty(cx, obj, "x", &hasProp)); CHECK(hasProp); hasProp = false;
--- a/js/src/jsapi-tests/testMutedErrors.cpp +++ b/js/src/jsapi-tests/testMutedErrors.cpp @@ -1,15 +1,15 @@ /* 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 "jsfriendapi.h" #include "js/CompilationAndEvaluation.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "jsapi-tests/tests.h" BEGIN_TEST(testMutedErrors) { CHECK(testOuter("function f() {return 1}; f;")); CHECK(testOuter("function outer() { return (function () {return 2}); }; outer();")); CHECK(testOuter("eval('(function() {return 3})');")); CHECK(testOuter("(function (){ return eval('(function() {return 4})'); })()")); @@ -52,17 +52,19 @@ eval(const char* asciiChars, bool mutedE JSAutoRealm ar(cx, global); CHECK(JS::InitRealmStandardClasses(cx)); JS::CompileOptions options(cx); options.setMutedErrors(mutedErrors) .setFileAndLine("", 0); - JS::SourceBufferHolder srcBuf(chars.get(), len, JS::SourceBufferHolder::NoOwnership); + JS::SourceText<char16_t> srcBuf; + CHECK(srcBuf.init(cx, chars.get(), len, JS::SourceOwnership::Borrowed)); + return JS::Evaluate(cx, options, srcBuf, rval); } bool testOuter(const char* asciiChars) { CHECK(testInner(asciiChars, false)); CHECK(testInner(asciiChars, true));
--- a/js/src/jsapi-tests/testScriptObject.cpp +++ b/js/src/jsapi-tests/testScriptObject.cpp @@ -1,17 +1,17 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: */ /* 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 "js/CompilationAndEvaluation.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "jsapi-tests/tests.h" struct ScriptObjectFixture : public JSAPITest { static const int code_size; static const char code[]; static char16_t uc_code[]; ScriptObjectFixture() @@ -76,44 +76,50 @@ BEGIN_FIXTURE_TEST(ScriptObjectFixture, } END_FIXTURE_TEST(ScriptObjectFixture, bug438633_CompileScriptForPrincipals) BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScript) { JS::CompileOptions options(cx); options.setFileAndLine(__FILE__, __LINE__); + JS::SourceText<char16_t> srcBuf; + CHECK(srcBuf.init(cx, uc_code, code_size, JS::SourceOwnership::Borrowed)); + JS::RootedScript script(cx); - JS::SourceBufferHolder srcBuf(uc_code, code_size, JS::SourceBufferHolder::NoOwnership); CHECK(JS::Compile(cx, options, srcBuf, &script)); return tryScript(script); } END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScript) BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScript_empty) { JS::CompileOptions options(cx); options.setFileAndLine(__FILE__, __LINE__); + JS::SourceText<char16_t> srcBuf; + CHECK(srcBuf.init(cx, uc_code, 0, JS::SourceOwnership::Borrowed)); + JS::RootedScript script(cx); - JS::SourceBufferHolder srcBuf(uc_code, 0, JS::SourceBufferHolder::NoOwnership); CHECK(JS::Compile(cx, options, srcBuf, &script)); return tryScript(script); } END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScript_empty) BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScriptForPrincipals) { JS::CompileOptions options(cx); options.setFileAndLine(__FILE__, __LINE__); + JS::SourceText<char16_t> srcBuf; + CHECK(srcBuf.init(cx, uc_code, code_size, JS::SourceOwnership::Borrowed)); + JS::RootedScript script(cx); - JS::SourceBufferHolder srcBuf(uc_code, code_size, JS::SourceBufferHolder::NoOwnership); CHECK(JS::Compile(cx, options, srcBuf, &script)); return tryScript(script); } END_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileUCScriptForPrincipals) BEGIN_FIXTURE_TEST(ScriptObjectFixture, bug438633_JS_CompileFile) {
--- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -57,17 +57,17 @@ #include "js/CompileOptions.h" #include "js/Conversions.h" #include "js/Date.h" #include "js/Initialization.h" #include "js/JSON.h" #include "js/LocaleSensitive.h" #include "js/Proxy.h" #include "js/SliceBudget.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "js/StableStringChars.h" #include "js/StructuredClone.h" #include "js/Utility.h" #include "js/Wrapper.h" #include "util/CompleteFile.h" #include "util/StringBuffer.h" #include "util/Text.h" #include "vm/AsyncFunction.h" @@ -109,17 +109,17 @@ using namespace js; using mozilla::Maybe; using mozilla::PodCopy; using mozilla::Some; using JS::AutoStableStringChars; using JS::CompileOptions; using JS::ReadOnlyCompileOptions; -using JS::SourceBufferHolder; +using JS::SourceText; #ifdef HAVE_VA_LIST_AS_ARRAY #define JS_ADDRESSOF_VA_LIST(ap) ((va_list*)(ap)) #else #define JS_ADDRESSOF_VA_LIST(ap) (&(ap)) #endif JS_PUBLIC_API(bool) @@ -4157,17 +4157,17 @@ JS::FinishDynamicModuleImport(JSContext* CHECK_THREAD(cx); cx->check(referencingPrivate, promise); return js::FinishDynamicModuleImport(cx, referencingPrivate, specifier, promise); } JS_PUBLIC_API(bool) JS::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options, - SourceBufferHolder& srcBuf, JS::MutableHandleObject module) + SourceText<char16_t>& srcBuf, JS::MutableHandleObject module) { MOZ_ASSERT(!cx->zone()->isAtomsZone()); AssertHeapIsIdle(); CHECK_THREAD(cx); module.set(frontend::CompileModule(cx, options, srcBuf)); return !!module; }
--- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -10,16 +10,17 @@ #define jsapi_h #include "mozilla/AlreadyAddRefed.h" #include "mozilla/FloatingPoint.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Range.h" #include "mozilla/RangedPtr.h" #include "mozilla/RefPtr.h" +#include "mozilla/Utf8.h" #include "mozilla/Variant.h" #include <stdarg.h> #include <stddef.h> #include <stdint.h> #include <stdio.h> #include "jspubtd.h" @@ -46,17 +47,18 @@ #include "js/Utility.h" #include "js/Value.h" #include "js/Vector.h" /************************************************************************/ namespace JS { -class SourceBufferHolder; +template<typename UnitT> class SourceText; + class TwoByteChars; /** AutoValueArray roots an internal fixed-size array of Values. */ template <size_t N> class MOZ_RAII AutoValueArray : public AutoGCRooter { const size_t length_; Value elements_[N]; @@ -3120,17 +3122,17 @@ FinishDynamicModuleImport(JSContext* cx, HandleObject promise); /** * Parse the given source buffer as a module in the scope of the current global * of cx and return a source text module record. */ extern JS_PUBLIC_API(bool) CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options, - SourceBufferHolder& srcBuf, JS::MutableHandleObject moduleRecord); + SourceText<char16_t>& srcBuf, JS::MutableHandleObject moduleRecord); /** * Set a private value associated with a source text module record. */ extern JS_PUBLIC_API(void) SetModulePrivate(JSObject* module, const JS::Value& value); /**
--- a/js/src/moz.build +++ b/js/src/moz.build @@ -146,17 +146,17 @@ EXPORTS.js += [ '../public/Proxy.h', '../public/Realm.h', '../public/RefCounted.h', '../public/RequiredDefines.h', '../public/Result.h', '../public/RootingAPI.h', '../public/SavedFrameAPI.h', '../public/SliceBudget.h', - '../public/SourceBufferHolder.h', + '../public/SourceText.h', '../public/StableStringChars.h', '../public/Stream.h', '../public/StructuredClone.h', '../public/SweepingAPI.h', '../public/TraceKind.h', '../public/TraceLoggerAPI.h', '../public/TracingAPI.h', '../public/TrackedOptimizationInfo.h',
--- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -65,33 +65,34 @@ #include "builtin/Array.h" #include "builtin/ModuleObject.h" #include "builtin/RegExp.h" #include "builtin/TestingFunctions.h" #if defined(JS_BUILD_BINAST) # include "frontend/BinSource.h" #endif // defined(JS_BUILD_BINAST) +#include "frontend/ModuleSharedContext.h" #include "frontend/Parser.h" #include "gc/PublicIterators.h" #include "jit/arm/Simulator-arm.h" #include "jit/InlinableNatives.h" #include "jit/Ion.h" #include "jit/JitcodeMap.h" #include "jit/JitRealm.h" #include "jit/shared/CodeGenerator-shared.h" #include "js/CharacterEncoding.h" #include "js/CompilationAndEvaluation.h" #include "js/CompileOptions.h" #include "js/Debug.h" #include "js/GCVector.h" #include "js/Initialization.h" #include "js/JSON.h" #include "js/Printf.h" -#include "js/SourceBufferHolder.h" +#include "js/SourceText.h" #include "js/StableStringChars.h" #include "js/StructuredClone.h" #include "js/SweepingAPI.h" #include "js/Wrapper.h" #include "perf/jsperf.h" #include "shell/jsoptparse.h" #include "shell/jsshell.h" #include "shell/OSObject.h" @@ -874,18 +875,25 @@ RegisterScriptPathWithModuleLoader(JSCon if (!JS_DefineProperty(cx, infoObject, "path", pathValue, 0)) { return false; } JS::SetScriptPrivate(script, ObjectValue(*infoObject)); return true; } +enum class CompileUtf8 +{ + InflateToUtf16, + DontInflate, +}; + static MOZ_MUST_USE bool -RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly) +RunFile(JSContext* cx, const char* filename, FILE* file, CompileUtf8 compileMethod, + bool compileOnly) { SkipUTF8BOM(file); // To support the UNIX #! shell hack, gobble the first line if it starts // with '#'. int ch = fgetc(file); if (ch == '#') { while ((ch = fgetc(file)) != EOF) { @@ -901,19 +909,28 @@ RunFile(JSContext* cx, const char* filen { CompileOptions options(cx); options.setIntroductionType("js shell file") .setFileAndLine(filename, 1) .setIsRunOnce(true) .setNoScriptRval(true); - if (!JS::CompileUtf8File(cx, options, file, &script)) { - return false; - } + if (compileMethod == CompileUtf8::DontInflate) { + fprintf(stderr, "(compiling '%s' as UTF-8 without inflating)\n", filename); + + if (!JS::CompileUtf8FileDontInflate(cx, options, file, &script)) { + return false; + } + } else { + if (!JS::CompileUtf8File(cx, options, file, &script)) { + return false; + } + } + MOZ_ASSERT(script); } if (!RegisterScriptPathWithModuleLoader(cx, script, filename)) { return false; } #ifdef DEBUG @@ -1358,16 +1375,17 @@ ReadEvalPrintLoop(JSContext* cx, FILE* i } return true; } enum FileKind { FileScript, + FileScriptUtf8, // FileScript, but don't inflate to UTF-16 before parsing FileModule, FileBinAST }; static void ReportCantOpenErrorUnknownEncoding(JSContext* cx, const char* filename) { /* @@ -1380,17 +1398,17 @@ ReportCantOpenErrorUnknownEncoding(JSCon * wrong it'll produce mojibake *safely*. Run with Latin-1 til someone * complains. */ JS_ReportErrorNumberLatin1(cx, my_GetErrorMessage, nullptr, JSSMSG_CANT_OPEN, filename, strerror(errno)); } static MOZ_MUST_USE bool -Process(JSContext* cx, const char* filename, bool forceTTY, FileKind kind = FileScript) +Process(JSContext* cx, const char* filename, bool forceTTY, FileKind kind) { FILE* file; if (forceTTY || !filename || strcmp(filename, "-") == 0) { file = stdin; } else { file = fopen(filename, "rb"); if (!file) { ReportCantOpenErrorUnknownEncoding(cx, filename); @@ -1398,17 +1416,22 @@ Process(JSContext* cx, const char* filen } } AutoCloseFile autoClose(file); if (!forceTTY && !isatty(fileno(file))) { // It's not interactive - just execute it. switch (kind) { case FileScript: - if (!RunFile(cx, filename, file, compileOnly)) { + if (!RunFile(cx, filename, file, CompileUtf8::InflateToUtf16, compileOnly)) { + return false; + } + break; + case FileScriptUtf8: + if (!RunFile(cx, filename, file, CompileUtf8::DontInflate, compileOnly)) { return false; } break; case FileModule: if (!RunModule(cx, filename, file, compileOnly)) { return false; } break; @@ -2136,18 +2159,23 @@ Evaluate(JSContext* cx, unsigned argc, V if (loadBytecode) { JS::TranscodeResult rv = JS::DecodeScript(cx, loadBuffer, &script); if (!ConvertTranscodeResultToJSException(cx, rv)) { return false; } } else { mozilla::Range<const char16_t> chars = codeChars.twoByteRange(); - JS::SourceBufferHolder srcBuf(chars.begin().get(), chars.length(), - JS::SourceBufferHolder::NoOwnership); + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(cx, chars.begin().get(), chars.length(), + JS::SourceOwnership::Borrowed)) + { + return false; + } + if (envChain.length() == 0) { (void) JS::Compile(cx, options, srcBuf, &script); } else { (void) JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script); } } if (!script) { @@ -2378,18 +2406,22 @@ Run(JSContext* cx, unsigned argc, Value* return false; } AutoStableStringChars chars(cx); if (!chars.initTwoByte(cx, str)) { return false; } - JS::SourceBufferHolder srcBuf(chars.twoByteRange().begin().get(), str->length(), - JS::SourceBufferHolder::NoOwnership); + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(cx, chars.twoByteRange().begin().get(), str->length(), + JS::SourceOwnership::Borrowed)) + { + return false; + } RootedScript script(cx); int64_t startClock = PRMJ_Now(); { /* FIXME: This should use UTF-8 (bug 987069). */ UniqueChars filename = JS_EncodeStringToLatin1(cx, str); if (!filename) { return false; @@ -3858,20 +3890,24 @@ EvalInContext(JSContext* cx, unsigned ar } sobj = ToWindowIfWindowProxy(sobj); if (!(sobj->getClass()->flags & JSCLASS_IS_GLOBAL)) { JS_ReportErrorASCII(cx, "Invalid scope argument to evalcx"); return false; } + JS::CompileOptions opts(cx); opts.setFileAndLine(filename.get(), lineno); - JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership); - if (!JS::Evaluate(cx, opts, srcBuf, args.rval())) { + + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(cx, src, srclen, JS::SourceOwnership::Borrowed) || + !JS::Evaluate(cx, opts, srcBuf, args.rval())) + { return false; } } if (!cx->compartment()->wrap(cx, args.rval())) { return false; } @@ -3974,19 +4010,21 @@ WorkerMain(WorkerInput* input) JSAutoRealm ar(cx, global); JS::CompileOptions options(cx); options.setFileAndLine("<string>", 1) .setIsRunOnce(true); AutoReportException are(cx); RootedScript script(cx); - JS::SourceBufferHolder srcBuf(input->chars.get(), input->length, - JS::SourceBufferHolder::NoOwnership); - if (!JS::Compile(cx, options, srcBuf, &script)) { + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(cx, input->chars.get(), input->length, + JS::SourceOwnership::Borrowed) || + !JS::Compile(cx, options, srcBuf, &script)) + { break; } RootedValue result(cx); JS_ExecuteScript(cx, script, &result); } while (0); KillWatchdog(cx); JS_SetGrayGCRootsTracer(cx, nullptr, nullptr); @@ -4651,23 +4689,31 @@ Compile(JSContext* cx, unsigned argc, Va return false; } JS::CompileOptions options(cx); options.setIntroductionType("js shell compile") .setFileAndLine("<string>", 1) .setIsRunOnce(true) .setNoScriptRval(true); + + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(cx, stableChars.twoByteRange().begin().get(), scriptContents->length(), + JS::SourceOwnership::Borrowed)) + { + return false; + } + RootedScript script(cx); - JS::SourceBufferHolder srcBuf(stableChars.twoByteRange().begin().get(), - scriptContents->length(), - JS::SourceBufferHolder::NoOwnership); - bool ok = JS::Compile(cx, options, srcBuf, &script); + if (!JS::Compile(cx, options, srcBuf, &script)) { + return false; + } + args.rval().setUndefined(); - return ok; + return true; } static ShellCompartmentPrivate* EnsureShellCompartmentPrivate(JSContext* cx) { Compartment* comp = cx->compartment(); auto priv = static_cast<ShellCompartmentPrivate*>(JS_GetCompartmentPrivate(comp)); if (!priv) { @@ -4719,18 +4765,20 @@ ParseModule(JSContext* cx, unsigned argc } AutoStableStringChars stableChars(cx); if (!stableChars.initTwoByte(cx, scriptContents)) { return false; } const char16_t* chars = stableChars.twoByteRange().begin().get(); - JS::SourceBufferHolder srcBuf(chars, scriptContents->length(), - JS::SourceBufferHolder::NoOwnership); + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(cx, chars, scriptContents->length(), JS::SourceOwnership::Borrowed)) { + return false; + } RootedObject module(cx, frontend::CompileModule(cx, options, srcBuf)); if (!module) { return false; } args.rval().setObject(*module); return true; @@ -5261,17 +5309,17 @@ Parse(JSContext* cx, unsigned argc, Valu return false; } Rooted<ModuleObject*> module(cx, ModuleObject::create(cx)); if (!module) { return false; } - ModuleBuilder builder(cx, module, parser.anyChars); + ModuleBuilder builder(cx, module, &parser); ModuleSharedContext modulesc(cx, module, nullptr, builder); pn = parser.moduleBody(&modulesc); } if (!pn) { return false; } #ifdef DEBUG @@ -5431,20 +5479,19 @@ OffThreadCompileScript(JSContext* cx, un } OffThreadJob* job = NewOffThreadJob(cx, ScriptKind::Script, OffThreadJob::Source(std::move(ownedChars))); if (!job) { return false; } - JS::SourceBufferHolder srcBuf(job->sourceChars(), length, - JS::SourceBufferHolder::NoOwnership); - if (!JS::CompileOffThread(cx, options, srcBuf, - OffThreadCompileScriptCallback, job)) + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(cx, job->sourceChars(), length, JS::SourceOwnership::Borrowed) || + !JS::CompileOffThread(cx, options, srcBuf, OffThreadCompileScriptCallback, job)) { job->cancel(); DeleteOffThreadJob(cx, job); return false; } args.rval().setInt32(job->id); return true; @@ -5525,20 +5572,19 @@ OffThreadCompileModule(JSContext* cx, un } OffThreadJob* job = NewOffThreadJob(cx, ScriptKind::Module, OffThreadJob::Source(std::move(ownedChars))); if (!job) { return false; } - JS::SourceBufferHolder srcBuf(job->sourceChars(), length, - JS::SourceBufferHolder::NoOwnership); - if (!JS::CompileOffThreadModule(cx, options, srcBuf, - OffThreadCompileScriptCallback, job)) + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(cx, job->sourceChars(), length, JS::SourceOwnership::Borrowed) || + !JS::CompileOffThreadModule(cx, options, srcBuf, OffThreadCompileScriptCallback, job)) { job->cancel(); DeleteOffThreadJob(cx, job); return false; } args.rval().setInt32(job->id); return true; @@ -8206,19 +8252,22 @@ EntryPoints(JSContext* cx, unsigned argc if (!codeString || !codeString->ensureFlat(cx)) { return false; } AutoStableStringChars stableChars(cx); if (!stableChars.initTwoByte(cx, codeString)) { return false; } - JS::SourceBufferHolder srcBuf(stableChars.twoByteRange().begin().get(), - codeString->length(), - JS::SourceBufferHolder::NoOwnership); + JS::SourceText<char16_t> srcBuf; + if (!srcBuf.init(cx, stableChars.twoByteRange().begin().get(), codeString->length(), + JS::SourceOwnership::Borrowed)) + { + return false; + } CompileOptions options(cx); options.setIntroductionType("entryPoint eval") .setFileAndLine("entryPoint eval", 1); js::shell::ShellAutoEntryMonitor sarep(cx); if (!JS::Evaluate(cx, options, srcBuf, &dummy)) { return false; @@ -10247,30 +10296,32 @@ ProcessArgs(JSContext* cx, OptionParser* } /* |scriptArgs| gets bound on the global before any code is run. */ if (!BindScriptArgs(cx, op)) { return false; } MultiStringRange filePaths = op->getMultiStringOption('f'); + MultiStringRange utf8FilePaths = op->getMultiStringOption('u'); MultiStringRange codeChunks = op->getMultiStringOption('e'); MultiStringRange modulePaths = op->getMultiStringOption('m'); MultiStringRange binASTPaths(nullptr, nullptr); #if defined(JS_BUILD_BINAST) binASTPaths = op->getMultiStringOption('B'); #endif // JS_BUILD_BINAST if (filePaths.empty() && + utf8FilePaths.empty() && codeChunks.empty() && modulePaths.empty() && binASTPaths.empty() && !op->getStringArg("script")) { - return Process(cx, nullptr, true); /* Interactive. */ + return Process(cx, nullptr, true, FileScript); /* Interactive. */ } if (const char* path = op->getStringOption("module-load-path")) { RootedString jspath(cx, JS_NewStringCopyZ(cx, path)); if (!jspath) { return false; } @@ -10287,74 +10338,105 @@ ProcessArgs(JSContext* cx, OptionParser* if (!sc->moduleLoadPath) { return false; } if (!InitModuleLoader(cx)) { return false; } - while (!filePaths.empty() || !codeChunks.empty() || !modulePaths.empty() || !binASTPaths.empty()) { + while (!filePaths.empty() || + !utf8FilePaths.empty() || + !codeChunks.empty() || + !modulePaths.empty() || + !binASTPaths.empty()) + { size_t fpArgno = filePaths.empty() ? SIZE_MAX : filePaths.argno(); + size_t ufpArgno = utf8FilePaths.empty() ? SIZE_MAX : utf8FilePaths.argno(); size_t ccArgno = codeChunks.empty() ? SIZE_MAX : codeChunks.argno(); size_t mpArgno = modulePaths.empty() ? SIZE_MAX : modulePaths.argno(); size_t baArgno = binASTPaths.empty() ? SIZE_MAX : binASTPaths.argno(); - if (fpArgno < ccArgno && fpArgno < mpArgno && fpArgno < baArgno) { + if (fpArgno < ufpArgno && fpArgno < ccArgno && fpArgno < mpArgno && fpArgno < baArgno) { char* path = filePaths.front(); if (!Process(cx, path, false, FileScript)) { return false; } + filePaths.popFront(); - } else if (ccArgno < fpArgno && ccArgno < mpArgno && ccArgno < baArgno) { + continue; + } + + if (ufpArgno < fpArgno && ufpArgno < ccArgno && ufpArgno < mpArgno && ufpArgno < baArgno) { + char* path = utf8FilePaths.front(); + if (!Process(cx, path, false, FileScriptUtf8)) { + return false; + } + + utf8FilePaths.popFront(); + continue; + } + + if (ccArgno < fpArgno && ccArgno < ufpArgno && ccArgno < mpArgno && ccArgno < baArgno) { const char* code = codeChunks.front(); JS::CompileOptions opts(cx); opts.setFileAndLine("-e", 1); // This might be upgradable to UTF-8, but for now keep assuming the // worst. RootedValue rval(cx); if (!JS::EvaluateLatin1(cx, opts, code, strlen(code), &rval)) { return false; } codeChunks.popFront(); if (sc->quitting) { break; } - } else if (baArgno < fpArgno && baArgno < ccArgno && baArgno < mpArgno) { + + continue; + } + + if (baArgno < fpArgno && baArgno < ufpArgno && baArgno < ccArgno && baArgno < mpArgno) { char* path = binASTPaths.front(); if (!Process(cx, path, false, FileBinAST)) { return false; } + binASTPaths.popFront(); - } else { - MOZ_ASSERT(mpArgno < fpArgno && mpArgno < ccArgno && mpArgno < baArgno); - char* path = modulePaths.front(); - if (!Process(cx, path, false, FileModule)) { - return false; - } - modulePaths.popFront(); - } + continue; + } + + MOZ_ASSERT(mpArgno < fpArgno && + mpArgno < ufpArgno && + mpArgno < ccArgno && + mpArgno < baArgno); + + char* path = modulePaths.front(); + if (!Process(cx, path, false, FileModule)) { + return false; + } + + modulePaths.popFront(); } if (sc->quitting) { return false; } /* The |script| argument is processed after all options. */ if (const char* path = op->getStringArg("script")) { - if (!Process(cx, path, false)) { + if (!Process(cx, path, false, FileScript)) { return false; } } if (op->getBoolOption('i')) { - if (!Process(cx, nullptr, true)) { + if (!Process(cx, nullptr, true, FileScript)) { return false; } } return true; } static bool @@ -10952,16 +11034,19 @@ main(int argc, char** argv, char** envp) "run left to right. If provided, the optional script argument is run after " "all options have been processed. Just-In-Time compilation modes may be enabled via " "command line options."); op.setDescriptionWidth(72); op.setHelpWidth(80); op.setVersion(JS_GetImplementationVersion()); if (!op.addMultiStringOption('f', "file", "PATH", "File path to run") + || !op.addMultiStringOption('u', "utf8-file", "PATH", + "File path to run, directly parsing file contents as UTF-8 " + "without first inflating to UTF-16") || !op.addMultiStringOption('m', "module", "PATH", "Module path to run") #if defined(JS_BUILD_BINAST) || !op.addMultiStringOption('B', "binast", "PATH", "BinAST path to run") #else || !op.addMultiStringOption('B', "binast", "", "No-op") #endif // JS_BUILD_BINAST || !op.addMultiStringOption('e', "execute", "CODE", "Inline code to run") || !op.addBoolOption('i', "shell", "Enter prompt after running code")
--- a/js/src/util/StringBuffer.h +++ b/js/src/util/StringBuffer.h @@ -4,16 +4,17 @@ * 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 util_StringBuffer_h #define util_StringBuffer_h #include "mozilla/DebugOnly.h" #include "mozilla/MaybeOneOf.h" +#include "mozilla/Utf8.h" #include "js/Vector.h" #include "vm/JSContext.h" namespace js { /* * String builder that eagerly checks for over-allocation past the maximum @@ -154,16 +155,24 @@ class StringBuffer MOZ_MUST_USE bool append(const Latin1Char* begin, const Latin1Char* end) { return isLatin1() ? latin1Chars().append(begin, end) : twoByteChars().append(begin, end); } MOZ_MUST_USE bool append(const Latin1Char* chars, size_t len) { return append(chars, chars + len); } + /** + * Interpret the provided count of UTF-8 code units as UTF-8, and append + * the represented code points to this. If the code units contain invalid + * UTF-8, leave the internal buffer in a consistent but unspecified state, + * report an error, and return false. + */ + MOZ_MUST_USE bool append(const mozilla::Utf8Unit* units, size_t len); + MOZ_MUST_USE bool append(const JS::ConstCharPtr chars, size_t len) { return append(chars.get(), chars.get() + len); } MOZ_MUST_USE bool appendN(Latin1Char c, size_t n) { return isLatin1() ? latin1Chars().appendN(c, n) : twoByteChars().appendN(c, n); } inline MOZ_MUST_USE bool append(JSString* str);
--- a/js/src/vm/CharacterEncoding.cpp +++ b/js/src/vm/CharacterEncoding.cpp @@ -3,23 +3,29 @@ * 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 "js/CharacterEncoding.h" #include "mozilla/Range.h" #include "mozilla/Sprintf.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Utf8.h" #include <algorithm> #include <type_traits> +#include "util/StringBuffer.h" #include "util/Unicode.h" // unicode::REPLACEMENT_CHARACTER #include "vm/JSContext.h" +using mozilla::IsAscii; +using mozilla::Utf8Unit; + using namespace js; Latin1CharsZ JS::LossyTwoByteCharsToNewLatin1CharsZ(JSContext* cx, const mozilla::Range<const char16_t> tbchars) { MOZ_ASSERT(cx); size_t len = tbchars.length(); @@ -602,8 +608,73 @@ JS::StringIsASCII(const char* s) while (*s) { if (*s & 0x80) { return false; } s++; } return true; } + +bool +StringBuffer::append(const Utf8Unit* units, size_t len) +{ + if (isLatin1()) { + Latin1CharBuffer& latin1 = latin1Chars(); + + while (len > 0) { + if (!IsAscii(*units)) { + break; + } + + if (!latin1.append(units->toUnsignedChar())) { + return false; + } + + ++units; + --len; + } + if (len == 0) { + return true; + } + + // Non-ASCII doesn't *necessarily* mean we couldn't keep appending to + // |latin1|, but it's only possible for [U+0080, U+0100) code points, + // and handling the full complexity of UTF-8 only for that very small + // additional range isn't worth it. Inflate to two-byte storage before + // appending the remaining code points. + if (!inflateChars()) { + return false; + } + } + + UTF8Chars remainingUtf8(units, len); + + // Determine how many UTF-16 code units are required to represent the + // remaining units. + size_t utf16Len = 0; + auto countInflated = [&utf16Len](char16_t c) -> LoopDisposition { + utf16Len++; + return LoopDisposition::Continue; + }; + if (!InflateUTF8ToUTF16<OnUTF8Error::Throw>(cx, remainingUtf8, countInflated)) { + return false; + } + + TwoByteCharBuffer& buf = twoByteChars(); + + size_t i = buf.length(); + if (!buf.growByUninitialized(utf16Len)) { + return false; + } + MOZ_ASSERT(i + utf16Len == buf.length(), + "growByUninitialized assumed to increase length immediately"); + + char16_t* toFill = &buf[i]; + auto appendUtf16 = [&toFill](char16_t unit) { + *toFill++ = unit; + return LoopDisposition::Continue; + }; + + MOZ_ALWAYS_TRUE(InflateUTF8ToUTF16<OnUTF8Error::Throw>(cx, remainingUtf8, appendUtf16)); + MOZ_ASSERT(toFill == buf.end()); + return true; +}
--- a/js/src/vm/CompilationAndEvaluation.cpp +++ b/js/src/vm/CompilationAndEvaluation.cpp @@ -5,90 +5,127 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* Same-thread compilation and evaluation APIs. */ #include "js/CompilationAndEvaluation.h" #include "mozilla/Maybe.h" // mozilla::None, mozilla::Some #include "mozilla/TextUtils.h" // mozilla::IsAscii +#include "mozilla/Utf8.h" // mozilla::Utf8Unit -#include <algorithm> // std::all_of +#include <utility> // std::move +#include "jsfriendapi.h" // js::GetErrorMessage #include "jstypes.h" // JS_PUBLIC_API +#include "frontend/BytecodeCompilation.h" // frontend::CompileGlobalScript #include "frontend/FullParseHandler.h" // frontend::FullParseHandler #include "frontend/ParseContext.h" // frontend::UsedNameTracker #include "frontend/Parser.h" // frontend::Parser, frontend::ParseGoal #include "js/CharacterEncoding.h" // JS::UTF8Chars, JS::UTF8CharsToNewTwoByteCharsZ #include "js/RootingAPI.h" // JS::Rooted -#include "js/SourceBufferHolder.h" // JS::SourceBufferHolder +#include "js/SourceText.h" // JS::SourceText #include "js/TypeDecls.h" // JS::HandleObject, JS::MutableHandleScript +#include "js/Utility.h" // JS::UniqueTwoByteChars #include "js/Value.h" // JS::Value #include "util/CompleteFile.h" // js::FileContents, js::ReadCompleteFile #include "util/StringBuffer.h" // js::StringBuffer #include "vm/Debugger.h" // js::Debugger #include "vm/EnvironmentObject.h" // js::CreateNonSyntacticEnvironmentChain #include "vm/Interpreter.h" // js::Execute #include "vm/JSContext.h" // JSContext #include "vm/JSContext-inl.h" // JSContext::check +using mozilla::Utf8Unit; + using JS::CompileOptions; using JS::HandleObject; using JS::ReadOnlyCompileOptions; -using JS::SourceBufferHolder; +using JS::SourceOwnership; +using JS::SourceText; +using JS::UniqueTwoByteChars; using JS::UTF8Chars; using JS::UTF8CharsToNewTwoByteCharsZ; using namespace js; +JS_PUBLIC_API(void) +JS::detail::ReportSourceTooLong(JSContext* cx) +{ + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SOURCE_TOO_LONG); +} + +template<typename Unit> static bool CompileSourceBuffer(JSContext* cx, const ReadOnlyCompileOptions& options, - SourceBufferHolder& srcBuf, JS::MutableHandleScript script) + SourceText<Unit>& srcBuf, JS::MutableHandleScript script) { ScopeKind scopeKind = options.nonSyntacticScope ? ScopeKind::NonSyntactic : ScopeKind::Global; MOZ_ASSERT(!cx->zone()->isAtomsZone()); AssertHeapIsIdle(); CHECK_THREAD(cx); - script.set(frontend::CompileGlobalScript(cx, scopeKind, options, srcBuf)); + frontend::GlobalScriptInfo info(cx, options, scopeKind); + script.set(frontend::CompileGlobalScript(info, srcBuf)); return !!script; } static bool CompileLatin1(JSContext* cx, const ReadOnlyCompileOptions& options, const char* bytes, size_t length, JS::MutableHandleScript script) { - char16_t* chars = InflateString(cx, bytes, length); + auto chars = UniqueTwoByteChars(InflateString(cx, bytes, length)); if (!chars) { return false; } - SourceBufferHolder source(chars, length, SourceBufferHolder::GiveOwnership); + SourceText<char16_t> source; + if (!source.init(cx, std::move(chars), length)) { + return false; + } + return CompileSourceBuffer(cx, options, source, script); } static bool CompileUtf8(JSContext* cx, const ReadOnlyCompileOptions& options, const char* bytes, size_t length, JS::MutableHandleScript script) { - char16_t* chars = UTF8CharsToNewTwoByteCharsZ(cx, UTF8Chars(bytes, length), &length).get(); + auto chars = UniqueTwoByteChars(UTF8CharsToNewTwoByteCharsZ(cx, UTF8Chars(bytes, length), + &length).get()); if (!chars) { return false; } - SourceBufferHolder source(chars, length, SourceBufferHolder::GiveOwnership); + SourceText<char16_t> source; + if (!source.init(cx, std::move(chars), length)) { + return false; + } + + return CompileSourceBuffer(cx, options, source, script); +} + +static bool +CompileUtf8DontInflate(JSContext* cx, const ReadOnlyCompileOptions& options, + const char* bytes, size_t length, JS::MutableHandleScript script) +{ + SourceText<Utf8Unit> source; + if (!source.init(cx, bytes, length, SourceOwnership::Borrowed)) { + return false; + } + return CompileSourceBuffer(cx, options, source, script); } bool JS::Compile(JSContext* cx, const ReadOnlyCompileOptions& options, - SourceBufferHolder& srcBuf, JS::MutableHandleScript script) + SourceText<char16_t>& srcBuf, JS::MutableHandleScript script) { return CompileSourceBuffer(cx, options, srcBuf, script); } bool JS::CompileLatin1(JSContext* cx, const ReadOnlyCompileOptions& options, const char* bytes, size_t length, JS::MutableHandleScript script) { @@ -98,45 +135,66 @@ JS::CompileLatin1(JSContext* cx, const R bool JS::CompileUtf8(JSContext* cx, const ReadOnlyCompileOptions& options, const char* bytes, size_t length, JS::MutableHandleScript script) { return ::CompileUtf8(cx, options, bytes, length, script); } bool +JS::CompileUtf8DontInflate(JSContext* cx, const ReadOnlyCompileOptions& options, + const char* bytes, size_t length, JS::MutableHandleScript script) +{ + return ::CompileUtf8DontInflate(cx, options, bytes, length, script); +} + +bool JS::CompileUtf8File(JSContext* cx, const ReadOnlyCompileOptions& options, FILE* file, JS::MutableHandleScript script) { FileContents buffer(cx); if (!ReadCompleteFile(cx, file, buffer)) { return false; } return ::CompileUtf8(cx, options, reinterpret_cast<const char*>(buffer.begin()), buffer.length(), script); } bool +JS::CompileUtf8FileDontInflate(JSContext* cx, const ReadOnlyCompileOptions& options, + FILE* file, JS::MutableHandleScript script) +{ + FileContents buffer(cx); + if (!ReadCompleteFile(cx, file, buffer)) { + return false; + } + + return ::CompileUtf8DontInflate(cx, options, + reinterpret_cast<const char*>(buffer.begin()), buffer.length(), + script); +} + +bool JS::CompileUtf8Path(JSContext* cx, const ReadOnlyCompileOptions& optionsArg, const char* filename, JS::MutableHandleScript script) { AutoFile file; if (!file.open(cx, filename)) { return false; } CompileOptions options(cx, optionsArg); options.setFileAndLine(filename, 1); return CompileUtf8File(cx, options, file.fp(), script); } bool JS::CompileForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& optionsArg, - SourceBufferHolder& srcBuf, JS::MutableHandleScript script) + SourceText<char16_t>& srcBuf, JS::MutableHandleScript script) { CompileOptions options(cx, optionsArg); options.setNonSyntacticScope(true); return CompileSourceBuffer(cx, options, srcBuf, script); } bool JS::CompileLatin1ForNonSyntacticScope(JSContext* cx, const ReadOnlyCompileOptions& optionsArg, @@ -207,17 +265,17 @@ JS_Utf8BufferIsCompilableUnit(JSContext* * enclosingScope is a scope, if any (e.g. a WithScope). If the scope is the * global scope, this must be null. * * enclosingEnv is an environment to use, if it's not the global. */ static bool CompileFunction(JSContext* cx, const ReadOnlyCompileOptions& optionsArg, HandleAtom name, bool isInvalidName, - SourceBufferHolder& srcBuf, uint32_t parameterListEnd, + SourceText<char16_t>& srcBuf, uint32_t parameterListEnd, HandleObject enclosingEnv, HandleScope enclosingScope, MutableHandleFunction fun) { MOZ_ASSERT(!cx->zone()->isAtomsZone()); AssertHeapIsIdle(); CHECK_THREAD(cx); cx->check(enclosingEnv); RootedAtom funAtom(cx); @@ -249,17 +307,17 @@ CompileFunction(JSContext* cx, const Rea } return true; } static MOZ_MUST_USE bool BuildFunctionString(const char* name, size_t nameLen, unsigned nargs, const char* const* argnames, - const SourceBufferHolder& srcBuf, StringBuffer* out, + const SourceText<char16_t>& srcBuf, StringBuffer* out, uint32_t* parameterListEnd) { MOZ_ASSERT(out); MOZ_ASSERT(parameterListEnd); if (!out->ensureTwoByteChars()) { return false; } @@ -301,17 +359,17 @@ BuildFunctionString(const char* name, si return true; } JS_PUBLIC_API(bool) JS::CompileFunction(JSContext* cx, AutoObjectVector& envChain, const ReadOnlyCompileOptions& options, const char* name, unsigned nargs, const char* const* argnames, - SourceBufferHolder& srcBuf, MutableHandleFunction fun) + SourceText<char16_t>& srcBuf, MutableHandleFunction fun) { RootedObject env(cx); RootedScope scope(cx); if (!CreateNonSyntacticEnvironmentChain(cx, envChain, &env, &scope)) { return false; } size_t nameLen = 0; @@ -334,34 +392,47 @@ JS::CompileFunction(JSContext* cx, AutoO StringBuffer funStr(cx); if (!BuildFunctionString(isInvalidName ? nullptr : name, nameLen, nargs, argnames, srcBuf, &funStr, ¶meterListEnd)) { return false; } size_t newLen = funStr.length(); - SourceBufferHolder newSrcBuf(funStr.stealChars(), newLen, SourceBufferHolder::GiveOwnership); + UniqueTwoByteChars stolen(funStr.stealChars()); + if (!stolen) { + return false; + } + + SourceText<char16_t> newSrcBuf; + if (!newSrcBuf.init(cx, std::move(stolen), newLen)) { + return false; + } return CompileFunction(cx, options, nameAtom, isInvalidName, newSrcBuf, parameterListEnd, env,