--- a/browser/extensions/e10srollout/bootstrap.js
+++ b/browser/extensions/e10srollout/bootstrap.js
@@ -4,56 +4,48 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/UpdateUtils.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/TelemetryArchive.jsm");
-Cu.import("resource://gre/modules/TelemetryController.jsm");
// The amount of people to be part of e10s
const TEST_THRESHOLD = {
"beta" : 0.5, // 50%
"release" : 1.0, // 100%
};
const ADDON_ROLLOUT_POLICY = {
"beta" : "50allmpc", // Any WebExtension or addon with mpc = true
- "release" : "49a", // 10 tested add-ons + any WebExtension
+ "release" : "50allmpc", // Any WebExtension or addon with mpc = true
};
const PREF_COHORT_SAMPLE = "e10s.rollout.cohortSample";
const PREF_COHORT_NAME = "e10s.rollout.cohort";
const PREF_E10S_OPTED_IN = "browser.tabs.remote.autostart";
const PREF_E10S_FORCE_ENABLED = "browser.tabs.remote.force-enable";
const PREF_E10S_FORCE_DISABLED = "browser.tabs.remote.force-disable";
const PREF_TOGGLE_E10S = "browser.tabs.remote.autostart.2";
const PREF_E10S_ADDON_POLICY = "extensions.e10s.rollout.policy";
const PREF_E10S_ADDON_BLOCKLIST = "extensions.e10s.rollout.blocklist";
const PREF_E10S_HAS_NONEXEMPT_ADDON = "extensions.e10s.rollout.hasAddon";
-const PREF_DISABLED_FOR_SPINNERS = "e10s.rollout.disabledByLongSpinners";
-
-const LONG_SPINNER_HISTOGRAM = "FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS";
function startup() {
// In theory we only need to run this once (on install()), but
// it's better to also run it on every startup. If the user has
// made manual changes to the prefs, this will keep the data
// reported more accurate.
// It's also fine (and preferred) to just do it here on startup
// (instead of observing prefs), because e10s takes a restart
// to take effect, so we keep the data based on how it was when
// the session started.
defineCohort();
-
- setUpSpinnerCheck();
}
function install() {
defineCohort();
}
let cohortDefinedOnThisSession = false;
@@ -173,105 +165,20 @@ function optedOut() {
/* If this function returns a non-empty string, it
* means that this particular user should be temporarily
* disqualified due to some particular reason.
* If a user shouldn't be disqualified, then an empty
* string must be returned.
*/
function getTemporaryDisqualification() {
- if (Preferences.isSet(PREF_DISABLED_FOR_SPINNERS)) {
- return "longspinner";
- }
-
let applicationLanguage =
Cc["@mozilla.org/chrome/chrome-registry;1"]
.getService(Ci.nsIXULChromeRegistry)
.getSelectedLocale("global")
.split("-")[0];
if (applicationLanguage == "ru") {
return "ru";
}
return "";
}
-
-let performLongSpinnerCheck = Task.async(function*() {
- if (!Services.appinfo.browserTabsRemoteAutostart) {
- return;
- }
-
- const DAYS_OLD = 3;
- let thresholdDate = new Date(Date.now() - (1000 * 60 * 60 * 24 * DAYS_OLD));
-
- let allPingsInfo = yield TelemetryArchive.promiseArchivedPingList();
-
- let recentPingsInfo = allPingsInfo.filter(ping => {
- let pingDate = new Date(ping.timestampCreated);
- return pingDate > thresholdDate;
- });
-
- let pingList = [];
-
- for (let pingInfo of recentPingsInfo) {
- pingList.push(yield TelemetryArchive.promiseArchivedPingById(pingInfo.id));
- }
-
- pingList.push(TelemetryController.getCurrentPingData(/* subsession = */ true));
-
- let totalSessionTime = 0;
- let totalSpinnerTime = 0;
-
- for (let ping of pingList) {
- try {
- if (ping.type != "main") {
- continue;
- }
-
- if (!ping.environment.settings.e10sEnabled) {
- continue;
- }
-
- totalSessionTime = ping.payload.info.subsessionLength;
-
- if (!(LONG_SPINNER_HISTOGRAM in ping.payload.histograms)) {
- // The Histogram might not be defined in this ping if no data was recorded for it.
- // In this case, we still add the session length because that was a valid session
- // without a long spinner.
- continue;
- }
-
- let histogram = ping.payload.histograms[LONG_SPINNER_HISTOGRAM];
-
- for (let spinnerTime of Object.keys(histogram.values)) {
- // Only consider spinners that took more than 2 seconds.
- // Note: the first bucket size that fits this criteria is
- // 2297ms. And the largest bucket is 64000ms, meaning that
- // any pause larger than that is only counted as a 64s pause.
- // For reference, the bucket sizes are:
- // 0, 1000, 2297, 5277, 12124, 27856, 64000
- if (spinnerTime >= 2000) {
- totalSpinnerTime += spinnerTime * histogram.values[spinnerTime];
- }
- }
- } catch (e) { /* just in case there's a malformed ping, ignore it silently */ }
- }
-
- totalSpinnerTime /= 1000; // session time is in seconds, but spinner time in ms
-
- const ACCEPTABLE_THRESHOLD = 20 / 3600; // 20 seconds per hour, per bug 1301131
-
- if ((totalSpinnerTime / totalSessionTime) > ACCEPTABLE_THRESHOLD) {
- Preferences.set(PREF_DISABLED_FOR_SPINNERS, true);
- } else {
- Preferences.reset(PREF_DISABLED_FOR_SPINNERS);
- }
-});
-
-function setUpSpinnerCheck() {
- let {setTimeout, setInterval} = Cu.import("resource://gre/modules/Timer.jsm");
-
- // Perform an initial check after 5min (which should give good clearance from
- // the startup process), and then a subsequent check every hour.
- setTimeout(performLongSpinnerCheck, 1000 * 60 * 5);
- setInterval(performLongSpinnerCheck, 1000 * 60 * 60);
-}
--- a/browser/extensions/e10srollout/install.rdf.in
+++ b/browser/extensions/e10srollout/install.rdf.in
@@ -5,17 +5,17 @@
#filter substitution
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>e10srollout@mozilla.org</em:id>
- <em:version>1.4</em:version>
+ <em:version>1.5</em:version>
<em:type>2</em:type>
<em:bootstrap>true</em:bootstrap>
<em:multiprocessCompatible>true</em:multiprocessCompatible>
<!-- Target Application this theme can install into,
with minimum and maximum supported versions. -->
<em:targetApplication>
<Description>
--- a/dom/html/HTMLAnchorElement.cpp
+++ b/dom/html/HTMLAnchorElement.cpp
@@ -342,20 +342,21 @@ HTMLAnchorElement::SetText(const nsAStri
}
NS_IMETHODIMP
HTMLAnchorElement::ToString(nsAString& aSource)
{
return GetHref(aSource);
}
-NS_IMETHODIMP
+NS_IMETHODIMP
HTMLAnchorElement::GetPing(nsAString& aValue)
{
- return GetURIListAttr(nsGkAtoms::ping, aValue);
+ GetAttr(kNameSpaceID_None, nsGkAtoms::ping, aValue);
+ return NS_OK;
}
NS_IMETHODIMP
HTMLAnchorElement::SetPing(const nsAString& aValue)
{
return SetAttr(kNameSpaceID_None, nsGkAtoms::ping, aValue, true);
}
--- a/dom/html/HTMLAreaElement.cpp
+++ b/dom/html/HTMLAreaElement.cpp
@@ -216,20 +216,21 @@ IMPL_URI_PART(Hash)
#undef IMPL_URI_PART
NS_IMETHODIMP
HTMLAreaElement::ToString(nsAString& aSource)
{
return GetHref(aSource);
}
-NS_IMETHODIMP
+NS_IMETHODIMP
HTMLAreaElement::GetPing(nsAString& aValue)
{
- return GetURIListAttr(nsGkAtoms::ping, aValue);
+ GetAttr(kNameSpaceID_None, nsGkAtoms::ping, aValue);
+ return NS_OK;
}
NS_IMETHODIMP
HTMLAreaElement::SetPing(const nsAString& aValue)
{
return SetAttr(kNameSpaceID_None, nsGkAtoms::ping, aValue, true);
}
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -1674,78 +1674,16 @@ nsGenericHTMLElement::GetURIAttr(nsIAtom
/* static */ bool
nsGenericHTMLElement::IsScrollGrabAllowed(JSContext*, JSObject*)
{
// Only allow scroll grabbing in chrome
nsIPrincipal* prin = nsContentUtils::SubjectPrincipal();
return nsContentUtils::IsSystemPrincipal(prin);
}
-nsresult
-nsGenericHTMLElement::GetURIListAttr(nsIAtom* aAttr, nsAString& aResult)
-{
- aResult.Truncate();
-
- nsAutoString value;
- if (!GetAttr(kNameSpaceID_None, aAttr, value))
- return NS_OK;
-
- nsIDocument* doc = OwnerDoc();
- nsCOMPtr<nsIURI> baseURI = GetBaseURI();
-
- nsString::const_iterator end;
- value.EndReading(end);
-
- nsAString::const_iterator iter;
- value.BeginReading(iter);
-
- while (iter != end) {
- while (*iter == ' ' && iter != end) {
- ++iter;
- }
-
- if (iter == end) {
- break;
- }
-
- nsAString::const_iterator start = iter;
-
- while (iter != end && *iter != ' ') {
- ++iter;
- }
-
- if (!aResult.IsEmpty()) {
- aResult.Append(NS_LITERAL_STRING(" "));
- }
-
- const nsSubstring& uriPart = Substring(start, iter);
- nsCOMPtr<nsIURI> attrURI;
- nsresult rv =
- nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(attrURI),
- uriPart, doc, baseURI);
- if (NS_FAILED(rv)) {
- aResult.Append(uriPart);
- continue;
- }
-
- MOZ_ASSERT(attrURI);
-
- nsAutoCString spec;
- rv = attrURI->GetSpec(spec);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- aResult.Append(uriPart);
- continue;
- }
-
- AppendUTF8toUTF16(spec, aResult);
- }
-
- return NS_OK;
-}
-
HTMLMenuElement*
nsGenericHTMLElement::GetContextMenu() const
{
nsAutoString value;
GetHTMLAttr(nsGkAtoms::contextmenu, value);
if (!value.IsEmpty()) {
//XXXsmaug How should this work in Shadow DOM?
nsIDocument* doc = GetUncomposedDoc();
--- a/dom/html/nsGenericHTMLElement.h
+++ b/dom/html/nsGenericHTMLElement.h
@@ -1058,30 +1058,16 @@ protected:
{
nsAutoString value;
value.AppendFloat(aValue);
SetHTMLAttr(aAttr, value, aRv);
}
/**
- * This method works like GetURIAttr, except that it supports multiple
- * URIs separated by whitespace (one or more U+0020 SPACE characters).
- *
- * Gets the absolute URI values of an attribute, by resolving any relative
- * URIs in the attribute against the baseuri of the element. If a substring
- * isn't a relative URI, the substring is returned as is. Only works for
- * attributes in null namespace.
- *
- * @param aAttr name of attribute.
- * @param aResult result value [out]
- */
- nsresult GetURIListAttr(nsIAtom* aAttr, nsAString& aResult);
-
- /**
* Locates the nsIEditor associated with this node. In general this is
* equivalent to GetEditorInternal(), but for designmode or contenteditable,
* this may need to get an editor that's not actually on this element's
* associated TextControlFrame. This is used by the spellchecking routines
* to get the editor affected by changing the spellcheck attribute on this
* node.
*/
virtual already_AddRefed<nsIEditor> GetAssociatedEditor();
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5239,19 +5239,19 @@ ContentParent::ForceTabPaint(TabParent*
}
ProcessHangMonitor::ForcePaint(mHangMonitorActor, aTabParent, aLayerObserverEpoch);
}
bool
ContentParent::RecvAccumulateChildHistogram(
InfallibleTArray<Accumulation>&& aAccumulations)
{
- Telemetry::AccumulateChild(aAccumulations);
+ Telemetry::AccumulateChild(GeckoProcessType_Content, aAccumulations);
return true;
}
bool
ContentParent::RecvAccumulateChildKeyedHistogram(
InfallibleTArray<KeyedAccumulation>&& aAccumulations)
{
- Telemetry::AccumulateChildKeyed(aAccumulations);
+ Telemetry::AccumulateChildKeyed(GeckoProcessType_Content, aAccumulations);
return true;
}
--- a/gfx/ipc/GPUChild.cpp
+++ b/gfx/ipc/GPUChild.cpp
@@ -132,16 +132,30 @@ GPUChild::RecvNotifyUiObservers(const ns
nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
MOZ_ASSERT(obsSvc);
if (obsSvc) {
obsSvc->NotifyObservers(nullptr, aTopic.get(), nullptr);
}
return true;
}
+bool
+GPUChild::RecvAccumulateChildHistogram(InfallibleTArray<Accumulation>&& aAccumulations)
+{
+ Telemetry::AccumulateChild(GeckoProcessType_GPU, aAccumulations);
+ return true;
+}
+
+bool
+GPUChild::RecvAccumulateChildKeyedHistogram(InfallibleTArray<KeyedAccumulation>&& aAccumulations)
+{
+ Telemetry::AccumulateChildKeyed(GeckoProcessType_GPU, aAccumulations);
+ return true;
+}
+
void
GPUChild::ActorDestroy(ActorDestroyReason aWhy)
{
if (aWhy == AbnormalShutdown) {
#ifdef MOZ_CRASHREPORTER
if (mCrashReporter) {
mCrashReporter->GenerateCrashReport(OtherPid());
mCrashReporter = nullptr;
--- a/gfx/ipc/GPUChild.h
+++ b/gfx/ipc/GPUChild.h
@@ -33,16 +33,18 @@ public:
// gfxVarReceiver overrides.
void OnVarChanged(const GfxVarUpdate& aVar) override;
// PGPUChild overrides.
bool RecvInitComplete(const GPUDeviceData& aData) override;
bool RecvReportCheckerboard(const uint32_t& aSeverity, const nsCString& aLog) override;
bool RecvInitCrashReporter(Shmem&& shmem) override;
+ bool RecvAccumulateChildHistogram(InfallibleTArray<Accumulation>&& aAccumulations) override;
+ bool RecvAccumulateChildKeyedHistogram(InfallibleTArray<KeyedAccumulation>&& aAccumulations) override;
void ActorDestroy(ActorDestroyReason aWhy) override;
bool RecvGraphicsError(const nsCString& aError) override;
bool RecvNotifyUiObservers(const nsCString& aTopic) override;
static void Destroy(UniquePtr<GPUChild>&& aChild);
private:
GPUProcessHost* mHost;
--- a/gfx/ipc/GPUParent.cpp
+++ b/gfx/ipc/GPUParent.cpp
@@ -92,19 +92,21 @@ GPUParent::Init(base::ProcessId aParentP
gfxVars::Initialize();
gfxPlatform::InitNullMetadata();
// Ensure our Factory is initialised, mainly for gfx logging to work.
gfxPlatform::InitMoz2DLogging();
#if defined(XP_WIN)
DeviceManagerDx::Init();
DeviceManagerD3D9::Init();
#endif
+
if (NS_FAILED(NS_InitMinimalXPCOM())) {
return false;
}
+
CompositorThreadHolder::Start();
APZThreadUtils::SetControllerThread(CompositorThreadHolder::Loop());
APZCTreeManager::InitializeGlobalState();
VRManager::ManagerInit();
LayerTreeOwnerTracker::Initialize();
mozilla::ipc::SetThisProcessName("GPU Process");
#ifdef XP_WIN
wmf::MFStartup();
--- a/gfx/ipc/GPUProcessManager.h
+++ b/gfx/ipc/GPUProcessManager.h
@@ -132,16 +132,21 @@ public:
// true if the message was sent, false if not.
bool NotifyGpuObservers(const char* aTopic);
// Returns access to the PGPU protocol if a GPU process is present.
GPUChild* GetGPUChild() {
return mGPUChild;
}
+ // Returns whether or not a GPU process was ever launched.
+ bool AttemptedGPUProcess() const {
+ return mNumProcessAttempts > 0;
+ }
+
private:
// Called from our xpcom-shutdown observer.
void OnXPCOMShutdown();
bool CreateContentCompositorBridge(base::ProcessId aOtherProcess,
ipc::Endpoint<PCompositorBridgeChild>* aOutEndpoint);
bool CreateContentImageBridge(base::ProcessId aOtherProcess,
ipc::Endpoint<PImageBridgeChild>* aOutEndpoint);
--- a/gfx/ipc/PGPU.ipdl
+++ b/gfx/ipc/PGPU.ipdl
@@ -9,16 +9,18 @@ include protocol PImageBridge;
include protocol PVRManager;
include protocol PVsyncBridge;
include protocol PVideoDecoderManager;
using base::ProcessId from "base/process.h";
using mozilla::TimeDuration from "mozilla/TimeStamp.h";
using mozilla::CSSToLayoutDeviceScale from "Units.h";
using mozilla::gfx::IntSize from "mozilla/gfx/2D.h";
+using mozilla::Telemetry::Accumulation from "mozilla/TelemetryComms.h";
+using mozilla::Telemetry::KeyedAccumulation from "mozilla/TelemetryComms.h";
namespace mozilla {
namespace gfx {
union GfxPrefValue {
bool;
int32_t;
uint32_t;
@@ -83,12 +85,16 @@ child:
// Graphics errors, analogous to PContent::GraphicsError
async GraphicsError(nsCString aError);
async InitCrashReporter(Shmem shmem);
// Have a message be broadcasted to the UI process by the UI process
// observer service.
async NotifyUiObservers(nsCString aTopic);
+
+ // Messages for reporting telemetry to the UI process.
+ async AccumulateChildHistogram(Accumulation[] accumulations);
+ async AccumulateChildKeyedHistogram(KeyedAccumulation[] accumulations);
};
} // namespace gfx
} // namespace mozilla
--- a/ipc/glue/BackgroundImpl.cpp
+++ b/ipc/glue/BackgroundImpl.cpp
@@ -1732,20 +1732,16 @@ ChildImpl::CloseForCurrentThread()
return;
}
#ifdef DEBUG
MOZ_ASSERT(!threadLocalInfo->mClosed);
threadLocalInfo->mClosed = true;
#endif
- if (threadLocalInfo->mActor) {
- threadLocalInfo->mActor->FlushPendingInterruptQueue();
- }
-
// Clearing the thread local will synchronously close the actor.
DebugOnly<PRStatus> status = PR_SetThreadPrivate(sThreadLocalIndex, nullptr);
MOZ_ASSERT(status == PR_SUCCESS);
}
// static
BackgroundChildImpl::ThreadLocal*
ChildImpl::GetThreadLocalForCurrentThread()
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -484,41 +484,34 @@ MessageChannel::MessageChannel(MessageLi
mInTimeoutSecondHalf(false),
mNextSeqno(0),
mLastSendError(SyncSendError::SendSuccess),
mDispatchingAsyncMessage(false),
mDispatchingAsyncMessageNestedLevel(0),
mTransactionStack(nullptr),
mTimedOutMessageSeqno(0),
mTimedOutMessageNestedLevel(0),
-#if defined(MOZ_CRASHREPORTER) && defined(OS_WIN)
- mPending(AnnotateAllocator<Message>(*this)),
-#endif
- mRemoteStackDepthGuess(false),
+ mRemoteStackDepthGuess(0),
mSawInterruptOutMsg(false),
mIsWaitingForIncoming(false),
mAbortOnError(false),
mNotifiedChannelDone(false),
mFlags(REQUIRE_DEFAULT),
mPeerPidSet(false),
mPeerPid(-1)
{
MOZ_COUNT_CTOR(ipc::MessageChannel);
#ifdef OS_WIN
mTopFrame = nullptr;
mIsSyncWaitingOnNonMainThread = false;
#endif
- RefPtr<CancelableRunnable> runnable =
- NewNonOwningCancelableRunnableMethod(this, &MessageChannel::OnMaybeDequeueOne);
- mDequeueOneTask = new RefCountedTask(runnable.forget());
-
- runnable = NewNonOwningCancelableRunnableMethod(this, &MessageChannel::DispatchOnChannelConnected);
- mOnChannelConnectedTask = new RefCountedTask(runnable.forget());
+ mOnChannelConnectedTask =
+ NewNonOwningCancelableRunnableMethod(this, &MessageChannel::DispatchOnChannelConnected);
#ifdef OS_WIN
mEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
MOZ_RELEASE_ASSERT(mEvent, "CreateEvent failed! Nothing is going to work!");
#endif
}
MessageChannel::~MessageChannel()
@@ -630,31 +623,33 @@ MessageChannel::Clear()
//
// In practice, mListener owns the channel, so the channel gets deleted
// before mListener. But just to be safe, mListener is a weak pointer.
if (gParentProcessBlocker == this) {
gParentProcessBlocker = nullptr;
}
- mDequeueOneTask->Cancel();
-
mWorkerLoop = nullptr;
delete mLink;
mLink = nullptr;
mOnChannelConnectedTask->Cancel();
if (mChannelErrorTask) {
mChannelErrorTask->Cancel();
mChannelErrorTask = nullptr;
}
// Free up any memory used by pending messages.
+ for (RefPtr<MessageTask> task : mPending) {
+ task->Clear();
+ }
mPending.clear();
+
mOutOfTurnReplies.clear();
while (!mDeferred.empty()) {
mDeferred.pop();
}
}
bool
MessageChannel::Open(Transport* aTransport, MessageLoop* aIOLoop, Side aSide)
@@ -874,29 +869,16 @@ MessageChannel::ShouldDeferMessage(const
//
// Deferring in the parent only sort of breaks message ordering. When the
// child's message comes in, we can pretend the child hasn't quite
// finished sending it yet. Since the message is sync, we know that the
// child hasn't moved on yet.
return mSide == ParentSide && aMsg.transaction_id() != CurrentNestedInsideSyncTransaction();
}
-// Predicate that is true for messages that should be consolidated if 'compress' is set.
-class MatchingKinds {
- typedef IPC::Message Message;
- Message::msgid_t mType;
- int32_t mRoutingId;
-public:
- MatchingKinds(Message::msgid_t aType, int32_t aRoutingId) :
- mType(aType), mRoutingId(aRoutingId) {}
- bool operator()(const Message &msg) {
- return msg.type() == mType && msg.routing_id() == mRoutingId;
- }
-};
-
void
MessageChannel::OnMessageReceivedFromLink(Message&& aMsg)
{
AssertLinkThread();
mMonitor->AssertCurrentThreadOwns();
if (MaybeInterceptSpecialIOMessage(aMsg))
return;
@@ -920,59 +902,66 @@ MessageChannel::OnMessageReceivedFromLin
NotifyWorkerThread();
return;
}
// Nested messages cannot be compressed.
MOZ_RELEASE_ASSERT(aMsg.compress_type() == IPC::Message::COMPRESSION_NONE ||
aMsg.nested_level() == IPC::Message::NOT_NESTED);
- bool compress = false;
+ bool reuseTask = false;
if (aMsg.compress_type() == IPC::Message::COMPRESSION_ENABLED) {
- compress = (!mPending.empty() &&
- mPending.back().type() == aMsg.type() &&
- mPending.back().routing_id() == aMsg.routing_id());
+ bool compress = (!mPending.isEmpty() &&
+ mPending.getLast()->Msg().type() == aMsg.type() &&
+ mPending.getLast()->Msg().routing_id() == aMsg.routing_id());
if (compress) {
// This message type has compression enabled, and the back of the
// queue was the same message type and routed to the same destination.
// Replace it with the newer message.
- MOZ_RELEASE_ASSERT(mPending.back().compress_type() ==
- IPC::Message::COMPRESSION_ENABLED);
- mPending.pop_back();
+ MOZ_RELEASE_ASSERT(mPending.getLast()->Msg().compress_type() ==
+ IPC::Message::COMPRESSION_ENABLED);
+ mPending.getLast()->Msg() = Move(aMsg);
+
+ reuseTask = true;
}
- } else if (aMsg.compress_type() == IPC::Message::COMPRESSION_ALL) {
- // Check the message queue for another message with this type/destination.
- auto it = std::find_if(mPending.rbegin(), mPending.rend(),
- MatchingKinds(aMsg.type(), aMsg.routing_id()));
- if (it != mPending.rend()) {
- // This message type has compression enabled, and the queue holds
- // a message with the same message type and routed to the same destination.
- // Erase it. Note that, since we always compress these redundancies, There Can
- // Be Only One.
- compress = true;
- MOZ_RELEASE_ASSERT((*it).compress_type() == IPC::Message::COMPRESSION_ALL);
- mPending.erase((++it).base());
+ } else if (aMsg.compress_type() == IPC::Message::COMPRESSION_ALL && !mPending.isEmpty()) {
+ for (RefPtr<MessageTask> p = mPending.getLast(); p; p = p->getPrevious()) {
+ if (p->Msg().type() == aMsg.type() &&
+ p->Msg().routing_id() == aMsg.routing_id())
+ {
+ // This message type has compression enabled, and the queue
+ // holds a message with the same message type and routed to the
+ // same destination. Erase it. Note that, since we always
+ // compress these redundancies, There Can Be Only One.
+ MOZ_RELEASE_ASSERT(p->Msg().compress_type() == IPC::Message::COMPRESSION_ALL);
+ p->remove();
+ break;
+ }
}
}
bool wakeUpSyncSend = AwaitingSyncReply() && !ShouldDeferMessage(aMsg);
bool shouldWakeUp = AwaitingInterruptReply() ||
wakeUpSyncSend ||
AwaitingIncomingMessage();
- // Although we usually don't need to post an OnMaybeDequeueOne task if
+ // Although we usually don't need to post a message task if
// shouldWakeUp is true, it's easier to post anyway than to have to
// guarantee that every Send call processes everything it's supposed to
// before returning.
bool shouldPostTask = !shouldWakeUp || wakeUpSyncSend;
IPC_LOG("Receive on link thread; seqno=%d, xid=%d, shouldWakeUp=%d",
aMsg.seqno(), aMsg.transaction_id(), shouldWakeUp);
+ if (reuseTask) {
+ return;
+ }
+
// There are three cases we're concerned about, relating to the state of the
// main thread:
//
// (1) We are waiting on a sync reply - main thread is blocked on the
// IPC monitor.
// - If the message is NESTED_INSIDE_SYNC, we wake up the main thread to
// deliver the message depending on ShouldDeferMessage. Otherwise, we
// leave it in the mPending queue, posting a task to the main event
@@ -985,83 +974,83 @@ MessageChannel::OnMessageReceivedFromLin
//
// (3) We are not waiting on a reply.
// - We post a task to the main event loop.
//
// Note that, we may notify the main thread even though the monitor is not
// blocked. This is okay, since we always check for pending events before
// blocking again.
- mPending.push_back(Move(aMsg));
+ RefPtr<MessageTask> task = new MessageTask(this, Move(aMsg));
+ mPending.insertBack(task);
if (shouldWakeUp) {
NotifyWorkerThread();
}
if (shouldPostTask) {
- if (!compress) {
- // If we compressed away the previous message, we'll re-use
- // its pending task.
- RefPtr<DequeueTask> task = new DequeueTask(mDequeueOneTask);
- mWorkerLoop->PostTask(task.forget());
- }
+ task->Post();
}
}
void
MessageChannel::PeekMessages(mozilla::function<bool(const Message& aMsg)> aInvoke)
{
+ // FIXME: We shouldn't be holding the lock for aInvoke!
MonitorAutoLock lock(*mMonitor);
- for (MessageQueue::iterator it = mPending.begin(); it != mPending.end(); it++) {
- Message &msg = *it;
+ for (RefPtr<MessageTask> it : mPending) {
+ const Message &msg = it->Msg();
if (!aInvoke(msg)) {
break;
}
}
}
void
MessageChannel::ProcessPendingRequests(AutoEnterTransaction& aTransaction)
{
+ mMonitor->AssertCurrentThreadOwns();
+
IPC_LOG("ProcessPendingRequests for seqno=%d, xid=%d",
aTransaction.SequenceNumber(), aTransaction.TransactionID());
// Loop until there aren't any more nested messages to process.
for (;;) {
// If we canceled during ProcessPendingRequest, then we need to leave
// immediately because the results of ShouldDeferMessage will be
// operating with weird state (as if no Send is in progress). That could
// cause even NOT_NESTED sync messages to be processed (but not
// NOT_NESTED async messages), which would break message ordering.
if (aTransaction.IsCanceled()) {
return;
}
mozilla::Vector<Message> toProcess;
- for (MessageQueue::iterator it = mPending.begin(); it != mPending.end(); ) {
- Message &msg = *it;
+ for (RefPtr<MessageTask> p = mPending.getFirst(); p; ) {
+ Message &msg = p->Msg();
MOZ_RELEASE_ASSERT(!aTransaction.IsCanceled(),
"Calling ShouldDeferMessage when cancelled");
bool defer = ShouldDeferMessage(msg);
// Only log the interesting messages.
if (msg.is_sync() || msg.nested_level() == IPC::Message::NESTED_INSIDE_CPOW) {
IPC_LOG("ShouldDeferMessage(seqno=%d) = %d", msg.seqno(), defer);
}
if (!defer) {
if (!toProcess.append(Move(msg)))
MOZ_CRASH();
- it = mPending.erase(it);
+
+ p = p->removeAndGetNext();
continue;
}
- it++;
+ p = p->getNext();
}
if (toProcess.empty()) {
break;
}
// Processing these messages could result in more messages, so we
// loop around to check for more afterwards.
@@ -1353,19 +1342,19 @@ MessageChannel::Call(Message* aMsg, Mess
Message recvd;
MessageMap::iterator it;
if ((it = mOutOfTurnReplies.find(mInterruptStack.top().seqno()))
!= mOutOfTurnReplies.end())
{
recvd = Move(it->second);
mOutOfTurnReplies.erase(it);
- } else if (!mPending.empty()) {
- recvd = Move(mPending.front());
- mPending.pop_front();
+ } else if (!mPending.isEmpty()) {
+ RefPtr<MessageTask> task = mPending.popFirst();
+ recvd = Move(task->Msg());
} else {
// because of subtleties with nested event loops, it's possible
// that we got here and nothing happened. or, we might have a
// deferred in-call that needs to be processed. either way, we
// won't break the inner while loop again until something new
// happens.
continue;
}
@@ -1444,47 +1433,48 @@ MessageChannel::Call(Message* aMsg, Mess
bool
MessageChannel::WaitForIncomingMessage()
{
#ifdef OS_WIN
SyncStackFrame frame(this, true);
NeuteredWindowRegion neuteredRgn(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION);
#endif
- { // Scope for lock
- MonitorAutoLock lock(*mMonitor);
- AutoEnterWaitForIncoming waitingForIncoming(*this);
- if (mChannelState != ChannelConnected) {
- return false;
- }
- if (!HasPendingEvents()) {
- return WaitForInterruptNotify();
- }
+ MonitorAutoLock lock(*mMonitor);
+ AutoEnterWaitForIncoming waitingForIncoming(*this);
+ if (mChannelState != ChannelConnected) {
+ return false;
}
-
- return OnMaybeDequeueOne();
+ if (!HasPendingEvents()) {
+ return WaitForInterruptNotify();
+ }
+
+ MOZ_RELEASE_ASSERT(!mPending.isEmpty());
+ RefPtr<MessageTask> task = mPending.getFirst();
+ RunMessage(*task);
+ return true;
}
bool
MessageChannel::HasPendingEvents()
{
AssertWorkerThread();
mMonitor->AssertCurrentThreadOwns();
- return Connected() && !mPending.empty();
+ return Connected() && !mPending.isEmpty();
}
bool
MessageChannel::InterruptEventOccurred()
{
AssertWorkerThread();
mMonitor->AssertCurrentThreadOwns();
IPC_ASSERT(InterruptStackDepth() > 0, "not in wait loop");
return (!Connected() ||
- !mPending.empty() ||
+ !mPending.isEmpty() ||
(!mOutOfTurnReplies.empty() &&
mOutOfTurnReplies.find(mInterruptStack.top().seqno()) !=
mOutOfTurnReplies.end()));
}
bool
MessageChannel::ProcessPendingRequest(Message &&aUrgent)
{
@@ -1498,95 +1488,163 @@ MessageChannel::ProcessPendingRequest(Me
ReportConnectionError("MessageChannel::ProcessPendingRequest");
return false;
}
return true;
}
bool
-MessageChannel::DequeueOne(Message *recvd)
+MessageChannel::ShouldRunMessage(const Message& aMsg)
{
- AssertWorkerThread();
- mMonitor->AssertCurrentThreadOwns();
-
- if (!Connected()) {
- ReportConnectionError("OnMaybeDequeueOne");
- return false;
+ if (!mTimedOutMessageSeqno) {
+ return true;
}
- if (!mDeferred.empty())
- MaybeUndeferIncall();
-
// If we've timed out a message and we're awaiting the reply to the timed
// out message, we have to be careful what messages we process. Here's what
// can go wrong:
// 1. child sends a NOT_NESTED sync message S
// 2. parent sends a NESTED_INSIDE_SYNC sync message H at the same time
// 3. parent times out H
// 4. child starts processing H and sends a NESTED_INSIDE_SYNC message H' nested
// within the same transaction
// 5. parent dispatches S and sends reply
// 6. child asserts because it instead expected a reply to H'.
//
// To solve this, we refuse to process S in the parent until we get a reply
// to H. More generally, let the timed out message be M. We don't process a
// message unless the child would need the response to that message in order
// to process M. Those messages are the ones that have a higher nested level
// than M or that are part of the same transaction as M.
- if (mTimedOutMessageSeqno) {
- for (MessageQueue::iterator it = mPending.begin(); it != mPending.end(); it++) {
- Message &msg = *it;
- if (msg.nested_level() > mTimedOutMessageNestedLevel ||
- (msg.nested_level() == mTimedOutMessageNestedLevel
- && msg.transaction_id() == mTimedOutMessageSeqno))
- {
- *recvd = Move(msg);
- mPending.erase(it);
- return true;
- }
- }
+ if (aMsg.nested_level() < mTimedOutMessageNestedLevel ||
+ (aMsg.nested_level() == mTimedOutMessageNestedLevel
+ && aMsg.transaction_id() != mTimedOutMessageSeqno))
+ {
return false;
}
- if (mPending.empty())
- return false;
-
- *recvd = Move(mPending.front());
- mPending.pop_front();
- return true;
-}
-
-bool
-MessageChannel::OnMaybeDequeueOne()
-{
- AssertWorkerThread();
- mMonitor->AssertNotCurrentThreadOwns();
-
- Message recvd;
-
- MonitorAutoLock lock(*mMonitor);
- if (!DequeueOne(&recvd))
- return false;
-
- if (IsOnCxxStack() && recvd.is_interrupt() && recvd.is_reply()) {
- // We probably just received a reply in a nested loop for an
- // Interrupt call sent before entering that loop.
- mOutOfTurnReplies[recvd.seqno()] = Move(recvd);
- return false;
- }
-
- DispatchMessage(Move(recvd));
-
return true;
}
void
+MessageChannel::RunMessage(MessageTask& aTask)
+{
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
+ Message& msg = aTask.Msg();
+
+ if (!Connected()) {
+ ReportConnectionError("RunMessage");
+ return;
+ }
+
+ // Check that we're going to run the first message that's valid to run.
+#ifdef DEBUG
+ for (RefPtr<MessageTask> task : mPending) {
+ if (task == &aTask) {
+ break;
+ }
+ MOZ_ASSERT(!ShouldRunMessage(task->Msg()));
+ }
+#endif
+
+ if (!mDeferred.empty()) {
+ MaybeUndeferIncall();
+ }
+
+ if (!ShouldRunMessage(msg)) {
+ return;
+ }
+
+ MOZ_RELEASE_ASSERT(aTask.isInList());
+ aTask.remove();
+
+ if (IsOnCxxStack() && msg.is_interrupt() && msg.is_reply()) {
+ // We probably just received a reply in a nested loop for an
+ // Interrupt call sent before entering that loop.
+ mOutOfTurnReplies[msg.seqno()] = Move(msg);
+ return;
+ }
+
+ DispatchMessage(Move(msg));
+}
+
+nsresult
+MessageChannel::MessageTask::Run()
+{
+ if (!mChannel) {
+ return NS_OK;
+ }
+
+ mChannel->AssertWorkerThread();
+ mChannel->mMonitor->AssertNotCurrentThreadOwns();
+
+ MonitorAutoLock lock(*mChannel->mMonitor);
+
+ // In case we choose not to run this message, we may need to be able to Post
+ // it again.
+ mScheduled = false;
+
+ if (!isInList()) {
+ return NS_OK;
+ }
+
+ mChannel->RunMessage(*this);
+ return NS_OK;
+}
+
+// Warning: This method removes the receiver from whatever list it might be in.
+nsresult
+MessageChannel::MessageTask::Cancel()
+{
+ if (!mChannel) {
+ return NS_OK;
+ }
+
+ mChannel->AssertWorkerThread();
+ mChannel->mMonitor->AssertNotCurrentThreadOwns();
+
+ MonitorAutoLock lock(*mChannel->mMonitor);
+
+ if (!isInList()) {
+ return NS_OK;
+ }
+ remove();
+
+ return NS_OK;
+}
+
+void
+MessageChannel::MessageTask::Post()
+{
+ MOZ_RELEASE_ASSERT(!mScheduled);
+ MOZ_RELEASE_ASSERT(isInList());
+
+ mScheduled = true;
+
+ RefPtr<MessageTask> self = this;
+ mChannel->mWorkerLoop->PostTask(self.forget());
+}
+
+void
+MessageChannel::MessageTask::Clear()
+{
+ mChannel->AssertWorkerThread();
+
+ mChannel = nullptr;
+}
+
+void
MessageChannel::DispatchMessage(Message &&aMsg)
{
+ AssertWorkerThread();
+ mMonitor->AssertCurrentThreadOwns();
+
Maybe<AutoNoJSAPI> nojsapi;
if (ScriptSettingsInitialized() && NS_IsMainThread())
nojsapi.emplace();
nsAutoPtr<Message> reply;
IPC_LOG("DispatchMessage: seqno=%d, xid=%d", aMsg.seqno(), aMsg.transaction_id());
@@ -1775,39 +1833,19 @@ MessageChannel::MaybeUndeferIncall()
Message call(Move(mDeferred.top()));
mDeferred.pop();
// fix up fudge factor we added to account for race
IPC_ASSERT(0 < mRemoteStackDepthGuess, "fatal logic error");
--mRemoteStackDepthGuess;
MOZ_RELEASE_ASSERT(call.nested_level() == IPC::Message::NOT_NESTED);
- mPending.push_back(Move(call));
-}
-
-void
-MessageChannel::FlushPendingInterruptQueue()
-{
- AssertWorkerThread();
- mMonitor->AssertNotCurrentThreadOwns();
-
- {
- MonitorAutoLock lock(*mMonitor);
-
- if (mDeferred.empty()) {
- if (mPending.empty())
- return;
-
- const Message& last = mPending.back();
- if (!last.is_interrupt() || last.is_reply())
- return;
- }
- }
-
- while (OnMaybeDequeueOne());
+ RefPtr<MessageTask> task = new MessageTask(this, Move(call));
+ mPending.insertBack(task);
+ task->Post();
}
void
MessageChannel::ExitedCxxStack()
{
mListener->OnExitedCxxStack();
if (mSawInterruptOutMsg) {
MonitorAutoLock lock(*mMonitor);
@@ -1820,28 +1858,20 @@ MessageChannel::ExitedCxxStack()
void
MessageChannel::EnqueuePendingMessages()
{
AssertWorkerThread();
mMonitor->AssertCurrentThreadOwns();
MaybeUndeferIncall();
- for (size_t i = 0; i < mDeferred.size(); ++i) {
- RefPtr<DequeueTask> task = new DequeueTask(mDequeueOneTask);
- mWorkerLoop->PostTask(task.forget());
- }
-
// XXX performance tuning knob: could process all or k pending
// messages here, rather than enqueuing for later processing
- for (size_t i = 0; i < mPending.size(); ++i) {
- RefPtr<DequeueTask> task = new DequeueTask(mDequeueOneTask);
- mWorkerLoop->PostTask(task.forget());
- }
+ RepostAllMessages();
}
static inline bool
IsTimeoutExpired(PRIntervalTime aStart, PRIntervalTime aTimeout)
{
return (aTimeout != PR_INTERVAL_NO_TIMEOUT) &&
(aTimeout <= (PR_IntervalNow() - aStart));
}
@@ -1938,17 +1968,17 @@ MessageChannel::SetReplyTimeoutMs(int32_
}
void
MessageChannel::OnChannelConnected(int32_t peer_id)
{
MOZ_RELEASE_ASSERT(!mPeerPidSet);
mPeerPidSet = true;
mPeerPid = peer_id;
- RefPtr<DequeueTask> task = new DequeueTask(mOnChannelConnectedTask);
+ RefPtr<CancelableRunnable> task = mOnChannelConnectedTask;
mWorkerLoop->PostTask(task.forget());
}
void
MessageChannel::DispatchOnChannelConnected()
{
AssertWorkerThread();
MOZ_RELEASE_ASSERT(mPeerPidSet);
@@ -2286,26 +2316,24 @@ MessageChannel::DebugAbort(const char* f
// technically we need the mutex for this, but we're dying anyway
DumpInterruptStack(" ");
printf_stderr(" remote Interrupt stack guess: %" PRIuSIZE "\n",
mRemoteStackDepthGuess);
printf_stderr(" deferred stack size: %" PRIuSIZE "\n",
mDeferred.size());
printf_stderr(" out-of-turn Interrupt replies stack size: %" PRIuSIZE "\n",
mOutOfTurnReplies.size());
- printf_stderr(" Pending queue size: %" PRIuSIZE ", front to back:\n",
- mPending.size());
MessageQueue pending = Move(mPending);
- while (!pending.empty()) {
+ while (!pending.isEmpty()) {
printf_stderr(" [ %s%s ]\n",
- pending.front().is_interrupt() ? "intr" :
- (pending.front().is_sync() ? "sync" : "async"),
- pending.front().is_reply() ? "reply" : "");
- pending.pop_front();
+ pending.getFirst()->Msg().is_interrupt() ? "intr" :
+ (pending.getFirst()->Msg().is_sync() ? "sync" : "async"),
+ pending.getFirst()->Msg().is_reply() ? "reply" : "");
+ pending.popFirst();
}
NS_RUNTIMEABORT(why);
}
void
MessageChannel::DumpInterruptStack(const char* const pfx) const
{
@@ -2343,23 +2371,43 @@ void
MessageChannel::EndTimeout()
{
mMonitor->AssertCurrentThreadOwns();
IPC_LOG("Ending timeout of seqno=%d", mTimedOutMessageSeqno);
mTimedOutMessageSeqno = 0;
mTimedOutMessageNestedLevel = 0;
- for (size_t i = 0; i < mPending.size(); i++) {
- // There may be messages in the queue that we expected to process from
- // OnMaybeDequeueOne. But during the timeout, that function will skip
- // some messages. Now they're ready to be processed, so we enqueue more
- // tasks.
- RefPtr<DequeueTask> task = new DequeueTask(mDequeueOneTask);
- mWorkerLoop->PostTask(task.forget());
+ RepostAllMessages();
+}
+
+void
+MessageChannel::RepostAllMessages()
+{
+ bool needRepost = false;
+ for (RefPtr<MessageTask> task : mPending) {
+ if (!task->IsScheduled()) {
+ needRepost = true;
+ }
+ }
+ if (!needRepost) {
+ // If everything is already scheduled to run, do nothing.
+ return;
+ }
+
+ // In some cases we may have deferred dispatch of some messages in the
+ // queue. Now we want to run them again. However, we can't just re-post
+ // those messages since the messages after them in mPending would then be
+ // before them in the event queue. So instead we cancel everything and
+ // re-post all messages in the correct order.
+ MessageQueue queue = Move(mPending);
+ while (RefPtr<MessageTask> task = queue.popFirst()) {
+ RefPtr<MessageTask> newTask = new MessageTask(this, Move(task->Msg()));
+ mPending.insertBack(newTask);
+ newTask->Post();
}
}
void
MessageChannel::CancelTransaction(int transaction)
{
mMonitor->AssertCurrentThreadOwns();
@@ -2392,33 +2440,33 @@ MessageChannel::CancelTransaction(int tr
mTransactionStack->Cancel();
}
} else {
MOZ_RELEASE_ASSERT(mTransactionStack->TransactionID() == transaction);
mTransactionStack->Cancel();
}
bool foundSync = false;
- for (MessageQueue::iterator it = mPending.begin(); it != mPending.end(); ) {
- Message &msg = *it;
+ for (RefPtr<MessageTask> p = mPending.getFirst(); p; ) {
+ Message &msg = p->Msg();
// If there was a race between the parent and the child, then we may
// have a queued sync message. We want to drop this message from the
// queue since if will get cancelled along with the transaction being
// cancelled. This happens if the message in the queue is NESTED_INSIDE_SYNC.
if (msg.is_sync() && msg.nested_level() != IPC::Message::NOT_NESTED) {
MOZ_RELEASE_ASSERT(!foundSync);
MOZ_RELEASE_ASSERT(msg.transaction_id() != transaction);
IPC_LOG("Removing msg from queue seqno=%d xid=%d", msg.seqno(), msg.transaction_id());
foundSync = true;
- it = mPending.erase(it);
+ p = p->removeAndGetNext();
continue;
}
- it++;
+ p = p->getNext();
}
}
bool
MessageChannel::IsInTransaction() const
{
MonitorAutoLock lock(*mMonitor);
return !!mTransactionStack;
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -178,18 +178,16 @@ class MessageChannel : HasResultCodes
/**
* This function is used by hang annotation code to determine which IPDL
* actor is highest in the call stack at the time of the hang. It should
* be called from the main thread when a sync or intr message is about to
* be sent.
*/
int32_t GetTopmostMessageRoutingId() const;
- void FlushPendingInterruptQueue();
-
// Unsound_IsClosed and Unsound_NumQueuedMessages are safe to call from any
// thread, but they make no guarantees about whether you'll get an
// up-to-date value; the values are written on one thread and read without
// locking, on potentially different threads. Thus you should only use
// them when you don't particularly care about getting a recent value (e.g.
// in a memory report).
bool Unsound_IsClosed() const {
return mLink ? mLink->Unsound_IsClosed() : true;
@@ -271,20 +269,16 @@ class MessageChannel : HasResultCodes
bool HasPendingEvents();
void ProcessPendingRequests(AutoEnterTransaction& aTransaction);
bool ProcessPendingRequest(Message &&aUrgent);
void MaybeUndeferIncall();
void EnqueuePendingMessages();
- // Executed on the worker thread. Dequeues one pending message.
- bool OnMaybeDequeueOne();
- bool DequeueOne(Message *recvd);
-
// Dispatches an incoming message to its appropriate handler.
void DispatchMessage(Message &&aMsg);
// DispatchMessage will route to one of these functions depending on the
// protocol type of the message.
void DispatchSyncMessage(const Message &aMsg, Message*& aReply);
void DispatchUrgentMessage(const Message &aMsg);
void DispatchAsyncMessage(const Message &aMsg);
@@ -306,16 +300,18 @@ class MessageChannel : HasResultCodes
bool WaitResponse(bool aWaitTimedOut);
bool ShouldContinueFromTimeout();
void EndTimeout();
void CancelTransaction(int transaction);
+ void RepostAllMessages();
+
// The "remote view of stack depth" can be different than the
// actual stack depth when there are out-of-turn replies. When we
// receive one, our actual Interrupt stack depth doesn't decrease, but
// the other side (that sent the reply) thinks it has. So, the
// "view" returned here is |stackDepth| minus the number of
// out-of-turn replies.
//
// Only called from the worker thread.
@@ -451,147 +447,66 @@ class MessageChannel : HasResultCodes
// NOT our worker thread.
void AssertLinkThread() const
{
MOZ_RELEASE_ASSERT(mWorkerLoopID != MessageLoop::current()->id(),
"on worker thread but should not be!");
}
private:
-#if defined(MOZ_CRASHREPORTER) && defined(OS_WIN)
- // TODO: Remove the condition OS_WIN above once we move to GCC 5 or higher,
- // the code will be able to get compiled as std::deque will meet C++11
- // allocator requirements.
- template<class T>
- struct AnnotateAllocator
+ class MessageTask :
+ public CancelableRunnable,
+ public LinkedListElement<RefPtr<MessageTask>>
{
- typedef T value_type;
- AnnotateAllocator(MessageChannel& channel) : mChannel(channel) {}
- template<class U> AnnotateAllocator(const AnnotateAllocator<U>& other) :
- mChannel(other.mChannel) {}
- template<class U> bool operator==(const AnnotateAllocator<U>&) { return true; }
- template<class U> bool operator!=(const AnnotateAllocator<U>&) { return false; }
- T* allocate(size_t n) {
- void* p = ::operator new(n * sizeof(T), std::nothrow);
- if (!p && n) {
- // Sort the pending messages by its type, note the sorting algorithm
- // has to be in-place to avoid memory allocation.
- MessageQueue& q = mChannel.mPending;
- std::sort(q.begin(), q.end(), [](const Message& a, const Message& b) {
- return a.type() < b.type();
- });
+ public:
+ explicit MessageTask(MessageChannel* aChannel, Message&& aMessage)
+ : mChannel(aChannel), mMessage(Move(aMessage)), mScheduled(false)
+ {}
+
+ NS_IMETHOD Run() override;
+ nsresult Cancel() override;
+ void Post();
+ void Clear();
+
+ bool IsScheduled() const { return mScheduled; }
- // Iterate over the sorted queue to find the message that has the
- // highest number of count.
- const char* topName = nullptr;
- const char* curName = nullptr;
- msgid_t topType = 0, curType = 0;
- uint32_t topCount = 0, curCount = 0;
- for (MessageQueue::iterator it = q.begin(); it != q.end(); ++it) {
- Message &msg = *it;
- if (msg.type() == curType) {
- ++curCount;
- } else {
- if (curCount > topCount) {
- topName = curName;
- topType = curType;
- topCount = curCount;
- }
- curName = StringFromIPCMessageType(msg.type());
- curType = msg.type();
- curCount = 1;
- }
- }
- // In case the last type is the top one.
- if (curCount > topCount) {
- topName = curName;
- topType = curType;
- topCount = curCount;
- }
+ Message& Msg() { return mMessage; }
+ const Message& Msg() const { return mMessage; }
+
+ private:
+ MessageTask() = delete;
+ MessageTask(const MessageTask&) = delete;
- CrashReporter::AnnotatePendingIPC(q.size(), topCount, topName, topType);
+ MessageChannel* mChannel;
+ Message mMessage;
+ bool mScheduled : 1;
+ };
- mozalloc_handle_oom(n * sizeof(T));
- }
- return static_cast<T*>(p);
- }
- void deallocate(T* p, size_t n) {
- ::operator delete(p);
- }
- MessageChannel& mChannel;
- };
- typedef std::deque<Message, AnnotateAllocator<Message>> MessageQueue;
-#else
- typedef std::deque<Message> MessageQueue;
-#endif
+ bool ShouldRunMessage(const Message& aMsg);
+ void RunMessage(MessageTask& aTask);
+
+ typedef LinkedList<RefPtr<MessageTask>> MessageQueue;
typedef std::map<size_t, Message> MessageMap;
typedef IPC::Message::msgid_t msgid_t;
- // XXXkhuey this can almost certainly die.
- // All dequeuing tasks require a single point of cancellation,
- // which is handled via a reference-counted task.
- class RefCountedTask
- {
- public:
- explicit RefCountedTask(already_AddRefed<CancelableRunnable> aTask)
- : mTask(aTask)
- { }
- private:
- ~RefCountedTask() { }
- public:
- void Run() { mTask->Run(); }
- void Cancel() { mTask->Cancel(); }
-
- NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefCountedTask)
-
- private:
- RefPtr<CancelableRunnable> mTask;
- };
-
- // Wrap an existing task which can be cancelled at any time
- // without the wrapper's knowledge.
- class DequeueTask : public CancelableRunnable
- {
- public:
- explicit DequeueTask(RefCountedTask* aTask)
- : mTask(aTask)
- { }
- NS_IMETHOD Run() override {
- if (mTask) {
- mTask->Run();
- }
- return NS_OK;
- }
- nsresult Cancel() override {
- mTask = nullptr;
- return NS_OK;
- }
-
- private:
- RefPtr<RefCountedTask> mTask;
- };
-
private:
// Based on presumption the listener owns and overlives the channel,
// this is never nullified.
MessageListener* mListener;
ChannelState mChannelState;
RefPtr<RefCountedMonitor> mMonitor;
Side mSide;
MessageLink* mLink;
MessageLoop* mWorkerLoop; // thread where work is done
RefPtr<CancelableRunnable> mChannelErrorTask; // NotifyMaybeChannelError runnable
// id() of mWorkerLoop. This persists even after mWorkerLoop is cleared
// during channel shutdown.
int mWorkerLoopID;
- // A task encapsulating dequeuing one pending message.
- RefPtr<RefCountedTask> mDequeueOneTask;
-
// Timeout periods are broken up in two to prevent system suspension from
// triggering an abort. This method (called by WaitForEvent with a 'did
// timeout' flag) decides if we should wait again for half of mTimeoutMs
// or give up.
int32_t mTimeoutMs;
bool mInTimeoutSecondHalf;
// Worker-thread only; sequence numbers for messages that require
@@ -668,46 +583,44 @@ class MessageChannel : HasResultCodes
// error.
//
// A message is only timed out if it initiated a transaction. This avoids
// hitting a lot of corner cases with message nesting that we don't really
// care about.
int32_t mTimedOutMessageSeqno;
int mTimedOutMessageNestedLevel;
- // Queue of all incoming messages, except for replies to sync and urgent
- // messages, which are delivered directly to mRecvd, and any pending urgent
- // incall, which is stored in mPendingUrgentRequest.
+ // Queue of all incoming messages.
//
// If both this side and the other side are functioning correctly, the queue
// can only be in certain configurations. Let
//
// |A<| be an async in-message,
// |S<| be a sync in-message,
// |C<| be an Interrupt in-call,
// |R<| be an Interrupt reply.
//
// The queue can only match this configuration
//
- // A<* (S< | C< | R< (?{mStack.size() == 1} A<* (S< | C<)))
+ // A<* (S< | C< | R< (?{mInterruptStack.size() == 1} A<* (S< | C<)))
//
// The other side can send as many async messages |A<*| as it wants before
// sending us a blocking message.
//
// The first case is |S<|, a sync in-msg. The other side must be blocked,
// and thus can't send us any more messages until we process the sync
// in-msg.
//
// The second case is |C<|, an Interrupt in-call; the other side must be blocked.
// (There's a subtlety here: this in-call might have raced with an
// out-call, but we detect that with the mechanism below,
// |mRemoteStackDepth|, and races don't matter to the queue.)
//
// Final case, the other side replied to our most recent out-call |R<|.
- // If that was the *only* out-call on our stack, |?{mStack.size() == 1}|,
+ // If that was the *only* out-call on our stack, |?{mInterruptStack.size() == 1}|,
// then other side "finished with us," and went back to its own business.
// That business might have included sending any number of async message
// |A<*| until sending a blocking message |(S< | C<)|. If we had more than
// one Interrupt call on our stack, the other side *better* not have sent us
// another blocking message, because it's blocked on a reply from us.
//
MessageQueue mPending;
@@ -722,17 +635,17 @@ class MessageChannel : HasResultCodes
// calls. With each Interrupt out-call sent, we send along what *we* think the
// stack depth of the remote side is *before* it will receive the Interrupt call.
//
// After sending the out-call, our stack depth is "incremented" by pushing
// that pending message onto mPending.
//
// Then when processing an in-call |c|, it must be true that
//
- // mStack.size() == c.remoteDepth
+ // mInterruptStack.size() == c.remoteDepth
//
// I.e., my depth is actually the same as what the other side thought it
// was when it sent in-call |c|. If this fails to hold, we have detected
// racy Interrupt calls.
//
// We then increment mRemoteStackDepth *just before* processing the
// in-call, since we know the other side is waiting on it, and decrement
// it *just after* finishing processing that in-call, since our response
@@ -783,17 +696,17 @@ class MessageChannel : HasResultCodes
bool mNotifiedChannelDone;
// See SetChannelFlags
ChannelFlags mFlags;
// Task and state used to asynchronously notify channel has been connected
// safely. This is necessary to be able to cancel notification if we are
// closed at the same time.
- RefPtr<RefCountedTask> mOnChannelConnectedTask;
+ RefPtr<CancelableRunnable> mOnChannelConnectedTask;
bool mPeerPidSet;
int32_t mPeerPid;
};
void
CancelCPOWs();
} // namespace ipc
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -3329,24 +3329,19 @@ class _GenerateProtocolActorCode(ipdl.as
onexitedcall.addstmt(StmtReturn(ExprCall(p.exitedCallVar())))
# bool IsOnCxxStack()
onstack = MethodDefn(
MethodDecl(p.onCxxStackVar().name, ret=Type.BOOL, const=1))
onstack.addstmt(StmtReturn(ExprCall(
ExprSelect(p.channelVar(), '.', p.onCxxStackVar().name))))
- # void ProcessIncomingRacingInterruptCall
- processincoming = MethodDefn(
- MethodDecl('FlushPendingInterruptQueue', ret=Type.VOID))
- processincoming.addstmt(StmtExpr(ExprCall(ExprSelect(_actorChannel(ExprVar.THIS), '.', 'FlushPendingInterruptQueue'))))
-
self.cls.addstmts([ onentered, onexited,
onenteredcall, onexitedcall,
- onstack, processincoming, Whitespace.NL ])
+ onstack, Whitespace.NL ])
# OnChannelClose()
onclose = MethodDefn(MethodDecl('OnChannelClose'))
if ptype.isToplevel():
onclose.addstmts([
StmtExpr(ExprCall(destroysubtreevar,
args=[ _DestroyReason.NormalShutdown ])),
StmtExpr(ExprCall(deallocsubtreevar)),
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -780,17 +780,17 @@ struct JSClass {
// with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
// previously allowed, but is now an ES5 violation and thus unsupported.
//
// JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at
// the beginning of every global object's slots for use by the
// application.
#define JSCLASS_GLOBAL_APPLICATION_SLOTS 5
#define JSCLASS_GLOBAL_SLOT_COUNT \
- (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 37)
+ (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 39)
#define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \
(JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
#define JSCLASS_GLOBAL_FLAGS \
JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0)
#define JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(clasp) \
(((clasp)->flags & JSCLASS_IS_GLOBAL) \
&& JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT)
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -7063,17 +7063,17 @@ ParseFunction(ModuleValidator& m, ParseN
RootedFunction& fun = m.dummyFunction();
fun->setAtom(name);
fun->setArgCount(0);
ParseContext* outerpc = m.parser().pc;
Directives directives(outerpc);
FunctionBox* funbox = m.parser().newFunctionBox(fn, fun, directives, NotGenerator,
- /* tryAnnexB = */ false);
+ SyncFunction, /* tryAnnexB = */ false);
if (!funbox)
return false;
funbox->initWithEnclosingParseContext(outerpc, frontend::Statement);
Directives newDirectives = directives;
ParseContext funpc(&m.parser(), funbox, &newDirectives);
if (!funpc.init())
return false;
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/AsyncFunctions.js
@@ -0,0 +1,47 @@
+/* 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/. */
+
+// Called when creating async function.
+// See the comment for js::CreateAsyncFunction what unwrapped, wrapped and the
+// return value are.
+function AsyncFunction_wrap(unwrapped) {
+ var wrapper = function() {
+ // The try block is required to handle throws in default arguments properly.
+ try {
+ return AsyncFunction_start(callFunction(std_Function_apply, unwrapped, this, arguments));
+ } catch (e) {
+ var promiseCtor = GetBuiltinConstructor('Promise');
+ return callFunction(Promise_static_reject, promiseCtor, e);
+ }
+ };
+ return CreateAsyncFunction(wrapper, unwrapped);
+}
+
+function AsyncFunction_start(generator) {
+ return AsyncFunction_resume(generator, undefined, generator.next);
+}
+
+function AsyncFunction_resume(gen, v, method) {
+ var promiseCtor = GetBuiltinConstructor('Promise');
+ let result;
+ try {
+ // get back into async function, run to next await point
+ result = callFunction(method, gen, v);
+ } catch (exc) {
+ // The async function itself failed.
+ return callFunction(Promise_static_reject, promiseCtor, exc);
+ }
+
+ if (result.done)
+ return callFunction(Promise_static_resolve, promiseCtor, result.value);
+
+ // If we get here, `await` occurred. `gen` is paused at a yield point.
+ return callFunction(Promise_then,
+ callFunction(Promise_static_resolve, promiseCtor, result.value),
+ function(val) {
+ return AsyncFunction_resume(gen, val, gen.next);
+ }, function (err) {
+ return AsyncFunction_resume(gen, err, gen.throw);
+ });
+}
--- a/js/src/builtin/AtomicsObject.cpp
+++ b/js/src/builtin/AtomicsObject.cpp
@@ -961,16 +961,17 @@ js::FutexRuntime::wait(JSContext* cx, js
MOZ_ASSERT(&cx->runtime()->fx == this);
MOZ_ASSERT(cx->runtime()->fx.canWait());
MOZ_ASSERT(state_ == Idle || state_ == WaitingInterrupted);
// Disallow waiting when a runtime is processing an interrupt.
// See explanation below.
if (state_ == WaitingInterrupted) {
+ UnlockGuard<Mutex> unlock(locked);
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ATOMICS_WAIT_NOT_ALLOWED);
return false;
}
const bool isTimed = timeout.isSome();
auto finalEnd = timeout.map([](mozilla::TimeDuration& timeout) {
return mozilla::TimeStamp::Now() + timeout;
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -699,17 +699,17 @@ ObjectDefineProperties(JSContext* cx, Ha
// Step 5.a.
if (!GetOwnPropertyDescriptor(cx, props, nextKey, &desc))
return false;
// Step 5.b.
if (desc.object() && desc.enumerable()) {
if (!GetProperty(cx, props, props, nextKey, &descObj) ||
- !ToPropertyDescriptor(cx, descObj, false, &desc) ||
+ !ToPropertyDescriptor(cx, descObj, true, &desc) ||
!descriptors.append(desc) ||
!descriptorKeys.append(nextKey))
{
return false;
}
}
}
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -1914,18 +1914,18 @@ CommonStaticResolveRejectImpl(JSContext*
// Step 6 of Resolve, 4 of Reject.
args.rval().setObject(*promise);
return true;
}
/**
* ES2016, 25.4.4.4, Promise.reject.
*/
-static bool
-Promise_reject(JSContext* cx, unsigned argc, Value* vp)
+bool
+js::Promise_reject(JSContext* cx, unsigned argc, Value* vp)
{
return CommonStaticResolveRejectImpl(cx, argc, vp, RejectMode);
}
/**
* Unforgeable version of ES2016, 25.4.4.4, Promise.reject.
*/
/* static */ JSObject*
@@ -1944,18 +1944,18 @@ PromiseObject::unforgeableReject(JSConte
// Step 5.
return promise;
}
/**
* ES2016, 25.4.4.5, Promise.resolve.
*/
-static bool
-Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp)
+bool
+js::Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp)
{
return CommonStaticResolveRejectImpl(cx, argc, vp, ResolveMode);
}
/**
* Unforgeable version of ES2016, 25.4.4.5, Promise.resolve.
*/
/* static */ JSObject*
@@ -2046,18 +2046,18 @@ js::OriginalPromiseThen(JSContext* cx, H
// Step 5.
if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise, resolve, reject))
return nullptr;
return resultPromise;
}
// ES2016, 25.4.5.3.
-static bool
-Promise_then(JSContext* cx, unsigned argc, Value* vp)
+bool
+js::Promise_then(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
RootedValue promiseVal(cx, args.thisv());
RootedValue onFulfilled(cx, args.get(0));
RootedValue onRejected(cx, args.get(1));
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -148,11 +148,18 @@ class PromiseTask : public JS::AsyncTask
// Called on a helper thread after StartAsyncTask. After execute()
// completes, the JS::FinishAsyncTaskCallback will be called. If this fails
// the task will be enqueued for deletion at some future point without ever
// calling finishPromise().
virtual void execute() = 0;
};
+bool
+Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp);
+bool
+Promise_reject(JSContext* cx, unsigned argc, Value* vp);
+bool
+Promise_then(JSContext* cx, unsigned argc, Value* vp);
+
} // namespace js
#endif /* builtin_Promise_h */
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -88,16 +88,17 @@ enum UnaryOperator {
UNOP_DELETE = 0,
UNOP_NEG,
UNOP_POS,
UNOP_NOT,
UNOP_BITNOT,
UNOP_TYPEOF,
UNOP_VOID,
+ UNOP_AWAIT,
UNOP_LIMIT
};
enum VarDeclKind {
VARDECL_ERR = -1,
VARDECL_VAR = 0,
VARDECL_CONST,
@@ -157,17 +158,18 @@ static const char* const binopNames[] =
static const char* const unopNames[] = {
"delete", /* UNOP_DELETE */
"-", /* UNOP_NEG */
"+", /* UNOP_POS */
"!", /* UNOP_NOT */
"~", /* UNOP_BITNOT */
"typeof", /* UNOP_TYPEOF */
- "void" /* UNOP_VOID */
+ "void", /* UNOP_VOID */
+ "await" /* UNOP_AWAIT */
};
static const char* const nodeTypeNames[] = {
#define ASTDEF(ast, str, method) str,
#include "jsast.tbl"
#undef ASTDEF
nullptr
};
@@ -461,17 +463,17 @@ class NodeBuilder
MOZ_MUST_USE bool literal(HandleValue val, TokenPos* pos, MutableHandleValue dst);
MOZ_MUST_USE bool identifier(HandleValue name, TokenPos* pos, MutableHandleValue dst);
MOZ_MUST_USE bool function(ASTType type, TokenPos* pos,
HandleValue id, NodeVector& args, NodeVector& defaults,
HandleValue body, HandleValue rest, GeneratorStyle generatorStyle,
- bool isExpression, MutableHandleValue dst);
+ bool isAsync, bool isExpression, MutableHandleValue dst);
MOZ_MUST_USE bool variableDeclarator(HandleValue id, HandleValue init, TokenPos* pos,
MutableHandleValue dst);
MOZ_MUST_USE bool switchCase(HandleValue expr, NodeVector& elts, TokenPos* pos, MutableHandleValue dst);
MOZ_MUST_USE bool catchClause(HandleValue var, HandleValue guard, HandleValue body, TokenPos* pos,
MutableHandleValue dst);
@@ -1585,27 +1587,28 @@ NodeBuilder::arrayPattern(NodeVector& el
{
return listNode(AST_ARRAY_PATT, "elements", elts, pos, dst);
}
bool
NodeBuilder::function(ASTType type, TokenPos* pos,
HandleValue id, NodeVector& args, NodeVector& defaults,
HandleValue body, HandleValue rest,
- GeneratorStyle generatorStyle, bool isExpression,
+ GeneratorStyle generatorStyle, bool isAsync, bool isExpression,
MutableHandleValue dst)
{
RootedValue array(cx), defarray(cx);
if (!newArray(args, &array))
return false;
if (!newArray(defaults, &defarray))
return false;
bool isGenerator = generatorStyle != GeneratorStyle::None;
RootedValue isGeneratorVal(cx, BooleanValue(isGenerator));
+ RootedValue isAsyncVal(cx, BooleanValue(isAsync));
RootedValue isExpressionVal(cx, BooleanValue(isExpression));
RootedValue cb(cx, callbacks[type]);
if (!cb.isNull()) {
return callback(cb, opt(id), array, body, isGeneratorVal, isExpressionVal, pos, dst);
}
if (isGenerator) {
@@ -1619,28 +1622,30 @@ NodeBuilder::function(ASTType type, Toke
styleVal.setString(styleStr);
return newNode(type, pos,
"id", id,
"params", array,
"defaults", defarray,
"body", body,
"rest", rest,
"generator", isGeneratorVal,
+ "async", isAsyncVal,
"style", styleVal,
"expression", isExpressionVal,
dst);
}
return newNode(type, pos,
"id", id,
"params", array,
"defaults", defarray,
"body", body,
"rest", rest,
"generator", isGeneratorVal,
+ "async", isAsyncVal,
"expression", isExpressionVal,
dst);
}
bool
NodeBuilder::classMethod(HandleValue name, HandleValue body, PropKind kind, bool isStatic,
TokenPos* pos, MutableHandleValue dst)
{
@@ -1799,16 +1804,17 @@ class ASTSerializer
bool literal(ParseNode* pn, MutableHandleValue dst);
bool pattern(ParseNode* pn, MutableHandleValue dst);
bool arrayPattern(ParseNode* pn, MutableHandleValue dst);
bool objectPattern(ParseNode* pn, MutableHandleValue dst);
bool function(ParseNode* pn, ASTType type, MutableHandleValue dst);
bool functionArgsAndBody(ParseNode* pn, NodeVector& args, NodeVector& defaults,
+ bool isAsync, bool isExpression,
MutableHandleValue body, MutableHandleValue rest);
bool functionBody(ParseNode* pn, TokenPos* pos, MutableHandleValue dst);
bool comprehensionBlock(ParseNode* pn, MutableHandleValue dst);
bool comprehensionIf(ParseNode* pn, MutableHandleValue dst);
bool comprehension(ParseNode* pn, MutableHandleValue dst);
bool generatorExpression(ParseNode* pn, MutableHandleValue dst);
@@ -1875,16 +1881,19 @@ UnaryOperator
ASTSerializer::unop(ParseNodeKind kind, JSOp op)
{
if (IsDeleteKind(kind))
return UNOP_DELETE;
if (IsTypeofKind(kind))
return UNOP_TYPEOF;
+ if (kind == PNK_AWAIT)
+ return UNOP_AWAIT;
+
switch (op) {
case JSOP_NEG:
return UNOP_NEG;
case JSOP_POS:
return UNOP_POS;
case JSOP_NOT:
return UNOP_NOT;
case JSOP_BITNOT:
@@ -2902,28 +2911,29 @@ ASTSerializer::expression(ParseNode* pn,
case PNK_BITOR:
case PNK_BITXOR:
case PNK_BITAND:
case PNK_IN:
case PNK_INSTANCEOF:
return leftAssociate(pn, dst);
case PNK_POW:
- return rightAssociate(pn, dst);
+ return rightAssociate(pn, dst);
case PNK_DELETENAME:
case PNK_DELETEPROP:
case PNK_DELETEELEM:
case PNK_DELETEEXPR:
case PNK_TYPEOFNAME:
case PNK_TYPEOFEXPR:
case PNK_VOID:
case PNK_NOT:
case PNK_BITNOT:
case PNK_POS:
+ case PNK_AWAIT:
case PNK_NEG: {
MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid->pn_pos));
UnaryOperator op = unop(pn->getKind(), pn->getOp());
LOCAL_ASSERT(op > UNOP_ERR && op < UNOP_LIMIT);
RootedValue expr(cx);
return expression(pn->pn_kid, &expr) &&
@@ -3391,16 +3401,17 @@ ASTSerializer::function(ParseNode* pn, A
GeneratorStyle generatorStyle =
pn->pn_funbox->isGenerator()
? (pn->pn_funbox->isLegacyGenerator()
? GeneratorStyle::Legacy
: GeneratorStyle::ES6)
: GeneratorStyle::None;
+ bool isAsync = pn->pn_funbox->isAsync();
bool isExpression =
#if JS_HAS_EXPR_CLOSURES
func->isExprBody();
#else
false;
#endif
RootedValue id(cx);
@@ -3411,23 +3422,24 @@ ASTSerializer::function(ParseNode* pn, A
NodeVector args(cx);
NodeVector defaults(cx);
RootedValue body(cx), rest(cx);
if (func->hasRest())
rest.setUndefined();
else
rest.setNull();
- return functionArgsAndBody(pn->pn_body, args, defaults, &body, &rest) &&
- builder.function(type, &pn->pn_pos, id, args, defaults, body,
- rest, generatorStyle, isExpression, dst);
+ return functionArgsAndBody(pn->pn_body, args, defaults, isAsync, isExpression, &body, &rest) &&
+ builder.function(type, &pn->pn_pos, id, args, defaults, body,
+ rest, generatorStyle, isAsync, isExpression, dst);
}
bool
ASTSerializer::functionArgsAndBody(ParseNode* pn, NodeVector& args, NodeVector& defaults,
+ bool isAsync, bool isExpression,
MutableHandleValue body, MutableHandleValue rest)
{
ParseNode* pnargs;
ParseNode* pnbody;
/* Extract the args and body separately. */
if (pn->isKind(PNK_PARAMSBODY)) {
pnargs = pn;
@@ -3451,16 +3463,24 @@ ASTSerializer::functionArgsAndBody(Parse
ParseNode* pnstart = pnbody->pn_head;
// Skip over initial yield in generator.
if (pnstart && pnstart->isKind(PNK_YIELD)) {
MOZ_ASSERT(pnstart->getOp() == JSOP_INITIALYIELD);
pnstart = pnstart->pn_next;
}
+ // Async arrow with expression body is converted into STATEMENTLIST
+ // to insert initial yield.
+ if (isAsync && isExpression) {
+ MOZ_ASSERT(pnstart->getKind() == PNK_RETURN);
+ return functionArgs(pn, pnargs, args, defaults, rest) &&
+ expression(pnstart->pn_kid, body);
+ }
+
return functionArgs(pn, pnargs, args, defaults, rest) &&
functionBody(pnstart, &pnbody->pn_pos, body);
}
default:
LOCAL_NOT_REACHED("unexpected function contents");
}
}
--- a/js/src/builtin/SelfHostingDefines.h
+++ b/js/src/builtin/SelfHostingDefines.h
@@ -88,16 +88,19 @@
#define REGEXP_FLAGS_SLOT 2
#define REGEXP_IGNORECASE_FLAG 0x01
#define REGEXP_GLOBAL_FLAG 0x02
#define REGEXP_MULTILINE_FLAG 0x04
#define REGEXP_STICKY_FLAG 0x08
#define REGEXP_UNICODE_FLAG 0x10
+#define ASYNC_WRAPPED_SLOT 1
+#define ASYNC_UNWRAPPED_SLOT 1
+
#define MODULE_OBJECT_ENVIRONMENT_SLOT 2
#define MODULE_STATE_FAILED 0
#define MODULE_STATE_PARSED 1
#define MODULE_STATE_INSTANTIATED 2
#define MODULE_STATE_EVALUATED 3
#endif
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -58,17 +58,17 @@ class MOZ_STACK_CLASS BytecodeCompiler
// Call setters for optional arguments.
void maybeSetSourceCompressor(SourceCompressionTask* sourceCompressor);
void setSourceArgumentsNotIncluded();
JSScript* compileGlobalScript(ScopeKind scopeKind);
JSScript* compileEvalScript(HandleObject environment, HandleScope enclosingScope);
ModuleObject* compileModule();
bool compileFunctionBody(MutableHandleFunction fun, Handle<PropertyNameVector> formals,
- GeneratorKind generatorKind);
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind);
ScriptSourceObject* sourceObjectPtr() const;
private:
JSScript* compileScript(HandleObject environment, SharedContext* sc);
bool checkLength();
bool createScriptSource();
bool maybeCompressSource();
@@ -430,17 +430,18 @@ BytecodeCompiler::compileModule()
MOZ_ASSERT_IF(cx->isJSContext(), !cx->asJSContext()->isExceptionPending());
return module;
}
bool
BytecodeCompiler::compileFunctionBody(MutableHandleFunction fun,
Handle<PropertyNameVector> formals,
- GeneratorKind generatorKind)
+ GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind)
{
MOZ_ASSERT(fun);
MOZ_ASSERT(fun->isTenured());
fun->setArgCount(formals.length());
if (!createSourceAndParser())
return false;
@@ -448,17 +449,17 @@ BytecodeCompiler::compileFunctionBody(Mu
// 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->standaloneFunctionBody(fun, enclosingScope, formals, generatorKind,
+ fn = parser->standaloneFunctionBody(fun, enclosingScope, formals, generatorKind, asyncKind,
directives, &newDirectives);
if (!fn && !handleParseFailure(newDirectives))
return false;
} while (!fn);
if (!NameFunctions(cx, fn))
return false;
@@ -641,17 +642,18 @@ frontend::CompileLazyFunction(JSContext*
return false;
Parser<FullParseHandler> parser(cx, cx->tempLifoAlloc(), options, chars, length,
/* foldConstants = */ true, usedNames, nullptr, lazy);
if (!parser.checkOptions())
return false;
Rooted<JSFunction*> fun(cx, lazy->functionNonDelazifying());
MOZ_ASSERT(!lazy->isLegacyGenerator());
- ParseNode* pn = parser.standaloneLazyFunction(fun, lazy->strict(), lazy->generatorKind());
+ ParseNode* pn = parser.standaloneLazyFunction(fun, lazy->strict(), lazy->generatorKind(),
+ lazy->asyncKind());
if (!pn)
return false;
if (!NameFunctions(cx, pn))
return false;
RootedScriptSource sourceObject(cx, lazy->sourceObject());
MOZ_ASSERT(sourceObject);
@@ -674,51 +676,63 @@ frontend::CompileLazyFunction(JSContext*
return bce.emitFunctionScript(pn->pn_body);
}
// Compile a JS function body, which might appear as the value of an event
// handler attribute in an HTML <INPUT> tag, or in a Function() constructor.
static bool
CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, const ReadOnlyCompileOptions& options,
Handle<PropertyNameVector> formals, SourceBufferHolder& srcBuf,
- HandleScope enclosingScope, GeneratorKind generatorKind)
+ HandleScope enclosingScope, GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind)
{
MOZ_ASSERT(!options.isRunOnce);
// FIXME: make Function pass in two strings and parse them as arguments and
// ProgramElements respectively.
BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, enclosingScope,
TraceLogger_ParserCompileFunction);
compiler.setSourceArgumentsNotIncluded();
- return compiler.compileFunctionBody(fun, formals, generatorKind);
+ return compiler.compileFunctionBody(fun, formals, generatorKind, asyncKind);
}
bool
frontend::CompileFunctionBody(JSContext* cx, MutableHandleFunction fun,
const ReadOnlyCompileOptions& options,
Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf,
HandleScope enclosingScope)
{
- return CompileFunctionBody(cx, fun, options, formals, srcBuf, enclosingScope, NotGenerator);
+ return CompileFunctionBody(cx, fun, options, formals, srcBuf, enclosingScope, NotGenerator,
+ SyncFunction);
}
bool
frontend::CompileFunctionBody(JSContext* cx, MutableHandleFunction fun,
const ReadOnlyCompileOptions& options,
Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf)
{
RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope,
- NotGenerator);
+ NotGenerator, SyncFunction);
}
-
bool
frontend::CompileStarGeneratorBody(JSContext* cx, MutableHandleFunction fun,
const ReadOnlyCompileOptions& options,
Handle<PropertyNameVector> formals,
JS::SourceBufferHolder& srcBuf)
{
RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope,
- StarGenerator);
+ StarGenerator, SyncFunction);
}
+
+bool
+frontend::CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun,
+ const ReadOnlyCompileOptions& options,
+ Handle<PropertyNameVector> formals,
+ JS::SourceBufferHolder& srcBuf)
+{
+ RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
+ return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope,
+ StarGenerator, AsyncFunction);
+}
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -63,16 +63,21 @@ CompileFunctionBody(JSContext* cx, Mutab
const ReadOnlyCompileOptions& options,
Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf);
MOZ_MUST_USE bool
CompileStarGeneratorBody(JSContext* cx, MutableHandleFunction fun,
const ReadOnlyCompileOptions& options,
Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf);
+MOZ_MUST_USE bool
+CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun,
+ const ReadOnlyCompileOptions& options,
+ Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf);
+
ScriptSourceObject*
CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options);
/*
* True if str consists of an IdentifierStart character, followed by one or
* more IdentifierPart characters, i.e. it matches the IdentifierName production
* in the language spec.
*
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -2460,16 +2460,17 @@ BytecodeEmitter::checkSideEffects(ParseN
// This invokes the (user-controllable) iterator protocol.
case PNK_SPREAD:
MOZ_ASSERT(pn->isArity(PN_UNARY));
*answer = true;
return true;
case PNK_YIELD_STAR:
case PNK_YIELD:
+ case PNK_AWAIT:
MOZ_ASSERT(pn->isArity(PN_BINARY));
*answer = true;
return true;
// Deletion generally has side effects, even if isolated cases have none.
case PNK_DELETENAME:
case PNK_DELETEPROP:
case PNK_DELETEELEM:
@@ -6923,30 +6924,42 @@ BytecodeEmitter::emitFunction(ParseNode*
/* Make the function object a literal in the outer script's pool. */
unsigned index = objectList.add(pn->pn_funbox);
/* Non-hoisted functions simply emit their respective op. */
if (!pn->functionIsHoisted()) {
/* JSOP_LAMBDA_ARROW is always preceded by a new.target */
MOZ_ASSERT(fun->isArrow() == (pn->getOp() == JSOP_LAMBDA_ARROW));
+ if (funbox->isAsync()) {
+ MOZ_ASSERT(!needsProto);
+ return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow());
+ }
+
if (fun->isArrow()) {
if (sc->allowNewTarget()) {
if (!emit1(JSOP_NEWTARGET))
return false;
} else {
if (!emit1(JSOP_NULL))
return false;
}
}
if (needsProto) {
MOZ_ASSERT(pn->getOp() == JSOP_FUNWITHPROTO || pn->getOp() == JSOP_LAMBDA);
pn->setOp(JSOP_FUNWITHPROTO);
}
+
+ if (pn->getOp() == JSOP_DEFFUN) {
+ if (!emitIndex32(JSOP_LAMBDA, index))
+ return false;
+ return emit1(JSOP_DEFFUN);
+ }
+
return emitIndex32(pn->getOp(), index);
}
MOZ_ASSERT(!needsProto);
bool topLevelFunction;
if (sc->isFunctionBox() || (sc->isEvalContext() && sc->strict())) {
// No nested functions inside other functions are top-level.
@@ -6967,40 +6980,120 @@ BytecodeEmitter::emitFunction(ParseNode*
RootedModuleObject module(cx, sc->asModuleContext()->module());
if (!module->noteFunctionDeclaration(cx, name, fun))
return false;
} else {
MOZ_ASSERT(sc->isGlobalContext() || sc->isEvalContext());
MOZ_ASSERT(pn->getOp() == JSOP_NOP);
switchToPrologue();
- if (!emitIndex32(JSOP_DEFFUN, index))
+ if (funbox->isAsync()) {
+ if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow()))
+ return false;
+ } else {
+ if (!emitIndex32(JSOP_LAMBDA, index))
+ return false;
+ }
+ if (!emit1(JSOP_DEFFUN))
return false;
if (!updateSourceCoordNotes(pn->pn_pos.begin))
return false;
switchToMain();
}
} else {
// For functions nested within functions and blocks, make a lambda and
// initialize the binding name of the function in the current scope.
- auto emitLambda = [index](BytecodeEmitter* bce, const NameLocation&, bool) {
+ bool isAsync = funbox->isAsync();
+ auto emitLambda = [index, isAsync](BytecodeEmitter* bce, const NameLocation&, bool) {
+ if (isAsync) {
+ return bce->emitAsyncWrapper(index, /* needsHomeObject = */ false,
+ /* isArrow = */ false);
+ }
return bce->emitIndexOp(JSOP_LAMBDA, index);
};
if (!emitInitializeName(name, emitLambda))
return false;
if (!emit1(JSOP_POP))
return false;
}
return true;
}
bool
+BytecodeEmitter::emitAsyncWrapperLambda(unsigned index, bool isArrow) {
+ if (isArrow) {
+ if (sc->allowNewTarget()) {
+ if (!emit1(JSOP_NEWTARGET))
+ return false;
+ } else {
+ if (!emit1(JSOP_NULL))
+ return false;
+ }
+ if (!emitIndex32(JSOP_LAMBDA_ARROW, index))
+ return false;
+ } else {
+ if (!emitIndex32(JSOP_LAMBDA, index))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow) {
+ // needsHomeObject can be true for propertyList for extended class.
+ // In that case push both unwrapped and wrapped function, in order to
+ // initialize home object of unwrapped function, and set wrapped function
+ // as a property.
+ //
+ // lambda // unwrapped
+ // getintrinsic // unwrapped AsyncFunction_wrap
+ // undefined // unwrapped AsyncFunction_wrap undefined
+ // dupat 2 // unwrapped AsyncFunction_wrap undefined unwrapped
+ // call 1 // unwrapped wrapped
+ //
+ // Emitted code is surrounded by the following code.
+ //
+ // // classObj classCtor classProto
+ // (emitted code) // classObj classCtor classProto unwrapped wrapped
+ // swap // classObj classCtor classProto wrapped unwrapped
+ // inithomeobject 1 // classObj classCtor classProto wrapped unwrapped
+ // // initialize the home object of unwrapped
+ // // with classProto here
+ // pop // classObj classCtor classProto wrapped
+ // inithiddenprop // classObj classCtor classProto wrapped
+ // // initialize the property of the classProto
+ // // with wrapped function here
+ // pop // classObj classCtor classProto
+ //
+ // needsHomeObject is false for other cases, push wrapped function only.
+ if (needsHomeObject) {
+ if (!emitAsyncWrapperLambda(index, isArrow))
+ return false;
+ }
+ if (!emitAtomOp(cx->names().AsyncFunction_wrap, JSOP_GETINTRINSIC))
+ return false;
+ if (!emit1(JSOP_UNDEFINED))
+ return false;
+ if (needsHomeObject) {
+ if (!emitDupAt(2))
+ return false;
+ } else {
+ if (!emitAsyncWrapperLambda(index, isArrow))
+ return false;
+ }
+ if (!emitCall(JSOP_CALL, 1))
+ return false;
+ return true;
+}
+
+bool
BytecodeEmitter::emitDo(ParseNode* pn)
{
/* Emit an annotated nop so IonBuilder can recognize the 'do' loop. */
unsigned noteIndex;
if (!newSrcNote(SRC_WHILE, ¬eIndex))
return false;
if (!emit1(JSOP_NOP))
return false;
@@ -8371,18 +8464,27 @@ BytecodeEmitter::emitPropertyList(ParseN
if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER)
objp.set(nullptr);
if (propdef->pn_right->isKind(PNK_FUNCTION) &&
propdef->pn_right->pn_funbox->needsHomeObject())
{
MOZ_ASSERT(propdef->pn_right->pn_funbox->function()->allowSuperProperty());
- if (!emit2(JSOP_INITHOMEOBJECT, isIndex))
- return false;
+ bool isAsync = propdef->pn_right->pn_funbox->isAsync();
+ if (isAsync) {
+ if (!emit1(JSOP_SWAP))
+ return false;
+ }
+ if (!emit2(JSOP_INITHOMEOBJECT, isIndex + isAsync))
+ return false;
+ if (isAsync) {
+ if (!emit1(JSOP_POP))
+ return false;
+ }
}
// Class methods are not enumerable.
if (type == ClassBody) {
switch (op) {
case JSOP_INITPROP: op = JSOP_INITHIDDENPROP; break;
case JSOP_INITPROP_GETTER: op = JSOP_INITHIDDENPROP_GETTER; break;
case JSOP_INITPROP_SETTER: op = JSOP_INITHIDDENPROP_SETTER; break;
@@ -9238,16 +9340,17 @@ BytecodeEmitter::emitTree(ParseNode* pn,
break;
case PNK_GENERATOR:
if (!emit1(JSOP_GENERATOR))
return false;
break;
case PNK_YIELD:
+ case PNK_AWAIT:
if (!emitYield(pn))
return false;
break;
case PNK_STATEMENTLIST:
if (!emitStatementList(pn))
return false;
break;
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -598,16 +598,19 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitYield(ParseNode* pn);
MOZ_MUST_USE bool emitYieldOp(JSOp op);
MOZ_MUST_USE bool emitYieldStar(ParseNode* iter, ParseNode* gen);
MOZ_MUST_USE bool emitPropLHS(ParseNode* pn);
MOZ_MUST_USE bool emitPropOp(ParseNode* pn, JSOp op);
MOZ_MUST_USE bool emitPropIncDec(ParseNode* pn);
+ MOZ_MUST_USE bool emitAsyncWrapperLambda(unsigned index, bool isArrow);
+ MOZ_MUST_USE bool emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow);
+
MOZ_MUST_USE bool emitComputedPropertyName(ParseNode* computedPropName);
// Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM
// opcode onto the stack in the right order. In the case of SETELEM, the
// value to be assigned must already be pushed.
enum class EmitElemOption { Get, Set, Call, IncDec, CompoundAssign };
MOZ_MUST_USE bool emitElemOperands(ParseNode* pn, EmitElemOption opts);
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -306,16 +306,17 @@ ContainsHoistedDeclaration(ExclusiveCont
case PNK_COMPUTED_NAME:
case PNK_SPREAD:
case PNK_MUTATEPROTO:
case PNK_COLON:
case PNK_SHORTHAND:
case PNK_CONDITIONAL:
case PNK_TYPEOFNAME:
case PNK_TYPEOFEXPR:
+ case PNK_AWAIT:
case PNK_VOID:
case PNK_NOT:
case PNK_BITNOT:
case PNK_DELETENAME:
case PNK_DELETEPROP:
case PNK_DELETEELEM:
case PNK_DELETEEXPR:
case PNK_POS:
@@ -1778,16 +1779,17 @@ Fold(ExclusiveContext* cx, ParseNode** p
return FoldList(cx, pn, parser, inGenexpLambda);
case PNK_YIELD_STAR:
MOZ_ASSERT(pn->isArity(PN_BINARY));
MOZ_ASSERT(pn->pn_right->isKind(PNK_NAME));
return Fold(cx, &pn->pn_left, parser, inGenexpLambda);
case PNK_YIELD:
+ case PNK_AWAIT:
MOZ_ASSERT(pn->isArity(PN_BINARY));
MOZ_ASSERT(pn->pn_right->isKind(PNK_NAME) ||
(pn->pn_right->isKind(PNK_ASSIGN) &&
pn->pn_right->pn_left->isKind(PNK_NAME) &&
pn->pn_right->pn_right->isKind(PNK_GENERATOR)));
if (!pn->pn_left)
return true;
return Fold(cx, &pn->pn_left, parser, inGenexpLambda);
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -427,16 +427,21 @@ class FullParseHandler
return new_<BinaryNode>(PNK_YIELD, op, pos, value, gen);
}
ParseNode* newYieldStarExpression(uint32_t begin, ParseNode* value, ParseNode* gen) {
TokenPos pos(begin, value->pn_pos.end);
return new_<BinaryNode>(PNK_YIELD_STAR, JSOP_NOP, pos, value, gen);
}
+ ParseNode* newAwaitExpression(uint32_t begin, ParseNode* value, ParseNode* gen) {
+ TokenPos pos(begin, value ? value->pn_pos.end : begin + 1);
+ return new_<BinaryNode>(PNK_AWAIT, JSOP_YIELD, pos, value, gen);
+ }
+
// Statements
ParseNode* newStatementList(const TokenPos& pos) {
return new_<ListNode>(PNK_STATEMENTLIST, pos);
}
MOZ_MUST_USE bool isFunctionStmt(ParseNode* stmt) {
while (stmt->isKind(PNK_LABEL))
--- a/js/src/frontend/NameFunctions.cpp
+++ b/js/src/frontend/NameFunctions.cpp
@@ -502,16 +502,17 @@ class NameResolver
case PNK_YIELD_STAR:
MOZ_ASSERT(cur->isArity(PN_BINARY));
MOZ_ASSERT(cur->pn_right->isKind(PNK_NAME));
if (!resolve(cur->pn_left, prefix))
return false;
break;
case PNK_YIELD:
+ case PNK_AWAIT:
MOZ_ASSERT(cur->isArity(PN_BINARY));
if (cur->pn_left) {
if (!resolve(cur->pn_left, prefix))
return false;
}
MOZ_ASSERT(cur->pn_right->isKind(PNK_NAME) ||
(cur->pn_right->isKind(PNK_ASSIGN) &&
cur->pn_right->pn_left->isKind(PNK_NAME) &&
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -285,17 +285,18 @@ PushNodeChildren(ParseNode* pn, NodeStac
return PushResult::Recyclable;
}
// The left half is the expression being yielded. The right half is
// internal goop: a name reference to the invisible '.generator' local
// variable, or an assignment of a PNK_GENERATOR node to the '.generator'
// local, for a synthesized, prepended initial yield. Yum!
case PNK_YIELD_STAR:
- case PNK_YIELD: {
+ case PNK_YIELD:
+ case PNK_AWAIT: {
MOZ_ASSERT(pn->isArity(PN_BINARY));
MOZ_ASSERT(pn->pn_right);
MOZ_ASSERT(pn->pn_right->isKind(PNK_NAME) ||
(pn->pn_right->isKind(PNK_ASSIGN) &&
pn->pn_right->pn_left->isKind(PNK_NAME) &&
pn->pn_right->pn_right->isKind(PNK_GENERATOR)));
if (pn->pn_left)
stack->push(pn->pn_left);
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -115,16 +115,17 @@ class ObjectBox;
F(SETTHIS) \
\
/* Unary operators. */ \
F(TYPEOFNAME) \
F(TYPEOFEXPR) \
F(VOID) \
F(NOT) \
F(BITNOT) \
+ F(AWAIT) \
\
/* \
* Binary operators. \
* These must be in the same order as TOK_OR and friends in TokenStream.h. \
*/ \
F(OR) \
F(AND) \
F(BITOR) \
@@ -345,17 +346,18 @@ IsTypeofKind(ParseNodeKind kind)
* PNK_MOD,
* PNK_POW (**) is right-associative, but forms a list
* nonetheless. Special hacks everywhere.
*
* PNK_POS, unary pn_kid: UNARY expr
* PNK_NEG
* PNK_VOID, unary pn_kid: UNARY expr
* PNK_NOT,
- * PNK_BITNOT
+ * PNK_BITNOT,
+ * PNK_AWAIT
* PNK_TYPEOFNAME, unary pn_kid: UNARY expr
* PNK_TYPEOFEXPR
* PNK_PREINCREMENT, unary pn_kid: MEMBER expr
* PNK_POSTINCREMENT,
* PNK_PREDECREMENT,
* PNK_POSTDECREMENT
* PNK_NEW list pn_head: list of ctor, arg1, arg2, ... argN
* pn_count: 1 + N (where N is number of args)
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -431,30 +431,31 @@ UsedNameTracker::rewind(RewindToken toke
scopeCounter_ = token.scopeId;
for (UsedNameMap::Range r = map_.all(); !r.empty(); r.popFront())
r.front().value().resetToScope(token.scriptId, token.scopeId);
}
FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead,
JSFunction* fun, Directives directives, bool extraWarnings,
- GeneratorKind generatorKind)
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind)
: ObjectBox(fun, traceListHead),
SharedContext(cx, Kind::ObjectBox, directives, extraWarnings),
enclosingScope_(nullptr),
namedLambdaBindings_(nullptr),
functionScopeBindings_(nullptr),
extraVarScopeBindings_(nullptr),
functionNode(nullptr),
bufStart(0),
bufEnd(0),
startLine(1),
startColumn(0),
length(0),
generatorKindBits_(GeneratorKindAsBits(generatorKind)),
+ asyncKindBits_(AsyncKindAsBits(asyncKind)),
isGenexpLambda(false),
hasDestructuringArgs(false),
hasParameterExprs(false),
hasDirectEvalInParameterExpr(false),
hasDuplicateParameters(false),
useAsm(false),
insideUseAsm(false),
isAnnexB(false),
@@ -729,31 +730,32 @@ Parser<ParseHandler>::newObjectBox(JSObj
traceListHead = objbox;
return objbox;
}
template <typename ParseHandler>
FunctionBox*
Parser<ParseHandler>::newFunctionBox(Node fn, JSFunction* fun, Directives inheritedDirectives,
- GeneratorKind generatorKind, bool tryAnnexB)
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ bool tryAnnexB)
{
MOZ_ASSERT(fun);
MOZ_ASSERT_IF(tryAnnexB, !pc->sc()->strict());
/*
* We use JSContext.tempLifoAlloc to allocate parsed objects and place them
* on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc
* arenas containing the entries must be alive until we are done with
* scanning, parsing and code generation for the whole script or top-level
* function.
*/
FunctionBox* funbox =
alloc.new_<FunctionBox>(context, alloc, traceListHead, fun, inheritedDirectives,
- options().extraWarningsOption, generatorKind);
+ options().extraWarningsOption, generatorKind, asyncKind);
if (!funbox) {
ReportOutOfMemory(context);
return nullptr;
}
traceListHead = funbox;
if (fn)
handler.setFunctionBox(fn, funbox);
@@ -850,17 +852,17 @@ Parser<ParseHandler>::reportBadReturn(No
template <typename ParseHandler>
bool
Parser<ParseHandler>::isValidStrictBinding(PropertyName* name)
{
return name != context->names().eval &&
name != context->names().arguments &&
name != context->names().let &&
name != context->names().static_ &&
- !IsKeyword(name);
+ !(IsKeyword(name) && name != context->names().await);
}
/*
* Check that it is permitted to introduce a binding for |name|. Use |pos| for
* reporting error locations.
*/
template <typename ParseHandler>
bool
@@ -1962,16 +1964,17 @@ Parser<FullParseHandler>::moduleBody(Mod
ParseContext::VarScope varScope(this);
if (!varScope.init(pc))
return nullptr;
Node mn = handler.newModule();
if (!mn)
return null();
+ AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, true);
ParseNode* pn = statementList(YieldIsKeyword);
if (!pn)
return null();
MOZ_ASSERT(pn->isKind(PNK_STATEMENTLIST));
mn->pn_body = pn;
TokenKind tt;
@@ -2190,16 +2193,17 @@ Parser<SyntaxParseHandler>::finishFuncti
if (!lazy)
return false;
// Flags that need to be copied into the JSScript when we do the full
// parse.
if (pc->sc()->strict())
lazy->setStrict();
lazy->setGeneratorKind(funbox->generatorKind());
+ lazy->setAsyncKind(funbox->asyncKind());
if (funbox->isLikelyConstructorWrapper())
lazy->setLikelyConstructorWrapper();
if (funbox->isDerivedClassConstructor())
lazy->setIsDerivedClassConstructor();
if (funbox->needsHomeObject())
lazy->setNeedsHomeObject();
if (funbox->declaredArguments)
lazy->setShouldDeclareArguments();
@@ -2209,38 +2213,49 @@ Parser<SyntaxParseHandler>::finishFuncti
// Flags that need to copied back into the parser when we do the full
// parse.
PropagateTransitiveParseFlags(funbox, lazy);
fun->initLazyScript(lazy);
return true;
}
+static YieldHandling
+GetYieldHandling(GeneratorKind generatorKind, FunctionAsyncKind asyncKind)
+{
+ if (asyncKind == AsyncFunction)
+ return YieldIsName;
+ if (generatorKind == NotGenerator)
+ return YieldIsName;
+ return YieldIsKeyword;
+}
+
template <>
ParseNode*
Parser<FullParseHandler>::standaloneFunctionBody(HandleFunction fun,
HandleScope enclosingScope,
Handle<PropertyNameVector> formals,
GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind,
Directives inheritedDirectives,
Directives* newDirectives)
{
MOZ_ASSERT(checkOptionsCalled);
Node fn = handler.newFunctionDefinition();
if (!fn)
return null();
ParseNode* argsbody = handler.newList(PNK_PARAMSBODY);
if (!argsbody)
return null();
fn->pn_body = argsbody;
FunctionBox* funbox = newFunctionBox(fn, fun, inheritedDirectives, generatorKind,
- /* tryAnnexB = */ false);
+ asyncKind, /* tryAnnexB = */ false);
if (!funbox)
return null();
funbox->initStandaloneFunction(enclosingScope);
ParseContext funpc(this, funbox, newDirectives);
if (!funpc.init())
return null();
funpc.setIsStandaloneFunctionBody();
@@ -2253,17 +2268,18 @@ Parser<FullParseHandler>::standaloneFunc
bool duplicatedParam = false;
for (uint32_t i = 0; i < formals.length(); i++) {
if (!notePositionalFormalParameter(fn, formals[i], false, &duplicatedParam))
return null();
}
funbox->hasDuplicateParameters = duplicatedParam;
- YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName;
+ YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind);
+ AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, asyncKind == AsyncFunction);
ParseNode* pn = functionBody(InAllowed, yieldHandling, Statement, StatementListBody);
if (!pn)
return null();
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
if (tt != TOK_EOF) {
@@ -2389,23 +2405,38 @@ Parser<ParseHandler>::functionBody(InHan
// the invalid parameter name at the correct source location.
pc->newDirectives->setStrict();
return null();
}
}
} else {
MOZ_ASSERT(type == ExpressionBody);
+ // Async functions are implemented as star generators, and star
+ // generators are assumed to be statement lists, to prepend initial
+ // `yield`.
+ Node stmtList = null();
+ if (pc->isAsync()) {
+ stmtList = handler.newStatementList(pos());
+ if (!stmtList)
+ return null();
+ }
+
Node kid = assignExpr(inHandling, yieldHandling, TripledotProhibited);
if (!kid)
return null();
pn = handler.newReturnStatement(kid, handler.getPosition(kid));
if (!pn)
return null();
+
+ if (pc->isAsync()) {
+ handler.addStatementToList(stmtList, pn);
+ pn = stmtList;
+ }
}
switch (pc->generatorKind()) {
case NotGenerator:
MOZ_ASSERT(pc->lastYieldOffset == startYieldOffset);
break;
case LegacyGenerator:
@@ -2416,23 +2447,23 @@ Parser<ParseHandler>::functionBody(InHan
MOZ_ASSERT(!IsGetterKind(kind));
MOZ_ASSERT(!IsSetterKind(kind));
MOZ_ASSERT(!IsConstructorKind(kind));
MOZ_ASSERT(kind != Method);
MOZ_ASSERT(type != ExpressionBody);
break;
case StarGenerator:
- MOZ_ASSERT(kind != Arrow);
- MOZ_ASSERT(type == StatementListBody);
+ MOZ_ASSERT_IF(!pc->isAsync(), kind != Arrow);
+ MOZ_ASSERT_IF(!pc->isAsync(), type == StatementListBody);
break;
}
if (pc->isGenerator()) {
- MOZ_ASSERT(type == StatementListBody);
+ MOZ_ASSERT_IF(!pc->isAsync(), type == StatementListBody);
if (!declareDotGeneratorName())
return null();
Node generator = newDotGeneratorName();
if (!generator)
return null();
if (!handler.prependInitialYield(pn, generator))
return null();
}
@@ -2448,17 +2479,18 @@ Parser<ParseHandler>::functionBody(InHan
}
return finishLexicalScope(pc->varScope(), pn);
}
template <typename ParseHandler>
JSFunction*
Parser<ParseHandler>::newFunction(HandleAtom atom, FunctionSyntaxKind kind,
- GeneratorKind generatorKind, HandleObject proto)
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ HandleObject proto)
{
MOZ_ASSERT_IF(kind == Statement, atom != nullptr);
RootedFunction fun(context);
gc::AllocKind allocKind = gc::AllocKind::FUNCTION;
JSFunction::Flags flags;
#ifdef DEBUG
@@ -2504,16 +2536,20 @@ Parser<ParseHandler>::newFunction(Handle
allocKind = gc::AllocKind::FUNCTION_EXTENDED;
}
#endif
flags = (generatorKind == NotGenerator
? JSFunction::INTERPRETED_NORMAL
: JSFunction::INTERPRETED_GENERATOR);
}
+ // We store the async wrapper in a slot for later access.
+ if (asyncKind == AsyncFunction)
+ allocKind = gc::AllocKind::FUNCTION_EXTENDED;
+
fun = NewFunctionWithProto(context, nullptr, 0, flags, nullptr, atom, proto,
allocKind, TenuredObject);
if (!fun)
return nullptr;
if (options().selfHostingMode) {
fun->setIsSelfHostedBuiltin();
#ifdef DEBUG
if (isGlobalSelfHostedBuiltin)
@@ -2613,29 +2649,57 @@ Parser<ParseHandler>::prefixAccessorName
template <typename ParseHandler>
bool
Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyntaxKind kind,
Node funcpn)
{
FunctionBox* funbox = pc->functionBox();
bool parenFreeArrow = false;
- TokenStream::Modifier modifier = TokenStream::None;
+ // Modifier for the following tokens.
+ // TokenStream::None for the following cases:
+ // async a => 1
+ // ^
+ //
+ // (a) => 1
+ // ^
+ //
+ // async (a) => 1
+ // ^
+ //
+ // function f(a) {}
+ // ^
+ //
+ // TokenStream::Operand for the following case:
+ // a => 1
+ // ^
+ TokenStream::Modifier firstTokenModifier = TokenStream::None;
+
+ // Modifier for the the first token in each argument.
+ // can be changed to TokenStream::None for the following case:
+ // async a => 1
+ // ^
+ TokenStream::Modifier argModifier = TokenStream::Operand;
if (kind == Arrow) {
TokenKind tt;
- if (!tokenStream.peekToken(&tt, TokenStream::Operand))
+ // In async function, the first token after `async` is already gotten
+ // with TokenStream::None.
+ // In sync function, the first token is already gotten with
+ // TokenStream::Operand.
+ firstTokenModifier = funbox->isAsync() ? TokenStream::None : TokenStream::Operand;
+ if (!tokenStream.peekToken(&tt, firstTokenModifier))
return false;
- if (tt == TOK_NAME || tt == TOK_YIELD)
+ if (tt == TOK_NAME || tt == TOK_YIELD) {
parenFreeArrow = true;
- else
- modifier = TokenStream::Operand;
+ argModifier = firstTokenModifier;
+ }
}
if (!parenFreeArrow) {
TokenKind tt;
- if (!tokenStream.getToken(&tt, modifier))
+ if (!tokenStream.getToken(&tt, firstTokenModifier))
return false;
if (tt != TOK_LP) {
report(ParseError, false, null(),
kind == Arrow ? JSMSG_BAD_ARROW_ARGS : JSMSG_PAREN_BEFORE_FORMAL);
return false;
}
// Record the start of function source (for FunctionToString). If we
@@ -2672,18 +2736,19 @@ Parser<ParseHandler>::functionArguments(
while (true) {
if (hasRest) {
report(ParseError, false, null(), JSMSG_PARAMETER_AFTER_REST);
return false;
}
TokenKind tt;
- if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ if (!tokenStream.getToken(&tt, argModifier))
return false;
+ argModifier = TokenStream::Operand;
MOZ_ASSERT_IF(parenFreeArrow, tt == TOK_NAME || tt == TOK_YIELD);
if (tt == TOK_TRIPLEDOT) {
if (IsSetterKind(kind)) {
report(ParseError, false, null(),
JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
return false;
}
@@ -2714,34 +2779,42 @@ Parser<ParseHandler>::functionArguments(
if (duplicatedParam) {
// Has duplicated args before the destructuring parameter.
report(ParseError, false, null(), JSMSG_BAD_DUP_ARGS);
return false;
}
funbox->hasDestructuringArgs = true;
- Node destruct = destructuringDeclarationWithoutYield(
+ Node destruct = destructuringDeclarationWithoutYieldOrAwait(
DeclarationKind::FormalParameter,
- yieldHandling, tt,
- JSMSG_YIELD_IN_DEFAULT);
+ yieldHandling, tt);
if (!destruct)
return false;
if (!noteDestructuredPositionalFormalParameter(funcpn, destruct))
return false;
break;
}
case TOK_NAME:
case TOK_YIELD: {
if (parenFreeArrow)
funbox->setStart(tokenStream);
+ if (funbox->isAsync() && tokenStream.currentName() == context->names().await) {
+ // `await` is already gotten as TOK_NAME for the following
+ // case:
+ //
+ // async await => 1
+ report(ParseError, false, null(), JSMSG_RESERVED_ID, "await");
+ return false;
+ }
+
RootedPropertyName name(context, bindingIdentifier(yieldHandling));
if (!name)
return false;
if (!notePositionalFormalParameter(funcpn, name, disallowDuplicateParams,
&duplicatedParam))
{
return false;
@@ -2786,17 +2859,17 @@ Parser<ParseHandler>::functionArguments(
hasDefault = true;
// The Function.length property is the number of formals
// before the first default argument.
funbox->length = positionalFormals.length() - 1;
}
funbox->hasParameterExprs = true;
- Node def_expr = assignExprWithoutYield(yieldHandling, JSMSG_YIELD_IN_DEFAULT);
+ Node def_expr = assignExprWithoutYieldOrAwait(yieldHandling);
if (!def_expr)
return false;
if (!handler.setLastFunctionFormalParameterDefault(funcpn, def_expr))
return false;
}
if (parenFreeArrow || IsSetterKind(kind))
break;
@@ -2916,17 +2989,17 @@ Parser<FullParseHandler>::skipLazyInnerF
// When a lazily-parsed function is called, we only fully parse (and emit)
// that function, not any of its nested children. The initial syntax-only
// parse recorded the free variables of nested functions and their extents,
// so we can skip over them after accounting for their free variables.
RootedFunction fun(context, handler.nextLazyInnerFunction());
MOZ_ASSERT(!fun->isLegacyGenerator());
FunctionBox* funbox = newFunctionBox(pn, fun, Directives(/* strict = */ false),
- fun->generatorKind(), tryAnnexB);
+ fun->generatorKind(), fun->asyncKind(), tryAnnexB);
if (!funbox)
return false;
LazyScript* lazy = fun->lazyScript();
if (lazy->needsHomeObject())
funbox->setNeedsHomeObject();
PropagateTransitiveParseFlags(lazy, pc->sc());
@@ -3024,19 +3097,21 @@ Parser<ParseHandler>::templateLiteral(Yi
} while (tt == TOK_TEMPLATE_HEAD);
return nodeList;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::functionDefinition(InHandling inHandling, YieldHandling yieldHandling,
HandleAtom funName, FunctionSyntaxKind kind,
- GeneratorKind generatorKind, InvokedPrediction invoked)
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ InvokedPrediction invoked)
{
MOZ_ASSERT_IF(kind == Statement, funName);
+ MOZ_ASSERT_IF(asyncKind == AsyncFunction, generatorKind == StarGenerator);
Node pn = handler.newFunctionDefinition();
if (!pn)
return null();
if (invoked)
pn = handler.setLikelyIIFE(pn);
@@ -3059,17 +3134,17 @@ Parser<ParseHandler>::functionDefinition
// If we are off the main thread, the generator meta-objects have
// already been created by js::StartOffThreadParseScript, so cx will not
// be necessary.
JSContext* cx = context->maybeJSContext();
proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, context->global());
if (!proto)
return null();
}
- RootedFunction fun(context, newFunction(funName, kind, generatorKind, proto));
+ RootedFunction fun(context, newFunction(funName, kind, generatorKind, asyncKind, proto));
if (!fun)
return null();
// Speculatively parse using the directives of the parent parsing 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.
Directives directives(pc);
@@ -3078,17 +3153,17 @@ Parser<ParseHandler>::functionDefinition
TokenStream::Position start(keepAtoms);
tokenStream.tell(&start);
// Parse the inner function. The following is a loop as we may attempt to
// reparse a function due to failed syntax parsing and encountering new
// "use foo" directives.
while (true) {
if (trySyntaxParseInnerFunction(pn, fun, inHandling, yieldHandling, kind, generatorKind,
- tryAnnexB, directives, &newDirectives))
+ asyncKind, tryAnnexB, directives, &newDirectives))
{
break;
}
// Return on error.
if (tokenStream.hadError() || directives == newDirectives)
return null();
@@ -3109,16 +3184,17 @@ Parser<ParseHandler>::functionDefinition
template <>
bool
Parser<FullParseHandler>::trySyntaxParseInnerFunction(ParseNode* pn, HandleFunction fun,
InHandling inHandling,
YieldHandling yieldHandling,
FunctionSyntaxKind kind,
GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind,
bool tryAnnexB,
Directives inheritedDirectives,
Directives* newDirectives)
{
// Try a syntax parse for this inner function.
do {
// If we're assuming this function is an IIFE, always perform a full
// parse to avoid the overhead of a lazy syntax-only parse. Although
@@ -3138,17 +3214,17 @@ Parser<FullParseHandler>::trySyntaxParse
tokenStream.tell(&position);
if (!parser->tokenStream.seek(position, tokenStream))
return false;
// Make a FunctionBox before we enter the syntax parser, because |pn|
// still expects a FunctionBox to be attached to it during BCE, and
// the syntax parser cannot attach one to it.
FunctionBox* funbox = newFunctionBox(pn, fun, inheritedDirectives, generatorKind,
- tryAnnexB);
+ asyncKind, tryAnnexB);
if (!funbox)
return false;
funbox->initWithEnclosingParseContext(pc, kind);
if (!parser->innerFunction(SyntaxParseHandler::NodeGeneric, pc, funbox, inHandling,
yieldHandling, kind, inheritedDirectives, newDirectives))
{
if (parser->hadAbortedSyntaxParse()) {
@@ -3170,34 +3246,35 @@ Parser<FullParseHandler>::trySyntaxParse
return false;
// Update the end position of the parse node.
pn->pn_pos.end = tokenStream.currentToken().pos.end;
return true;
} while (false);
// We failed to do a syntax parse above, so do the full parse.
- return innerFunction(pn, pc, fun, inHandling, yieldHandling, kind, generatorKind, tryAnnexB,
- inheritedDirectives, newDirectives);
+ return innerFunction(pn, pc, fun, inHandling, yieldHandling, kind, generatorKind, asyncKind,
+ tryAnnexB, inheritedDirectives, newDirectives);
}
template <>
bool
Parser<SyntaxParseHandler>::trySyntaxParseInnerFunction(Node pn, HandleFunction fun,
InHandling inHandling,
YieldHandling yieldHandling,
FunctionSyntaxKind kind,
GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind,
bool tryAnnexB,
Directives inheritedDirectives,
Directives* newDirectives)
{
// This is already a syntax parser, so just parse the inner function.
- return innerFunction(pn, pc, fun, inHandling, yieldHandling, kind, generatorKind, tryAnnexB,
- inheritedDirectives, newDirectives);
+ return innerFunction(pn, pc, fun, inHandling, yieldHandling, kind, generatorKind, asyncKind,
+ tryAnnexB, inheritedDirectives, newDirectives);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::innerFunction(Node pn, ParseContext* outerpc, FunctionBox* funbox,
InHandling inHandling, YieldHandling yieldHandling,
FunctionSyntaxKind kind, Directives inheritedDirectives,
Directives* newDirectives)
@@ -3217,26 +3294,28 @@ Parser<ParseHandler>::innerFunction(Node
return leaveInnerFunction(outerpc);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::innerFunction(Node pn, ParseContext* outerpc, HandleFunction fun,
InHandling inHandling, YieldHandling yieldHandling,
- FunctionSyntaxKind kind, GeneratorKind generatorKind,
- bool tryAnnexB, Directives inheritedDirectives,
- Directives* newDirectives)
+ FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ bool tryAnnexB,
+ Directives inheritedDirectives, Directives* newDirectives)
{
// Note that it is possible for outerpc != this->pc, as we may be
// attempting to syntax parse an inner function from an outer full
// parser. In that case, outerpc is a ParseContext from the full parser
// instead of the current top of the stack of the syntax parser.
- FunctionBox* funbox = newFunctionBox(pn, fun, inheritedDirectives, generatorKind, tryAnnexB);
+ FunctionBox* funbox = newFunctionBox(pn, fun, inheritedDirectives, generatorKind,
+ asyncKind, tryAnnexB);
if (!funbox)
return false;
funbox->initWithEnclosingParseContext(outerpc, kind);
return innerFunction(pn, outerpc, funbox, inHandling, yieldHandling, kind, inheritedDirectives,
newDirectives);
}
@@ -3257,47 +3336,48 @@ Parser<ParseHandler>::appendToCallSiteOb
handler.addToCallSiteObject(callSiteObj, rawNode, cookedNode);
return true;
}
template <>
ParseNode*
Parser<FullParseHandler>::standaloneLazyFunction(HandleFunction fun, bool strict,
- GeneratorKind generatorKind)
+ GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind)
{
MOZ_ASSERT(checkOptionsCalled);
Node pn = handler.newFunctionDefinition();
if (!pn)
return null();
Directives directives(strict);
- FunctionBox* funbox = newFunctionBox(pn, fun, directives, generatorKind,
+ FunctionBox* funbox = newFunctionBox(pn, fun, directives, generatorKind, asyncKind,
/* tryAnnexB = */ false);
if (!funbox)
return null();
funbox->initFromLazyFunction();
Directives newDirectives = directives;
ParseContext funpc(this, funbox, &newDirectives);
if (!funpc.init())
return null();
// Our tokenStream has no current token, so pn's position is garbage.
// Substitute the position of the first token in our source. If the function
- // is an arrow, use TokenStream::Operand to keep verifyConsistentModifier
- // from complaining (we will use TokenStream::Operand in functionArguments).
- if (!tokenStream.peekTokenPos(&pn->pn_pos,
- fun->isArrow() ? TokenStream::Operand : TokenStream::None))
- {
- return null();
- }
-
- YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName;
+ // is a not-async arrow, use TokenStream::Operand to keep
+ // verifyConsistentModifier from complaining (we will use
+ // TokenStream::Operand in functionArguments).
+ TokenStream::Modifier modifier = (fun->isArrow() && asyncKind == SyncFunction)
+ ? TokenStream::Operand : TokenStream::None;
+ if (!tokenStream.peekTokenPos(&pn->pn_pos, modifier))
+ return null();
+
+ YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind);
FunctionSyntaxKind syntaxKind = Statement;
if (fun->isClassConstructor())
syntaxKind = ClassConstructor;
else if (fun->isMethod())
syntaxKind = Method;
else if (fun->isGetter())
syntaxKind = Getter;
else if (fun->isSetter())
@@ -3324,16 +3404,17 @@ Parser<ParseHandler>::functionFormalPara
{
// Given a properly initialized parse context, try to parse an actual
// function without concern for conversion to strict mode, use of lazy
// parsing and such.
FunctionBox* funbox = pc->functionBox();
RootedFunction fun(context, funbox->function());
+ AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, funbox->isAsync());
if (!functionArguments(yieldHandling, kind, pn))
return false;
Maybe<ParseContext::VarScope> varScope;
if (funbox->hasParameterExprs) {
varScope.emplace(this);
if (!varScope->init(pc))
return false;
@@ -3352,17 +3433,17 @@ Parser<ParseHandler>::functionFormalPara
}
// Parse the function body.
FunctionBodyType bodyType = StatementListBody;
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return false;
if (tt != TOK_LC) {
- if (funbox->isStarGenerator() || kind == Method ||
+ if ((funbox->isStarGenerator() && !funbox->isAsync()) || kind == Method ||
kind == GetterNoExpressionClosure || kind == SetterNoExpressionClosure ||
IsConstructorKind(kind)) {
report(ParseError, false, null(), JSMSG_CURLY_BEFORE_BODY);
return false;
}
if (kind != Arrow) {
#if JS_HAS_EXPR_CLOSURES
@@ -3382,19 +3463,17 @@ Parser<ParseHandler>::functionFormalPara
#endif
}
// Arrow function parameters inherit yieldHandling from the enclosing
// context, but the arrow body doesn't. E.g. in |(a = yield) => yield|,
// |yield| in the parameters is either a name or keyword, depending on
// whether the arrow function is enclosed in a generator function or not.
// Whereas the |yield| in the function body is always parsed as a name.
- YieldHandling bodyYieldHandling = pc->isGenerator() ? YieldIsKeyword : YieldIsName;
- MOZ_ASSERT_IF(yieldHandling != bodyYieldHandling, kind == Arrow);
-
+ YieldHandling bodyYieldHandling = GetYieldHandling(pc->generatorKind(), pc->asyncKind());
Node body = functionBody(inHandling, bodyYieldHandling, kind, bodyType);
if (!body)
return false;
if ((kind != Method && !IsConstructorKind(kind)) && fun->name()) {
RootedPropertyName propertyName(context, fun->name()->asPropertyName());
if (!checkStrictBinding(propertyName, handler.getPosition(pn)))
return false;
@@ -3430,17 +3509,18 @@ Parser<ParseHandler>::functionFormalPara
handler.setEndPosition(pn, pos().end);
handler.setFunctionBody(pn, body);
return true;
}
template <typename ParseHandler>
typename ParseHandler::Node
-Parser<ParseHandler>::functionStmt(YieldHandling yieldHandling, DefaultHandling defaultHandling)
+Parser<ParseHandler>::functionStmt(YieldHandling yieldHandling, DefaultHandling defaultHandling,
+ FunctionAsyncKind asyncKind)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION));
// Annex B.3.4 says we can parse function declarations unbraced under if
// or else as if it were braced. That is, |if (x) function f() {}| is
// parsed as |if (x) { function f() {} }|.
Maybe<ParseContext::Statement> synthesizedStmtForAnnexB;
Maybe<ParseContext::Scope> synthesizedScopeForAnnexB;
@@ -3450,22 +3530,26 @@ Parser<ParseHandler>::functionStmt(Yield
synthesizedStmtForAnnexB.emplace(pc, StatementKind::Block);
synthesizedScopeForAnnexB.emplace(this);
if (!synthesizedScopeForAnnexB->init(pc))
return null();
}
}
RootedPropertyName name(context);
- GeneratorKind generatorKind = NotGenerator;
+ GeneratorKind generatorKind = asyncKind == AsyncFunction ? StarGenerator : NotGenerator;
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
if (tt == TOK_MUL) {
+ if (asyncKind != SyncFunction) {
+ report(ParseError, false, null(), JSMSG_ASYNC_GENERATOR);
+ return null();
+ }
generatorKind = StarGenerator;
if (!tokenStream.getToken(&tt))
return null();
}
if (tt == TOK_NAME || tt == TOK_YIELD) {
name = bindingIdentifier(yieldHandling);
if (!name)
@@ -3474,62 +3558,68 @@ Parser<ParseHandler>::functionStmt(Yield
name = context->names().starDefaultStar;
tokenStream.ungetToken();
} else {
/* Unnamed function expressions are forbidden in statement context. */
report(ParseError, false, null(), JSMSG_UNNAMED_FUNCTION_STMT);
return null();
}
- YieldHandling newYieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName;
+ YieldHandling newYieldHandling = GetYieldHandling(generatorKind, asyncKind);
Node fun = functionDefinition(InAllowed, newYieldHandling, name, Statement, generatorKind,
- PredictUninvoked);
+ asyncKind, PredictUninvoked);
if (!fun)
return null();
if (synthesizedStmtForAnnexB) {
Node synthesizedStmtList = handler.newStatementList(handler.getPosition(fun));
if (!synthesizedStmtList)
return null();
handler.addStatementToList(synthesizedStmtList, fun);
return finishLexicalScope(*synthesizedScopeForAnnexB, synthesizedStmtList);
}
return fun;
}
template <typename ParseHandler>
typename ParseHandler::Node
-Parser<ParseHandler>::functionExpr(InvokedPrediction invoked)
+Parser<ParseHandler>::functionExpr(InvokedPrediction invoked, FunctionAsyncKind asyncKind)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION));
- GeneratorKind generatorKind = NotGenerator;
+ AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, asyncKind == AsyncFunction);
+ GeneratorKind generatorKind = asyncKind == AsyncFunction ? StarGenerator : NotGenerator;
TokenKind tt;
if (!tokenStream.getToken(&tt))
return null();
if (tt == TOK_MUL) {
+ if (asyncKind != SyncFunction) {
+ report(ParseError, false, null(), JSMSG_ASYNC_GENERATOR);
+ return null();
+ }
generatorKind = StarGenerator;
if (!tokenStream.getToken(&tt))
return null();
}
- YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName;
+ YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind);
RootedPropertyName name(context);
if (tt == TOK_NAME || tt == TOK_YIELD) {
name = bindingIdentifier(yieldHandling);
if (!name)
return null();
} else {
tokenStream.ungetToken();
}
- return functionDefinition(InAllowed, yieldHandling, name, Expression, generatorKind, invoked);
+ return functionDefinition(InAllowed, yieldHandling, name, Expression, generatorKind,
+ asyncKind, invoked);
}
/*
* Return true if this node, known to be an unparenthesized string literal,
* could be the string of a directive in a Directive Prologue. Directive
* strings never contain escape sequences or line continuations.
* isEscapeFreeStringLiteral, below, checks whether the node itself could be
* a directive.
@@ -4133,26 +4223,32 @@ Parser<ParseHandler>::destructuringDecla
if (!pattern || !checkDestructuringPattern(pattern, Some(kind), &possibleError))
return null();
return pattern;
}
template <typename ParseHandler>
typename ParseHandler::Node
-Parser<ParseHandler>::destructuringDeclarationWithoutYield(DeclarationKind kind,
- YieldHandling yieldHandling,
- TokenKind tt, unsigned msg)
+Parser<ParseHandler>::destructuringDeclarationWithoutYieldOrAwait(DeclarationKind kind,
+ YieldHandling yieldHandling,
+ TokenKind tt)
{
uint32_t startYieldOffset = pc->lastYieldOffset;
+ uint32_t startAwaitOffset = pc->lastAwaitOffset;
Node res = destructuringDeclaration(kind, yieldHandling, tt);
- if (res && pc->lastYieldOffset != startYieldOffset) {
- reportWithOffset(ParseError, false, pc->lastYieldOffset,
- msg, js_yield_str);
- return null();
+ if (res) {
+ if (pc->lastYieldOffset != startYieldOffset) {
+ reportWithOffset(ParseError, false, pc->lastYieldOffset, JSMSG_YIELD_IN_DEFAULT);
+ return null();
+ }
+ if (pc->lastAwaitOffset != startAwaitOffset) {
+ reportWithOffset(ParseError, false, pc->lastAwaitOffset, JSMSG_AWAIT_IN_DEFAULT);
+ return null();
+ }
}
return res;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::blockStatement(YieldHandling yieldHandling, unsigned errorNumber)
{
@@ -5004,16 +5100,30 @@ Parser<FullParseHandler>::exportDeclarat
return null();
break;
case TOK_CLASS:
kid = classDefinition(YieldIsKeyword, ClassStatement, AllowDefaultName);
if (!kid)
return null();
break;
default: {
+ if (tt == TOK_NAME && tokenStream.currentName() == context->names().async) {
+ TokenKind nextSameLine = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine))
+ return null();
+
+ if (nextSameLine == TOK_FUNCTION) {
+ tokenStream.consumeKnownToken(nextSameLine);
+ kid = functionStmt(YieldIsName, AllowDefaultName, AsyncFunction);
+ if (!kid)
+ return null();
+ break;
+ }
+ }
+
tokenStream.ungetToken();
RootedPropertyName name(context, context->names().starDefaultStar);
nameNode = newName(name);
if (!nameNode)
return null();
if (!noteDeclaredName(name, DeclarationKind::Const, pos()))
return null();
kid = assignExpr(InAllowed, YieldIsKeyword, TripledotProhibited);
@@ -5841,16 +5951,26 @@ Parser<ParseHandler>::newYieldExpression
return null();
if (isYieldStar)
return handler.newYieldStarExpression(begin, expr, generator);
return handler.newYieldExpression(begin, expr, generator);
}
template <typename ParseHandler>
typename ParseHandler::Node
+Parser<ParseHandler>::newAwaitExpression(uint32_t begin, typename ParseHandler::Node expr)
+{
+ Node generator = newDotGeneratorName();
+ if (!generator)
+ return null();
+ return handler.newAwaitExpression(begin, expr, generator);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
Parser<ParseHandler>::yieldExpression(InHandling inHandling)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_YIELD));
uint32_t begin = pos().begin;
switch (pc->generatorKind()) {
case StarGenerator:
{
@@ -6351,16 +6471,17 @@ JSOpFromPropertyType(PropertyType propTy
case PropertyType::GetterNoExpressionClosure:
return JSOP_INITPROP_GETTER;
case PropertyType::Setter:
case PropertyType::SetterNoExpressionClosure:
return JSOP_INITPROP_SETTER;
case PropertyType::Normal:
case PropertyType::Method:
case PropertyType::GeneratorMethod:
+ case PropertyType::AsyncMethod:
case PropertyType::Constructor:
case PropertyType::DerivedConstructor:
return JSOP_INITPROP;
default:
MOZ_CRASH("unexpected property type");
}
}
@@ -6372,32 +6493,44 @@ FunctionSyntaxKindFromPropertyType(Prope
return Getter;
case PropertyType::GetterNoExpressionClosure:
return GetterNoExpressionClosure;
case PropertyType::Setter:
return Setter;
case PropertyType::SetterNoExpressionClosure:
return SetterNoExpressionClosure;
case PropertyType::Method:
- return Method;
case PropertyType::GeneratorMethod:
+ case PropertyType::AsyncMethod:
return Method;
case PropertyType::Constructor:
return ClassConstructor;
case PropertyType::DerivedConstructor:
return DerivedClassConstructor;
default:
MOZ_CRASH("unexpected property type");
}
}
static GeneratorKind
GeneratorKindFromPropertyType(PropertyType propType)
{
- return propType == PropertyType::GeneratorMethod ? StarGenerator : NotGenerator;
+ if (propType == PropertyType::GeneratorMethod)
+ return StarGenerator;
+ if (propType == PropertyType::AsyncMethod)
+ return StarGenerator;
+ return NotGenerator;
+}
+
+static FunctionAsyncKind
+AsyncKindFromPropertyType(PropertyType propType)
+{
+ if (propType == PropertyType::AsyncMethod)
+ return AsyncFunction;
+ return SyncFunction;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling,
ClassContext classContext,
DefaultHandling defaultHandling)
{
@@ -6501,16 +6634,17 @@ Parser<ParseHandler>::classDefinition(Yi
PropertyType propType;
Node propName = propertyName(yieldHandling, classMethods, &propType, &propAtom);
if (!propName)
return null();
if (propType != PropertyType::Getter && propType != PropertyType::Setter &&
propType != PropertyType::Method && propType != PropertyType::GeneratorMethod &&
+ propType != PropertyType::AsyncMethod &&
propType != PropertyType::Constructor && propType != PropertyType::DerivedConstructor)
{
report(ParseError, false, null(), JSMSG_BAD_METHOD_DEF);
return null();
}
if (propType == PropertyType::Getter)
propType = PropertyType::GetterNoExpressionClosure;
@@ -6933,16 +7067,26 @@ Parser<ParseHandler>::statementListItem(
if (!tokenStream.currentToken().nameContainsEscape() &&
tokenStream.currentName() == context->names().let &&
nextTokenContinuesLetDeclaration(next, yieldHandling))
{
return lexicalDeclaration(yieldHandling, /* isConst = */ false);
}
+ if (tokenStream.currentName() == context->names().async) {
+ TokenKind nextSameLine = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine))
+ return null();
+ if (nextSameLine == TOK_FUNCTION) {
+ tokenStream.consumeKnownToken(TOK_FUNCTION);
+ return functionStmt(yieldHandling, NameRequired, AsyncFunction);
+ }
+ }
+
if (next == TOK_COLON)
return labeledStatement(yieldHandling);
return expressionStatement(yieldHandling);
}
case TOK_NEW:
return expressionStatement(yieldHandling, PredictInvoked);
@@ -7423,27 +7567,63 @@ Parser<ParseHandler>::assignExpr(InHandl
return null();
if (endsExpr)
return stringLiteral();
}
if (tt == TOK_YIELD && yieldExpressionsSupported())
return yieldExpression(inHandling);
+ bool maybeAsyncArrow = false;
+ if (tt == TOK_NAME && tokenStream.currentName() == context->names().async) {
+ TokenKind nextSameLine = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine))
+ return null();
+
+ if (nextSameLine == TOK_NAME || nextSameLine == TOK_YIELD)
+ maybeAsyncArrow = true;
+ }
+
tokenStream.ungetToken();
// Save the tokenizer state in case we find an arrow function and have to
// rewind.
TokenStream::Position start(keepAtoms);
tokenStream.tell(&start);
PossibleError possibleErrorInner(*this);
- Node lhs = condExpr1(inHandling, yieldHandling, tripledotHandling, &possibleErrorInner, invoked);
- if (!lhs)
- return null();
+ Node lhs;
+ if (maybeAsyncArrow) {
+ tokenStream.consumeKnownToken(TOK_NAME, TokenStream::Operand);
+ MOZ_ASSERT(tokenStream.currentName() == context->names().async);
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt))
+ return null();
+ MOZ_ASSERT(tt == TOK_NAME || tt == TOK_YIELD);
+
+ // Check yield validity here.
+ RootedPropertyName name(context, bindingIdentifier(yieldHandling));
+ if (!name)
+ return null();
+
+ if (!tokenStream.getToken(&tt))
+ return null();
+ if (tt != TOK_ARROW) {
+ report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+ "'=>' after argument list", TokenKindToDesc(tt));
+
+ return null();
+ }
+ } else {
+ lhs = condExpr1(inHandling, yieldHandling, tripledotHandling, &possibleErrorInner, invoked);
+ if (!lhs) {
+ return null();
+ }
+ }
ParseNodeKind kind;
JSOp op;
switch (tokenStream.currentToken().type) {
case TOK_ASSIGN: kind = PNK_ASSIGN; op = JSOP_NOP; break;
case TOK_ADDASSIGN: kind = PNK_ADDASSIGN; op = JSOP_ADD; break;
case TOK_SUBASSIGN: kind = PNK_SUBASSIGN; op = JSOP_SUB; break;
case TOK_BITORASSIGN: kind = PNK_BITORASSIGN; op = JSOP_BITOR; break;
@@ -7475,22 +7655,43 @@ Parser<ParseHandler>::assignExpr(InHandl
bool isBlock = false;
if (!tokenStream.peekToken(&next, TokenStream::Operand))
return null();
if (next == TOK_LC)
isBlock = true;
tokenStream.seek(start);
- TokenKind ignored;
- if (!tokenStream.peekToken(&ignored, TokenStream::Operand))
- return null();
+ if (!tokenStream.peekToken(&next, TokenStream::Operand))
+ return null();
+
+ GeneratorKind generatorKind = NotGenerator;
+ FunctionAsyncKind asyncKind = SyncFunction;
+
+ if (next == TOK_NAME) {
+ tokenStream.consumeKnownToken(next, TokenStream::Operand);
+
+ if (tokenStream.currentName() == context->names().async) {
+ TokenKind nextSameLine = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine))
+ return null();
+
+ if (nextSameLine == TOK_ARROW) {
+ tokenStream.ungetToken();
+ } else {
+ generatorKind = StarGenerator;
+ asyncKind = AsyncFunction;
+ }
+ } else {
+ tokenStream.ungetToken();
+ }
+ }
Node arrowFunc = functionDefinition(inHandling, yieldHandling, nullptr,
- Arrow, NotGenerator);
+ Arrow, generatorKind, asyncKind);
if (!arrowFunc)
return null();
if (isBlock) {
// This arrow function could be a non-trailing member of a comma
// expression or a semicolon terminating a full expression. If so,
// the next token is that comma/semicolon, gotten with None:
//
@@ -7509,16 +7710,17 @@ Parser<ParseHandler>::assignExpr(InHandl
// before Parser::expr() looks for a comma. Do so here, then
// immediately add the modifier exception needed for the first
// case.
//
// Note that the second case occurs *only* if the arrow function
// has block body. An arrow function not ending in such, ends in
// another AssignmentExpression that we can inductively assume was
// peeked consistently.
+ TokenKind ignored;
if (!tokenStream.peekToken(&ignored, TokenStream::Operand))
return null();
tokenStream.addModifierException(TokenStream::NoneIsOperand);
}
return arrowFunc;
}
default:
@@ -7749,16 +7951,31 @@ Parser<ParseHandler>::unaryExpr(YieldHan
if (!report(ParseStrictError, pc->sc()->strict(), expr, JSMSG_DEPRECATED_DELETE_OPERAND))
return null();
pc->sc()->setBindingsAccessedDynamically();
}
return handler.newDelete(begin, expr);
}
+ case TOK_AWAIT: {
+ TokenKind nextSameLine = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine, TokenStream::Operand))
+ return null();
+ if (nextSameLine != TOK_EOL) {
+ Node kid = unaryExpr(yieldHandling, tripledotHandling, possibleError, invoked);
+ if (!kid)
+ return null();
+ pc->lastAwaitOffset = begin;
+ return newAwaitExpression(begin, kid);
+ }
+ report(ParseError, false, null(), JSMSG_LINE_BREAK_AFTER_AWAIT);
+ return null();
+ }
+
default: {
Node pn = memberExpr(yieldHandling, tripledotHandling, tt, /* allowCallSyntax = */ true,
possibleError, invoked);
if (!pn)
return null();
/* Don't look across a newline boundary for a postfix incop. */
if (!tokenStream.peekTokenSameLine(&tt))
@@ -7806,23 +8023,23 @@ Parser<ParseHandler>::generatorComprehen
// be necessary.
RootedObject proto(context);
JSContext* cx = context->maybeJSContext();
proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, context->global());
if (!proto)
return null();
RootedFunction fun(context, newFunction(/* atom = */ nullptr, Expression,
- StarGenerator, proto));
+ StarGenerator, SyncFunction, proto));
if (!fun)
return null();
// Create box for fun->object early to root it.
Directives directives(/* strict = */ outerpc->sc()->strict());
- FunctionBox* genFunbox = newFunctionBox(genfn, fun, directives, StarGenerator,
+ FunctionBox* genFunbox = newFunctionBox(genfn, fun, directives, StarGenerator, SyncFunction,
/* tryAnnexB = */ false);
if (!genFunbox)
return null();
genFunbox->isGenexpLambda = true;
genFunbox->initWithEnclosingParseContext(outerpc, Expression);
ParseContext genpc(this, genFunbox, /* newDirectives = */ nullptr);
if (!genpc.init())
@@ -8078,24 +8295,30 @@ Parser<ParseHandler>::generatorComprehen
handler.setBeginPosition(result, begin);
handler.setEndPosition(result, pos().end);
return result;
}
template <typename ParseHandler>
typename ParseHandler::Node
-Parser<ParseHandler>::assignExprWithoutYield(YieldHandling yieldHandling, unsigned msg)
+Parser<ParseHandler>::assignExprWithoutYieldOrAwait(YieldHandling yieldHandling)
{
uint32_t startYieldOffset = pc->lastYieldOffset;
+ uint32_t startAwaitOffset = pc->lastAwaitOffset;
Node res = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
- if (res && pc->lastYieldOffset != startYieldOffset) {
- reportWithOffset(ParseError, false, pc->lastYieldOffset,
- msg, js_yield_str);
- return null();
+ if (res) {
+ if (pc->lastYieldOffset != startYieldOffset) {
+ reportWithOffset(ParseError, false, pc->lastYieldOffset, JSMSG_YIELD_IN_DEFAULT);
+ return null();
+ }
+ if (pc->lastAwaitOffset != startAwaitOffset) {
+ reportWithOffset(ParseError, false, pc->lastAwaitOffset, JSMSG_AWAIT_IN_DEFAULT);
+ return null();
+ }
}
return res;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::argumentList(YieldHandling yieldHandling, Node listNode, bool* isSpread)
{
@@ -8659,22 +8882,41 @@ Parser<ParseHandler>::propertyName(Yield
{
TokenKind ltok;
if (!tokenStream.getToken(<ok, TokenStream::KeywordIsName))
return null();
MOZ_ASSERT(ltok != TOK_RC, "caller should have handled TOK_RC");
bool isGenerator = false;
+ bool isAsync = false;
if (ltok == TOK_MUL) {
isGenerator = true;
if (!tokenStream.getToken(<ok, TokenStream::KeywordIsName))
return null();
}
+ if (ltok == TOK_NAME && tokenStream.currentName() == context->names().async) {
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName))
+ return null();
+ if (tt != TOK_LP && tt != TOK_COLON && tt != TOK_RC && tt != TOK_ASSIGN) {
+ isAsync = true;
+ ltok = tt;
+ } else {
+ tokenStream.ungetToken();
+ tokenStream.addModifierException(TokenStream::NoneIsKeywordIsName);
+ }
+ }
+
+ if (isAsync && isGenerator) {
+ report(ParseError, false, null(), JSMSG_ASYNC_GENERATOR);
+ return null();
+ }
+
propAtom.set(nullptr);
Node propName;
switch (ltok) {
case TOK_NUMBER:
propAtom.set(DoubleToAtom(context, tokenStream.currentToken().number()));
if (!propAtom.get())
return null();
propName = newNumber(tokenStream.currentToken());
@@ -8686,17 +8928,17 @@ Parser<ParseHandler>::propertyName(Yield
propName = computedPropertyName(yieldHandling, propList);
if (!propName)
return null();
break;
case TOK_NAME: {
propAtom.set(tokenStream.currentName());
// Do not look for accessor syntax on generators
- if (isGenerator ||
+ if (isGenerator || isAsync ||
!(propAtom.get() == context->names().get ||
propAtom.get() == context->names().set))
{
propName = handler.newObjectLiteralPropertyName(propAtom, pos());
if (!propName)
return null();
break;
}
@@ -8805,17 +9047,22 @@ Parser<ParseHandler>::propertyName(Yield
*propType = tt == TOK_ASSIGN ?
PropertyType::CoverInitializedName :
PropertyType::Shorthand;
return propName;
}
if (tt == TOK_LP) {
tokenStream.ungetToken();
- *propType = isGenerator ? PropertyType::GeneratorMethod : PropertyType::Method;
+ if (isGenerator)
+ *propType = PropertyType::GeneratorMethod;
+ else if (isAsync)
+ *propType = PropertyType::AsyncMethod;
+ else
+ *propType = PropertyType::Method;
return propName;
}
report(ParseError, false, null(), JSMSG_COLON_AFTER_ID);
return null();
}
template <typename ParseHandler>
@@ -9037,18 +9284,19 @@ Parser<ParseHandler>::objectLiteral(Yiel
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::methodDefinition(PropertyType propType, HandleAtom funName)
{
FunctionSyntaxKind kind = FunctionSyntaxKindFromPropertyType(propType);
GeneratorKind generatorKind = GeneratorKindFromPropertyType(propType);
- YieldHandling yieldHandling = generatorKind != NotGenerator ? YieldIsKeyword : YieldIsName;
- return functionDefinition(InAllowed, yieldHandling, funName, kind, generatorKind);
+ FunctionAsyncKind asyncKind = AsyncKindFromPropertyType(propType);
+ YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind);
+ return functionDefinition(InAllowed, yieldHandling, funName, kind, generatorKind, asyncKind);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::tryNewTarget(Node &newTarget)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_NEW));
@@ -9161,16 +9409,27 @@ Parser<ParseHandler>::primaryExpr(YieldH
case TOK_NO_SUBS_TEMPLATE:
return noSubstitutionTemplate();
case TOK_STRING:
return stringLiteral();
case TOK_YIELD:
case TOK_NAME: {
+ if (tokenStream.currentName() == context->names().async) {
+ TokenKind nextSameLine = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine))
+ return null();
+
+ if (nextSameLine == TOK_FUNCTION) {
+ tokenStream.consumeKnownToken(TOK_FUNCTION);
+ return functionExpr(PredictUninvoked, AsyncFunction);
+ }
+ }
+
Rooted<PropertyName*> name(context, identifierReference(yieldHandling));
if (!name)
return null();
return identifierReference(name);
}
case TOK_REGEXP:
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -310,16 +310,21 @@ class ParseContext : public Nestable<Par
bool superScopeNeedsHomeObject_;
public:
// lastYieldOffset stores the offset of the last yield that was parsed.
// NoYieldOffset is its initial value.
static const uint32_t NoYieldOffset = UINT32_MAX;
uint32_t lastYieldOffset;
+ // lastAwaitOffset stores the offset of the last await that was parsed.
+ // NoAwaitOffset is its initial value.
+ static const uint32_t NoAwaitOffset = UINT32_MAX;
+ uint32_t lastAwaitOffset;
+
// All inner functions in this context. Only used when syntax parsing.
Rooted<GCVector<JSFunction*, 8>> innerFunctionsForLazy;
// In a function context, points to a Directive struct that can be updated
// to reflect new directives encountered in the Directive Prologue that
// require reparsing the function. In global/module/generator-tail contexts,
// we don't need to reparse when encountering a DirectivePrologue so this
// pointer may be nullptr.
@@ -353,16 +358,17 @@ class ParseContext : public Nestable<Par
varScope_(nullptr),
innerFunctionBoxesForAnnexB_(prs->context->frontendCollectionPool()),
positionalFormalParameterNames_(prs->context->frontendCollectionPool()),
closedOverBindingsForLazy_(prs->context->frontendCollectionPool()),
scriptId_(prs->usedNames.nextScriptId()),
isStandaloneFunctionBody_(false),
superScopeNeedsHomeObject_(false),
lastYieldOffset(NoYieldOffset),
+ lastAwaitOffset(NoAwaitOffset),
innerFunctionsForLazy(prs->context, GCVector<JSFunction*, 8>(prs->context)),
newDirectives(newDirectives),
funHasReturnExpr(false),
funHasReturnVoid(false)
{
if (isFunctionBox()) {
if (functionBox()->function()->isNamedLambda())
namedLambdaScope_.emplace(prs);
@@ -493,16 +499,24 @@ class ParseContext : public Nestable<Par
bool isLegacyGenerator() const {
return generatorKind() == LegacyGenerator;
}
bool isStarGenerator() const {
return generatorKind() == StarGenerator;
}
+ bool isAsync() const {
+ return sc_->isFunctionBox() && sc_->asFunctionBox()->isAsync();
+ }
+
+ FunctionAsyncKind asyncKind() const {
+ return isAsync() ? AsyncFunction : SyncFunction;
+ }
+
bool isArrowFunction() const {
return sc_->isFunctionBox() && sc_->asFunctionBox()->function()->isArrow();
}
bool isMethod() const {
return sc_->isFunctionBox() && sc_->asFunctionBox()->function()->isMethod();
}
@@ -548,16 +562,17 @@ enum class PropertyType {
Shorthand,
CoverInitializedName,
Getter,
GetterNoExpressionClosure,
Setter,
SetterNoExpressionClosure,
Method,
GeneratorMethod,
+ AsyncMethod,
Constructor,
DerivedConstructor
};
// Specify a value for an ES6 grammar parametrization. We have no enum for
// [Return] because its behavior is exactly equivalent to checking whether
// we're in a function box -- easier and simpler than passing an extra
// parameter everywhere.
@@ -935,23 +950,25 @@ class Parser final : private JS::AutoGCR
Node parse();
/*
* Allocate a new parsed object or function container from
* cx->tempLifoAlloc.
*/
ObjectBox* newObjectBox(JSObject* obj);
FunctionBox* newFunctionBox(Node fn, JSFunction* fun, Directives directives,
- GeneratorKind generatorKind, bool tryAnnexB);
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ bool tryAnnexB);
/*
* Create a new function object given a name (which is optional if this is
* a function expression).
*/
- JSFunction* newFunction(HandleAtom atom, FunctionSyntaxKind kind, GeneratorKind generatorKind,
+ JSFunction* newFunction(HandleAtom atom, FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
HandleObject proto);
void trace(JSTracer* trc);
bool hadAbortedSyntaxParse() {
return abortedSyntaxParse;
}
void clearAbortedSyntaxParse() {
@@ -974,16 +991,17 @@ class Parser final : private JS::AutoGCR
bool appendToCallSiteObj(Node callSiteObj);
bool addExprAndGetNextTemplStrToken(YieldHandling yieldHandling, Node nodeList,
TokenKind* ttp);
bool checkStatementsEOF();
inline Node newName(PropertyName* name);
inline Node newName(PropertyName* name, TokenPos pos);
inline Node newYieldExpression(uint32_t begin, Node expr, bool isYieldStar = false);
+ inline Node newAwaitExpression(uint32_t begin, Node expr);
inline bool abortIfSyntaxParser();
public:
/* Public entry points for parsing. */
Node statement(YieldHandling yieldHandling);
Node statementListItem(YieldHandling yieldHandling, bool canHaveDirectives = false);
@@ -1000,39 +1018,41 @@ class Parser final : private JS::AutoGCR
Node globalBody(GlobalSharedContext* globalsc);
// Parse a module.
Node moduleBody(ModuleSharedContext* modulesc);
// Parse a function, given only its body. Used for the Function and
// Generator constructors.
Node standaloneFunctionBody(HandleFunction fun, HandleScope enclosingScope,
- Handle<PropertyNameVector> formals, GeneratorKind generatorKind,
+ Handle<PropertyNameVector> formals,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
Directives inheritedDirectives, Directives* newDirectives);
// Parse a function, given only its arguments and body. Used for lazily
// parsed functions.
- Node standaloneLazyFunction(HandleFunction fun, bool strict, GeneratorKind generatorKind);
+ Node standaloneLazyFunction(HandleFunction fun, bool strict,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind);
// Parse an inner function given an enclosing ParseContext and a
// FunctionBox for the inner function.
bool innerFunction(Node pn, ParseContext* outerpc, FunctionBox* funbox, InHandling inHandling,
YieldHandling yieldHandling, FunctionSyntaxKind kind,
Directives inheritedDirectives, Directives* newDirectives);
// Parse a function's formal parameters and its body assuming its function
// ParseContext is already on the stack.
bool functionFormalParametersAndBody(InHandling inHandling, YieldHandling yieldHandling,
Node pn, FunctionSyntaxKind kind);
// Determine whether |yield| is a valid name in the current context, or
// whether it's prohibited due to strictness, JS version, or occurrence
// inside a star generator.
bool yieldExpressionsSupported() {
- return versionNumber() >= JSVERSION_1_7 || pc->isGenerator();
+ return (versionNumber() >= JSVERSION_1_7 || pc->isGenerator()) && !pc->isAsync();
}
// Match the current token against the BindingIdentifier production with
// the given Yield parameter. If there is no match, report a syntax
// error.
PropertyName* bindingIdentifier(YieldHandling yieldHandling);
virtual bool strictMode() { return pc->sc()->strict(); }
@@ -1061,18 +1081,20 @@ class Parser final : private JS::AutoGCR
* Parsers whose name has a '1' suffix leave the TokenStream state
* pointing to the token one past the end of the parsed fragment. For a
* number of the parsers this is convenient and avoids a lot of
* unnecessary ungetting and regetting of tokens.
*
* Some parsers have two versions: an always-inlined version (with an 'i'
* suffix) and a never-inlined version (with an 'n' suffix).
*/
- Node functionStmt(YieldHandling yieldHandling, DefaultHandling defaultHandling);
- Node functionExpr(InvokedPrediction invoked = PredictUninvoked);
+ Node functionStmt(YieldHandling yieldHandling, DefaultHandling defaultHandling,
+ FunctionAsyncKind asyncKind = SyncFunction);
+ Node functionExpr(InvokedPrediction invoked = PredictUninvoked,
+ FunctionAsyncKind asyncKind = SyncFunction);
Node statementList(YieldHandling yieldHandling);
Node blockStatement(YieldHandling yieldHandling,
unsigned errorNumber = JSMSG_CURLY_IN_COMPOUND);
Node doWhileStatement(YieldHandling yieldHandling);
Node whileStatement(YieldHandling yieldHandling);
@@ -1167,17 +1189,17 @@ class Parser final : private JS::AutoGCR
Node* forInOrOfExpression);
Node expr(InHandling inHandling, YieldHandling yieldHandling,
TripledotHandling tripledotHandling, PossibleError* possibleError = nullptr,
InvokedPrediction invoked = PredictUninvoked);
Node assignExpr(InHandling inHandling, YieldHandling yieldHandling,
TripledotHandling tripledotHandling, PossibleError* possibleError = nullptr,
InvokedPrediction invoked = PredictUninvoked);
- Node assignExprWithoutYield(YieldHandling yieldHandling, unsigned err);
+ Node assignExprWithoutYieldOrAwait(YieldHandling yieldHandling);
Node yieldExpression(InHandling inHandling);
Node condExpr1(InHandling inHandling, YieldHandling yieldHandling,
TripledotHandling tripledotHandling,
PossibleError* possibleError,
InvokedPrediction invoked = PredictUninvoked);
Node orExpr1(InHandling inHandling, YieldHandling yieldHandling,
TripledotHandling tripledotHandling,
PossibleError* possibleError,
@@ -1202,17 +1224,18 @@ class Parser final : private JS::AutoGCR
/*
* Additional JS parsers.
*/
bool functionArguments(YieldHandling yieldHandling, FunctionSyntaxKind kind,
Node funcpn);
Node functionDefinition(InHandling inHandling, YieldHandling yieldHandling, HandleAtom name,
- FunctionSyntaxKind kind, GeneratorKind generatorKind,
+ FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
InvokedPrediction invoked = PredictUninvoked);
// Parse a function body. Pass StatementListBody if the body is a list of
// statements; pass ExpressionBody if the body is a single expression.
enum FunctionBodyType { StatementListBody, ExpressionBody };
Node functionBody(InHandling inHandling, YieldHandling yieldHandling, FunctionSyntaxKind kind,
FunctionBodyType type);
@@ -1227,18 +1250,18 @@ class Parser final : private JS::AutoGCR
Node comprehensionTail(GeneratorKind comprehensionKind);
Node comprehension(GeneratorKind comprehensionKind);
Node arrayComprehension(uint32_t begin);
Node generatorComprehension(uint32_t begin);
bool argumentList(YieldHandling yieldHandling, Node listNode, bool* isSpread);
Node destructuringDeclaration(DeclarationKind kind, YieldHandling yieldHandling,
TokenKind tt);
- Node destructuringDeclarationWithoutYield(DeclarationKind kind, YieldHandling yieldHandling,
- TokenKind tt, unsigned msg);
+ Node destructuringDeclarationWithoutYieldOrAwait(DeclarationKind kind, YieldHandling yieldHandling,
+ TokenKind tt);
bool namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet);
bool checkExportedName(JSAtom* exportName);
bool checkExportedNamesForDeclaration(Node node);
enum ClassContext { ClassStatement, ClassExpression };
Node classDefinition(YieldHandling yieldHandling, ClassContext classContext,
DefaultHandling defaultHandling);
@@ -1293,21 +1316,23 @@ class Parser final : private JS::AutoGCR
Node newDotGeneratorName();
bool declareDotGeneratorName();
bool checkFunctionDefinition(HandleAtom funAtom, Node pn, FunctionSyntaxKind kind,
GeneratorKind generatorKind, bool* tryAnnexB);
bool skipLazyInnerFunction(Node pn, FunctionSyntaxKind kind, bool tryAnnexB);
bool innerFunction(Node pn, ParseContext* outerpc, HandleFunction fun,
InHandling inHandling, YieldHandling yieldHandling,
- FunctionSyntaxKind kind, GeneratorKind generatorKind, bool tryAnnexB,
+ FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
Directives inheritedDirectives, Directives* newDirectives);
bool trySyntaxParseInnerFunction(Node pn, HandleFunction fun, InHandling inHandling,
YieldHandling yieldHandling, FunctionSyntaxKind kind,
- GeneratorKind generatorKind, bool tryAnnexB,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ bool tryAnnexB,
Directives inheritedDirectives, Directives* newDirectives);
bool finishFunctionScopes();
bool finishFunction();
bool leaveInnerFunction(ParseContext* outerpc);
public:
enum FunctionCallBehavior {
PermitAssignmentToFunctionCalls,
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -448,16 +448,18 @@ class FunctionBox : public ObjectBox, pu
ParseNode* functionNode; /* back pointer used by asm.js for error messages */
uint32_t bufStart;
uint32_t bufEnd;
uint32_t startLine;
uint32_t startColumn;
uint16_t length;
uint8_t generatorKindBits_; /* The GeneratorKind of this function. */
+ uint8_t asyncKindBits_; /* The FunctionAsyncKing of this function. */
+
bool isGenexpLambda:1; /* lambda from generator expression */
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 insideUseAsm:1; /* see useAsmOrInsideUseAsm */
bool isAnnexB:1; /* need to emit a synthesized Annex B assignment */
@@ -468,17 +470,18 @@ class FunctionBox : public ObjectBox, pu
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 */
FunctionContextFlags funCxFlags;
FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead, JSFunction* fun,
- Directives directives, bool extraWarnings, GeneratorKind generatorKind);
+ Directives directives, bool extraWarnings, GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind);
MutableHandle<LexicalScope::Data*> namedLambdaBindings() {
MOZ_ASSERT(context->compartment()->runtimeFromAnyThread()->keepAtoms());
return MutableHandle<LexicalScope::Data*>::fromMarkedLocation(&namedLambdaBindings_);
}
MutableHandle<FunctionScope::Data*> functionScopeBindings() {
MOZ_ASSERT(context->compartment()->runtimeFromAnyThread()->keepAtoms());
@@ -527,16 +530,18 @@ class FunctionBox : public ObjectBox, pu
bool isLikelyConstructorWrapper() const {
return usesArguments && usesApply && usesThis && !usesReturn;
}
GeneratorKind generatorKind() const { return GeneratorKindFromBits(generatorKindBits_); }
bool isGenerator() const { return generatorKind() != NotGenerator; }
bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; }
bool isStarGenerator() const { return generatorKind() == StarGenerator; }
+ FunctionAsyncKind asyncKind() const { return AsyncKindFromBits(asyncKindBits_); }
+ bool isAsync() const { return asyncKind() == AsyncFunction; }
bool isArrow() const { return function()->isArrow(); }
void setGeneratorKind(GeneratorKind kind) {
// A generator kind can be set at initialization, or when "yield" is
// first seen. In both cases the transition can only happen from
// NotGenerator.
MOZ_ASSERT(!isGenerator());
generatorKindBits_ = GeneratorKindAsBits(kind);
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -284,16 +284,17 @@ class SyntaxParseHandler
MOZ_MUST_USE bool addPrototypeMutation(Node literal, uint32_t begin, Node expr) { return true; }
MOZ_MUST_USE bool addPropertyDefinition(Node literal, Node name, Node expr) { return true; }
MOZ_MUST_USE bool addShorthand(Node literal, Node name, Node expr) { return true; }
MOZ_MUST_USE bool addObjectMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; }
MOZ_MUST_USE bool addClassMethodDefinition(Node literal, Node name, Node fn, JSOp op, bool isStatic) { return true; }
Node newYieldExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; }
Node newYieldStarExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; }
+ Node newAwaitExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; }
// Statements
Node newStatementList(const TokenPos& pos) { return NodeGeneric; }
void addStatementToList(Node list, Node stmt) {}
void addCaseStatementToList(Node list, Node stmt) {}
MOZ_MUST_USE bool prependInitialYield(Node stmtList, Node gen) { return true; }
Node newEmptyStatement(const TokenPos& pos) { return NodeEmptyStatement; }
--- a/js/src/frontend/TokenKind.h
+++ b/js/src/frontend/TokenKind.h
@@ -102,16 +102,17 @@
macro(NEW, "keyword 'new'") \
macro(DELETE, "keyword 'delete'") \
macro(TRY, "keyword 'try'") \
macro(CATCH, "keyword 'catch'") \
macro(FINALLY, "keyword 'finally'") \
macro(THROW, "keyword 'throw'") \
macro(DEBUGGER, "keyword 'debugger'") \
macro(YIELD, "keyword 'yield'") \
+ macro(AWAIT, "keyword 'await'") \
macro(EXPORT, "keyword 'export'") \
macro(IMPORT, "keyword 'import'") \
macro(CLASS, "keyword 'class'") \
macro(EXTENDS, "keyword 'extends'") \
macro(SUPER, "keyword 'super'") \
macro(RESERVED, "reserved keyword") \
/* reserved keywords in strict mode */ \
macro(STRICT_RESERVED, "reserved keyword") \
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -960,16 +960,22 @@ TokenStream::putIdentInTokenbuf(const ch
}
userbuf.setAddressOfNextRawChar(tmp);
return true;
}
bool
TokenStream::checkForKeyword(const KeywordInfo* kw, TokenKind* ttp)
{
+ if (!awaitIsKeyword && kw->tokentype == TOK_AWAIT) {
+ if (ttp)
+ *ttp = TOK_NAME;
+ return true;
+ }
+
if (kw->tokentype == TOK_RESERVED)
return reportError(JSMSG_RESERVED_ID, kw->chars);
if (kw->tokentype == TOK_STRICT_RESERVED)
return reportStrictModeError(JSMSG_RESERVED_ID, kw->chars);
// Working keyword.
*ttp = kw->tokentype;
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -27,16 +27,18 @@
#include "js/Vector.h"
#include "vm/RegExpObject.h"
struct KeywordInfo;
namespace js {
namespace frontend {
+class AutoAwaitIsKeyword;
+
struct TokenPos {
uint32_t begin; // Offset of the token's first char.
uint32_t end; // Offset of 1 past the token's last char.
TokenPos() {}
TokenPos(uint32_t begin, uint32_t end) : begin(begin), end(end) {}
// Return a TokenPos that covers left, right, and anything in between.
@@ -425,16 +427,19 @@ class MOZ_STACK_CLASS TokenStream
// token.
bool hitOOM:1; // Hit OOM.
Flags()
: isEOF(), isDirtyLine(), sawOctalEscape(), hadError(), hitOOM()
{}
};
+ bool awaitIsKeyword = false;
+ friend class AutoAwaitIsKeyword;
+
public:
typedef Token::Modifier Modifier;
static constexpr Modifier None = Token::None;
static constexpr Modifier Operand = Token::Operand;
static constexpr Modifier KeywordIsName = Token::KeywordIsName;
static constexpr Modifier TemplateTail = Token::TemplateTail;
typedef Token::ModifierException ModifierException;
@@ -1010,16 +1015,35 @@ class MOZ_STACK_CLASS TokenStream
UniqueTwoByteChars sourceMapURL_; // source map's filename or null
CharBuffer tokenbuf; // current token string buffer
uint8_t isExprEnding[TOK_LIMIT];// which tokens definitely terminate exprs?
ExclusiveContext* const cx;
bool mutedErrors;
StrictModeGetter* strictModeGetter; // used to test for strict mode
};
+class MOZ_STACK_CLASS AutoAwaitIsKeyword
+{
+private:
+ TokenStream* ts_;
+ bool oldAwaitIsKeyword_;
+
+public:
+ AutoAwaitIsKeyword(TokenStream* ts, bool awaitIsKeyword) {
+ ts_ = ts;
+ oldAwaitIsKeyword_ = ts_->awaitIsKeyword;
+ ts_->awaitIsKeyword = awaitIsKeyword;
+ }
+
+ ~AutoAwaitIsKeyword() {
+ ts_->awaitIsKeyword = oldAwaitIsKeyword_;
+ ts_ = nullptr;
+ }
+};
+
extern const char*
TokenKindToDesc(TokenKind tt);
} // namespace frontend
} // namespace js
extern JS_FRIEND_API(int)
js_fgets(char* buf, int size, FILE* file);
--- a/js/src/jit-test/lib/syntax.js
+++ b/js/src/jit-test/lib/syntax.js
@@ -1176,17 +1176,17 @@ function test_syntax(postfixes, check_er
test("for each (var x in y) ");
test("for each (let ");
test("for each (let x ");
test("for each (let x in ");
test("for each (let x in y ");
test("for each (let x in y) ");
- // asm.js
+ // ==== asm.js ====
test("(function() { 'use asm'; ");
test("(function() { 'use asm'; var ");
test("(function() { 'use asm'; var a ");
test("(function() { 'use asm'; var a = ");
test("(function() { 'use asm'; var a = 1 ");
test("(function() { 'use asm'; var a = 1; ");
test("(function() { 'use asm'; var a = 1; function ");
@@ -1203,9 +1203,154 @@ function test_syntax(postfixes, check_er
test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f] ");
test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; ");
test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return ");
test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return f ");
test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return f; ");
test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return f; } ");
test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return f; }) ");
test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return f; }); ");
+
+ // ==== async/await ====
+
+ // async/await function decralation
+
+ test("async ");
+ test("async function ");
+ test("async function A ");
+ test("async function A( ");
+ test("async function A() ");
+ test("async function A(a ");
+ test("async function A(a) ");
+ test("async function A(a) { ");
+ test("async function A(a) {} ");
+ test("async function A(a) { await ");
+ test("async function A(a) { await X ");
+ test("async function A(a) { await X; ");
+ test("async function A(a) { await X; } ");
+ test("async function A(a) { await await ");
+ test("async function A(a) { await await await ");
+ test("async function A(a) { await await await X ");
+ test("async function A(a) { await await await X; ");
+ test("async function A(a) { await await await X; } ");
+
+ opts = { no_fun: true, no_eval: true, module: true };
+ test("export default async ", opts);
+ test("export default async function ", opts);
+ test("export default async function ( ", opts);
+ test("export default async function () ", opts);
+ test("export default async function (a ", opts);
+ test("export default async function (a) ", opts);
+ test("export default async function (a) { ", opts);
+ test("export default async function (a) {} ", opts);
+ test("export default async function (a) { await ", opts);
+ test("export default async function (a) { await X ", opts);
+ test("export default async function (a) { await X; ", opts);
+ test("export default async function (a) { await X; } ", opts);
+
+ // async/await function expression
+
+ test("(async ");
+ test("(async function ");
+ test("(async function A ");
+ test("(async function A( ");
+ test("(async function A() ");
+ test("(async function A(a ");
+ test("(async function A(a) ");
+ test("(async function A(a) { ");
+ test("(async function A(a) {} ");
+ test("(async function A(a) { await ");
+ test("(async function A(a) { await X ");
+ test("(async function A(a) { await X; ");
+ test("(async function A(a) { await X; } ");
+ test("(async function A(a) { await X; }) ");
+
+ test("(async function ( ");
+ test("(async function () ");
+ test("(async function (a ");
+ test("(async function (a) ");
+ test("(async function (a) { ");
+ test("(async function (a) {} ");
+ test("(async function (a) { await ");
+ test("(async function (a) { await X ");
+ test("(async function (a) { await X; ");
+ test("(async function (a) { await X; } ");
+ test("(async function (a) { await X; }) ");
+
+ // async/await method
+
+ test("({ async ");
+ test("({ async m ");
+ test("({ async m( ");
+ test("({ async m() ");
+ test("({ async m() { ");
+ test("({ async m() {} ");
+ test("({ async m() {}, ");
+
+ test("class X { async ");
+ test("class X { async m ");
+ test("class X { async m( ");
+ test("class X { async m() ");
+ test("class X { async m() { ");
+ test("class X { async m() {} ");
+
+ test("class X { static async ");
+ test("class X { static async m ");
+ test("class X { static async m( ");
+ test("class X { static async m() ");
+ test("class X { static async m() { ");
+ test("class X { static async m() {} ");
+
+ // async/await arrow
+
+ test("(async a ");
+ test("(async a => ");
+ test("(async a => b ");
+ test("(async a => b) ");
+
+ test("(async a => { ");
+ test("(async a => { b ");
+ test("(async a => { b } ");
+ test("(async a => { b }) ");
+
+ test("(async ( ");
+ test("(async (a ");
+ test("(async (a) ");
+ test("(async (a) => ");
+ test("(async (a) => b ");
+ test("(async (a) => b) ");
+ test("(async (a, ");
+ test("(async (a, b ");
+ test("(async (a, b) ");
+ test("(async (a, b) => ");
+ test("(async (a, b) => b ");
+ test("(async (a, b) => b) ");
+
+ test("(async ([ ");
+ test("(async ([a ");
+ test("(async ([a] ");
+ test("(async ([a]) ");
+ test("(async ([a]) => ");
+ test("(async ([a]) => b ");
+ test("(async ([a]) => b) ");
+ test("(async ([a, ");
+ test("(async ([a, b ");
+ test("(async ([a, b] ");
+ test("(async ([a, b]) ");
+ test("(async ([a, b]) => ");
+ test("(async ([a, b]) => b ");
+ test("(async ([a, b]) => b) ");
+
+ test("(async ({ ");
+ test("(async ({a ");
+ test("(async ({a} ");
+ test("(async ({a}) ");
+ test("(async ({a}) => ");
+ test("(async ({a}) => b ");
+ test("(async ({a}) => b) ");
+ test("(async ({a, ");
+ test("(async ({a, b ");
+ test("(async ({a, b} ");
+ test("(async ({a, b}) ");
+ test("(async ({a, b}) => ");
+ test("(async ({a, b}) => b ");
+ test("(async ({a, b}) => b) ");
}
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -2745,25 +2745,24 @@ BaselineCompiler::emit_JSOP_DEFLET()
typedef bool (*DefFunOperationFn)(JSContext*, HandleScript, HandleObject, HandleFunction);
static const VMFunction DefFunOperationInfo =
FunctionInfo<DefFunOperationFn>(DefFunOperation, "DefFunOperation");
bool
BaselineCompiler::emit_JSOP_DEFFUN()
{
- RootedFunction fun(cx, script->getFunction(GET_UINT32_INDEX(pc)));
-
- frame.syncStack(0);
- masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg());
+ frame.popRegsAndSync(1);
+ masm.unboxObject(R0, R0.scratchReg());
+ masm.loadPtr(frame.addressOfEnvironmentChain(), R1.scratchReg());
prepareVMCall();
- pushArg(ImmGCPtr(fun));
pushArg(R0.scratchReg());
+ pushArg(R1.scratchReg());
pushArg(ImmGCPtr(script));
return callVM(DefFunOperationInfo);
}
typedef bool (*InitPropGetterSetterFn)(JSContext*, jsbytecode*, HandleObject, HandlePropertyName,
HandleObject);
static const VMFunction InitPropGetterSetterInfo =
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -2429,17 +2429,18 @@ CodeGenerator::visitLambda(LLambda* lir)
MOZ_ASSERT(!info.singletonType);
masm.createGCObject(output, tempReg, info.fun, gc::DefaultHeap, ool->entry());
emitLambdaInit(output, envChain, info);
if (info.flags & JSFunction::EXTENDED) {
- MOZ_ASSERT(info.fun->allowSuperProperty() || info.fun->isSelfHostedBuiltin());
+ MOZ_ASSERT(info.fun->allowSuperProperty() || info.fun->isSelfHostedBuiltin() ||
+ info.fun->isAsync());
static_assert(FunctionExtended::NUM_EXTENDED_SLOTS == 2, "All slots must be initialized");
masm.storeValue(UndefinedValue(), Address(output, FunctionExtended::offsetOfExtendedSlot(0)));
masm.storeValue(UndefinedValue(), Address(output, FunctionExtended::offsetOfExtendedSlot(1)));
}
masm.bind(ool->rejoin());
}
@@ -4689,17 +4690,18 @@ typedef bool (*DefFunOperationFn)(JSCont
static const VMFunction DefFunOperationInfo =
FunctionInfo<DefFunOperationFn>(DefFunOperation, "DefFunOperation");
void
CodeGenerator::visitDefFun(LDefFun* lir)
{
Register envChain = ToRegister(lir->environmentChain());
- pushArg(ImmGCPtr(lir->mir()->fun()));
+ Register fun = ToRegister(lir->fun());
+ pushArg(fun);
pushArg(envChain);
pushArg(ImmGCPtr(current->mir()->info().script()));
callVM(DefFunOperationInfo, lir);
}
typedef bool (*CheckOverRecursedFn)(JSContext*);
static const VMFunction CheckOverRecursedInfo =
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -13437,23 +13437,19 @@ IonBuilder::jsop_deflexical(uint32_t ind
current->add(deflex);
return resumeAfter(deflex);
}
bool
IonBuilder::jsop_deffun(uint32_t index)
{
- JSFunction* fun = script()->getFunction(index);
- if (IsAsmJSModule(fun))
- return abort("asm.js module function");
-
MOZ_ASSERT(analysis().usesEnvironmentChain());
- MDefFun* deffun = MDefFun::New(alloc(), fun, current->environmentChain());
+ MDefFun* deffun = MDefFun::New(alloc(), current->pop(), current->environmentChain());
current->add(deffun);
return resumeAfter(deffun);
}
bool
IonBuilder::jsop_throwsetconst()
{
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -202,17 +202,21 @@ LIRGenerator::visitDefLexical(MDefLexica
LDefLexical* lir = new(alloc()) LDefLexical();
add(lir, ins);
assignSafepoint(lir, ins);
}
void
LIRGenerator::visitDefFun(MDefFun* ins)
{
- LDefFun* lir = new(alloc()) LDefFun(useRegisterAtStart(ins->environmentChain()));
+ MDefinition* fun = ins->fun();
+ MOZ_ASSERT(fun->type() == MIRType::Object);
+
+ LDefFun* lir = new(alloc()) LDefFun(useRegisterAtStart(fun),
+ useRegisterAtStart(ins->environmentChain()));
add(lir, ins);
assignSafepoint(lir, ins);
}
void
LIRGenerator::visitNewArray(MNewArray* ins)
{
LNewArray* lir = new(alloc()) LNewArray(temp());
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -8101,41 +8101,32 @@ class MDefLexical
return attrs_;
}
bool appendRoots(MRootList& roots) const override {
return roots.append(name_);
}
};
class MDefFun
- : public MUnaryInstruction,
- public NoTypePolicy::Data
-{
- CompilerFunction fun_;
-
+ : public MBinaryInstruction,
+ public ObjectPolicy<0>::Data
+{
private:
- MDefFun(JSFunction* fun, MDefinition* envChain)
- : MUnaryInstruction(envChain),
- fun_(fun)
+ MDefFun(MDefinition* fun, MDefinition* envChain)
+ : MBinaryInstruction(fun, envChain)
{}
public:
INSTRUCTION_HEADER(DefFun)
TRIVIAL_NEW_WRAPPERS
- NAMED_OPERANDS((0, environmentChain))
-
- JSFunction* fun() const {
- return fun_;
- }
+ NAMED_OPERANDS((0, fun), (1, environmentChain))
+
bool possiblyCalls() const override {
return true;
}
- bool appendRoots(MRootList& roots) const override {
- return roots.append(fun_);
- }
};
class MRegExp : public MNullaryInstruction
{
CompilerGCPointer<RegExpObject*> source_;
bool mustClone_;
MRegExp(CompilerConstraintList* constraints, RegExpObject* source)
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -1545,28 +1545,32 @@ class LDefLexical : public LCallInstruct
public:
LIR_HEADER(DefLexical)
MDefLexical* mir() const {
return mir_->toDefLexical();
}
};
-class LDefFun : public LCallInstructionHelper<0, 1, 0>
+class LDefFun : public LCallInstructionHelper<0, 2, 0>
{
public:
LIR_HEADER(DefFun)
- explicit LDefFun(const LAllocation& envChain)
- {
- setOperand(0, envChain);
- }
-
+ LDefFun(const LAllocation& fun, const LAllocation& envChain)
+ {
+ setOperand(0, fun);
+ setOperand(1, envChain);
+ }
+
+ const LAllocation* fun() {
+ return getOperand(0);
+ }
const LAllocation* environmentChain() {
- return getOperand(0);
+ return getOperand(1);
}
MDefFun* mir() const {
return mir_->toDefFun();
}
};
class LTypeOfV : public LInstructionHelper<1, BOX_PIECES, 1>
{
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -176,16 +176,18 @@ MSG_DEF(JSMSG_UNCAUGHT_EXCEPTION, 1
MSG_DEF(JSMSG_UNKNOWN_FORMAT, 1, JSEXN_INTERNALERR, "unknown bytecode format {0}")
// Frontend
MSG_DEF(JSMSG_ACCESSOR_WRONG_ARGS, 3, JSEXN_SYNTAXERR, "{0} functions must have {1} argument{2}")
MSG_DEF(JSMSG_ARRAY_COMP_LEFTSIDE, 0, JSEXN_SYNTAXERR, "invalid array comprehension left-hand side")
MSG_DEF(JSMSG_ARRAY_INIT_TOO_BIG, 0, JSEXN_INTERNALERR, "array initializer too large")
MSG_DEF(JSMSG_AS_AFTER_IMPORT_STAR, 0, JSEXN_SYNTAXERR, "missing keyword 'as' after import *")
MSG_DEF(JSMSG_AS_AFTER_RESERVED_WORD, 1, JSEXN_SYNTAXERR, "missing keyword 'as' after reserved word '{0}'")
+MSG_DEF(JSMSG_ASYNC_GENERATOR, 0, JSEXN_SYNTAXERR, "generator function or method can't be async")
+MSG_DEF(JSMSG_AWAIT_IN_DEFAULT, 0, JSEXN_SYNTAXERR, "await can't be used in default expression")
MSG_DEF(JSMSG_BAD_ANON_GENERATOR_RETURN, 0, JSEXN_TYPEERR, "anonymous generator function returns a value")
MSG_DEF(JSMSG_BAD_ARROW_ARGS, 0, JSEXN_SYNTAXERR, "invalid arrow-function arguments (parentheses around the arrow-function may help)")
MSG_DEF(JSMSG_BAD_BINDING, 1, JSEXN_SYNTAXERR, "redefining {0} is deprecated")
MSG_DEF(JSMSG_BAD_CONST_DECL, 0, JSEXN_SYNTAXERR, "missing = in const declaration")
MSG_DEF(JSMSG_BAD_CONTINUE, 0, JSEXN_SYNTAXERR, "continue must be inside loop")
MSG_DEF(JSMSG_BAD_DESTRUCT_ASS, 0, JSEXN_REFERENCEERR, "invalid destructuring assignment operator")
MSG_DEF(JSMSG_BAD_DESTRUCT_TARGET, 0, JSEXN_SYNTAXERR, "invalid destructuring target")
MSG_DEF(JSMSG_BAD_DESTRUCT_PARENS, 0, JSEXN_SYNTAXERR, "destructuring patterns in assignments can't be parenthesized")
@@ -257,16 +259,17 @@ MSG_DEF(JSMSG_GARBAGE_AFTER_INPUT, 2
MSG_DEF(JSMSG_IDSTART_AFTER_NUMBER, 0, JSEXN_SYNTAXERR, "identifier starts immediately after numeric literal")
MSG_DEF(JSMSG_ILLEGAL_CHARACTER, 0, JSEXN_SYNTAXERR, "illegal character")
MSG_DEF(JSMSG_IMPORT_DECL_AT_TOP_LEVEL, 0, JSEXN_SYNTAXERR, "import declarations may only appear at top level of a module")
MSG_DEF(JSMSG_INVALID_FOR_IN_DECL_WITH_INIT,0,JSEXN_SYNTAXERR,"for-in loop head declarations may not have initializers")
MSG_DEF(JSMSG_LABEL_NOT_FOUND, 0, JSEXN_SYNTAXERR, "label not found")
MSG_DEF(JSMSG_LET_COMP_BINDING, 0, JSEXN_SYNTAXERR, "'let' is not a valid name for a comprehension variable")
MSG_DEF(JSMSG_LEXICAL_DECL_NOT_IN_BLOCK, 1, JSEXN_SYNTAXERR, "{0} declaration not directly within block")
MSG_DEF(JSMSG_LEXICAL_DECL_LABEL, 1, JSEXN_SYNTAXERR, "{0} declarations cannot be labelled")
+MSG_DEF(JSMSG_LINE_BREAK_AFTER_AWAIT, 0, JSEXN_SYNTAXERR, "no line break is allowed after 'await'")
MSG_DEF(JSMSG_GENERATOR_LABEL, 0, JSEXN_SYNTAXERR, "generator functions cannot be labelled")
MSG_DEF(JSMSG_FUNCTION_LABEL, 0, JSEXN_SYNTAXERR, "functions cannot be labelled")
MSG_DEF(JSMSG_SLOPPY_FUNCTION_LABEL, 0, JSEXN_SYNTAXERR, "functions can only be labelled inside blocks")
MSG_DEF(JSMSG_LINE_BREAK_AFTER_THROW, 0, JSEXN_SYNTAXERR, "no line break is allowed between 'throw' and its expression")
MSG_DEF(JSMSG_LINE_BREAK_BEFORE_ARROW, 0, JSEXN_SYNTAXERR, "no line break is allowed before '=>'")
MSG_DEF(JSMSG_MALFORMED_ESCAPE, 1, JSEXN_SYNTAXERR, "malformed {0} character escape sequence")
MSG_DEF(JSMSG_MISSING_BINARY_DIGITS, 0, JSEXN_SYNTAXERR, "missing binary digits after '0b'")
MSG_DEF(JSMSG_MISSING_EXPONENT, 0, JSEXN_SYNTAXERR, "missing exponent")
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -34,19 +34,21 @@
#include "frontend/TokenStream.h"
#include "gc/Marking.h"
#include "gc/Policy.h"
#include "jit/InlinableNatives.h"
#include "jit/Ion.h"
#include "jit/JitFrameIterator.h"
#include "js/CallNonGenericMethod.h"
#include "js/Proxy.h"
+#include "vm/AsyncFunction.h"
#include "vm/Debugger.h"
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
+#include "vm/SelfHosting.h"
#include "vm/Shape.h"
#include "vm/SharedImmutableStringsCache.h"
#include "vm/StringBuffer.h"
#include "vm/WrapperObject.h"
#include "vm/Xdr.h"
#include "jsscriptinlines.h"
@@ -980,44 +982,59 @@ js::FunctionToString(JSContext* cx, Hand
if (fun->isInterpretedLazy() && !fun->getOrCreateScript(cx))
return nullptr;
if (IsAsmJSModule(fun))
return AsmJSModuleToString(cx, fun, !lambdaParen);
if (IsAsmJSFunction(fun))
return AsmJSFunctionToString(cx, fun);
+ if (IsWrappedAsyncFunction(cx, fun)) {
+ RootedFunction unwrapped(cx, GetUnwrappedAsyncFunction(fun));
+ return FunctionToString(cx, unwrapped, lambdaParen);
+ }
+
StringBuffer out(cx);
RootedScript script(cx);
if (fun->hasScript()) {
script = fun->nonLazyScript();
if (script->isGeneratorExp()) {
if (!out.append("function genexp() {") ||
!out.append("\n [generator expression]\n") ||
!out.append("}"))
{
return nullptr;
}
return out.finishString();
}
}
+ if (fun->isAsync()) {
+ if (!out.append("async "))
+ return nullptr;
+ }
+
bool funIsMethodOrNonArrowLambda = (fun->isLambda() && !fun->isArrow()) || fun->isMethod() ||
fun->isGetter() || fun->isSetter();
// If we're not in pretty mode, put parentheses around lambda functions and methods.
if (fun->isInterpreted() && !lambdaParen && funIsMethodOrNonArrowLambda &&
!fun->isSelfHostedBuiltin())
{
if (!out.append("("))
return nullptr;
}
if (!fun->isArrow()) {
- if (!(fun->isStarGenerator() ? out.append("function* ") : out.append("function ")))
+ bool ok;
+ if (fun->isStarGenerator() && !fun->isAsync())
+ ok = out.append("function* ");
+ else
+ ok = out.append("function ");
+ if (!ok)
return nullptr;
}
if (fun->name()) {
if (!out.append(fun->name()))
return nullptr;
}
bool haveSource = fun->isInterpreted() && !fun->isSelfHostedBuiltin();
@@ -1653,40 +1670,46 @@ const JSFunctionSpec js::function_method
JS_FN(js_call_str, fun_call, 1,0),
JS_FN("isGenerator", fun_isGenerator,0,0),
JS_SELF_HOSTED_FN("bind", "FunctionBind", 2, JSFUN_HAS_REST),
JS_SYM_FN(hasInstance, fun_symbolHasInstance, 1, JSPROP_READONLY | JSPROP_PERMANENT),
JS_FS_END
};
static bool
-FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind generatorKind)
+FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind)
{
CallArgs args = CallArgsFromVp(argc, vp);
/* Block this call if security callbacks forbid it. */
Rooted<GlobalObject*> global(cx, &args.callee().global());
if (!GlobalObject::isRuntimeCodeGenEnabled(cx, global)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_FUNCTION);
return false;
}
bool isStarGenerator = generatorKind == StarGenerator;
+ bool isAsync = asyncKind == AsyncFunction;
MOZ_ASSERT(generatorKind != LegacyGenerator);
+ MOZ_ASSERT_IF(isAsync, isStarGenerator);
+ MOZ_ASSERT_IF(!isStarGenerator, !isAsync);
RootedScript maybeScript(cx);
const char* filename;
unsigned lineno;
bool mutedErrors;
uint32_t pcOffset;
DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
&mutedErrors);
const char* introductionType = "Function";
- if (generatorKind != NotGenerator)
+ if (isAsync)
+ introductionType = "AsyncFunction";
+ else if (generatorKind != NotGenerator)
introductionType = "GeneratorFunction";
const char* introducerFilename = filename;
if (maybeScript && maybeScript->scriptSource()->introducerFilename())
introducerFilename = maybeScript->scriptSource()->introducerFilename();
CompileOptions options(cx);
options.setMutedErrors(mutedErrors)
@@ -1770,29 +1793,32 @@ FunctionConstructor(JSContext* cx, unsig
* NB: (new Function) is not lexically closed by its caller, it's just an
* anonymous function in the top-level scope that its constructor inhabits.
* Thus 'var x = 42; f = new Function("return x"); print(f())' prints 42,
* and so would a call to f from another top-level's script or function.
*/
RootedAtom anonymousAtom(cx, cx->names().anonymous);
RootedObject proto(cx);
if (isStarGenerator) {
+ // Unwrapped function of async function should use GeneratorFunction,
+ // while wrapped function isn't generator.
proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, global);
if (!proto)
return false;
} else {
if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
return false;
}
RootedObject globalLexical(cx, &global->lexicalEnvironment());
+ AllocKind allocKind = isAsync ? AllocKind::FUNCTION_EXTENDED : AllocKind::FUNCTION;
RootedFunction fun(cx, NewFunctionWithProto(cx, nullptr, 0,
JSFunction::INTERPRETED_LAMBDA, globalLexical,
anonymousAtom, proto,
- AllocKind::FUNCTION, TenuredObject));
+ allocKind, TenuredObject));
if (!fun)
return false;
if (!JSFunction::setTypeForScriptedFunction(cx, fun))
return false;
AutoStableStringChars stableChars(cx);
if (!stableChars.initTwoByte(cx, bodyText))
@@ -1870,34 +1896,49 @@ FunctionConstructor(JSContext* cx, unsig
fun->setHasRest();
mozilla::Range<const char16_t> chars = stableChars.twoByteRange();
SourceBufferHolder::Ownership ownership = stableChars.maybeGiveOwnershipToCaller()
? SourceBufferHolder::GiveOwnership
: SourceBufferHolder::NoOwnership;
bool ok;
SourceBufferHolder srcBuf(chars.start().get(), chars.length(), ownership);
- if (isStarGenerator)
+ if (isAsync)
+ ok = frontend::CompileAsyncFunctionBody(cx, &fun, options, formals, srcBuf);
+ else if (isStarGenerator)
ok = frontend::CompileStarGeneratorBody(cx, &fun, options, formals, srcBuf);
else
ok = frontend::CompileFunctionBody(cx, &fun, options, formals, srcBuf);
args.rval().setObject(*fun);
return ok;
}
bool
js::Function(JSContext* cx, unsigned argc, Value* vp)
{
- return FunctionConstructor(cx, argc, vp, NotGenerator);
+ return FunctionConstructor(cx, argc, vp, NotGenerator, SyncFunction);
}
bool
js::Generator(JSContext* cx, unsigned argc, Value* vp)
{
- return FunctionConstructor(cx, argc, vp, StarGenerator);
+ return FunctionConstructor(cx, argc, vp, StarGenerator, SyncFunction);
+}
+
+bool
+js::AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!FunctionConstructor(cx, argc, vp, StarGenerator, AsyncFunction))
+ return false;
+
+ FixedInvokeArgs<1> args2(cx);
+ args2[0].set(args.rval());
+ return CallSelfHostedFunction(cx, cx->names().AsyncFunction_wrap,
+ NullHandleValue, args2, args.rval());
}
bool
JSFunction::isBuiltinFunctionConstructor()
{
return maybeNative() == Function || maybeNative() == Generator;
}
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -296,16 +296,23 @@ class JSFunction : public js::NativeObje
void setResolvedLength() {
flags_ |= RESOLVED_LENGTH;
}
void setResolvedName() {
flags_ |= RESOLVED_NAME;
}
+ void setAsyncKind(js::FunctionAsyncKind asyncKind) {
+ if (isInterpretedLazy())
+ lazyScript()->setAsyncKind(asyncKind);
+ else
+ nonLazyScript()->setAsyncKind(asyncKind);
+ }
+
bool getUnresolvedLength(JSContext* cx, js::MutableHandleValue v);
JSAtom* getUnresolvedName(JSContext* cx);
JSAtom* name() const { return hasGuessedAtom() ? nullptr : atom_.get(); }
// Because display names (see Debugger.Object.displayName) are already stored
// on functions and will always contain a valid es6 function name, as described
@@ -464,16 +471,28 @@ class JSFunction : public js::NativeObje
}
bool isGenerator() const { return generatorKind() != js::NotGenerator; }
bool isLegacyGenerator() const { return generatorKind() == js::LegacyGenerator; }
bool isStarGenerator() const { return generatorKind() == js::StarGenerator; }
+ js::FunctionAsyncKind asyncKind() const {
+ return isInterpretedLazy() ? lazyScript()->asyncKind() : nonLazyScript()->asyncKind();
+ }
+
+ bool isAsync() const {
+ if (isInterpretedLazy())
+ return lazyScript()->asyncKind() == js::AsyncFunction;
+ if (hasScript())
+ return nonLazyScript()->asyncKind() == js::AsyncFunction;
+ return false;
+ }
+
void setScript(JSScript* script_) {
mutableScript() = script_;
}
void initScript(JSScript* script_) {
mutableScript().init(script_);
}
@@ -596,16 +615,19 @@ fun_toStringHelper(JSContext* cx, js::Ha
namespace js {
extern bool
Function(JSContext* cx, unsigned argc, Value* vp);
extern bool
Generator(JSContext* cx, unsigned argc, Value* vp);
+extern bool
+AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp);
+
// Allocate a new function backed by a JSNative. Note that by default this
// creates a singleton object.
extern JSFunction*
NewNativeFunction(ExclusiveContext* cx, JSNative native, unsigned nargs, HandleAtom atom,
gc::AllocKind allocKind = gc::AllocKind::FUNCTION,
NewObjectKind newKind = SingletonObject);
// Allocate a new constructor backed by a JSNative. Note that by default this
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -2610,16 +2610,17 @@ JSScript::initFromFunctionBox(ExclusiveC
script->functionHasThisBinding_ = funbox->hasThisBinding();
script->functionHasExtraBodyVarScope_ = funbox->hasExtraBodyVarScope();
script->funLength_ = funbox->length;
script->isGeneratorExp_ = funbox->isGenexpLambda;
script->setGeneratorKind(funbox->generatorKind());
+ script->setAsyncKind(funbox->asyncKind());
PositionalFormalParameterIter fi(script);
while (fi && !fi.closedOver())
fi++;
script->funHasAnyAliasedFormal_ = !!fi;
script->setHasInnerFunctions(funbox->hasInnerFunctions());
}
@@ -3099,16 +3100,18 @@ Rebase(JSScript* dst, JSScript* src, T*
{
size_t off = reinterpret_cast<uint8_t*>(srcp) - src->data;
return reinterpret_cast<T*>(dst->data + off);
}
static JSObject*
CloneInnerInterpretedFunction(JSContext* cx, HandleScope enclosingScope, HandleFunction srcFun)
{
+ /* async function should not appear as inner function. */
+ MOZ_ASSERT(!srcFun->isAsync());
/* NB: Keep this in sync with XDRInterpretedFunction. */
RootedObject cloneProto(cx);
if (srcFun->isStarGenerator()) {
cloneProto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, cx->global());
if (!cloneProto)
return nullptr;
}
@@ -3267,16 +3270,17 @@ js::detail::CopyScript(JSContext* cx, Ha
dst->hasSingletons_ = src->hasSingletons();
dst->treatAsRunOnce_ = src->treatAsRunOnce();
dst->hasInnerFunctions_ = src->hasInnerFunctions();
dst->isGeneratorExp_ = src->isGeneratorExp();
dst->setGeneratorKind(src->generatorKind());
dst->isDerivedClassConstructor_ = src->isDerivedClassConstructor();
dst->needsHomeObject_ = src->needsHomeObject();
dst->isDefaultClassConstructor_ = src->isDefaultClassConstructor();
+ dst->isAsync_ = src->asyncKind() == AsyncFunction;
if (nconsts != 0) {
GCPtrValue* vector = Rebase<GCPtrValue>(dst, src, src->consts()->vector);
dst->consts()->vector = vector;
for (unsigned i = 0; i < nconsts; ++i)
MOZ_ASSERT_IF(vector[i].isMarkable(), vector[i].toString()->isAtom());
}
if (nobjects != 0) {
@@ -3999,16 +4003,17 @@ LazyScript::Create(ExclusiveContext* cx,
union {
PackedView p;
uint64_t packedFields;
};
p.version = version;
p.shouldDeclareArguments = false;
p.hasThisBinding = false;
+ p.isAsync = false;
p.numClosedOverBindings = closedOverBindings.length();
p.numInnerFunctions = innerFunctions.length();
p.generatorKindBits = GeneratorKindAsBits(NotGenerator);
p.strict = false;
p.bindingsAccessedDynamically = false;
p.hasDebuggerStatement = false;
p.hasDirectEval = false;
p.isLikelyConstructorWrapper = false;
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -635,28 +635,40 @@ class ScriptSourceObject : public Native
static const uint32_t SOURCE_SLOT = 0;
static const uint32_t ELEMENT_SLOT = 1;
static const uint32_t ELEMENT_PROPERTY_SLOT = 2;
static const uint32_t INTRODUCTION_SCRIPT_SLOT = 3;
static const uint32_t RESERVED_SLOTS = 4;
};
enum GeneratorKind { NotGenerator, LegacyGenerator, StarGenerator };
+enum FunctionAsyncKind { SyncFunction, AsyncFunction };
static inline unsigned
GeneratorKindAsBits(GeneratorKind generatorKind) {
return static_cast<unsigned>(generatorKind);
}
static inline GeneratorKind
GeneratorKindFromBits(unsigned val) {
MOZ_ASSERT(val <= StarGenerator);
return static_cast<GeneratorKind>(val);
}
+static inline unsigned
+AsyncKindAsBits(FunctionAsyncKind asyncKind) {
+ return static_cast<unsigned>(asyncKind);
+}
+
+static inline FunctionAsyncKind
+AsyncKindFromBits(unsigned val) {
+ MOZ_ASSERT(val <= AsyncFunction);
+ return static_cast<FunctionAsyncKind>(val);
+}
+
/*
* NB: after a successful XDR_DECODE, XDRScript callers must do any required
* subsequent set-up of owning function or script object and then call
* CallNewScriptHook.
*/
template<XDRMode mode>
bool
XDRScript(XDRState<mode>* xdr, HandleScope enclosingScope, HandleScript enclosingScript,
@@ -983,16 +995,18 @@ class JSScript : public js::gc::TenuredC
// script.
bool hasInnerFunctions_:1;
bool needsHomeObject_:1;
bool isDerivedClassConstructor_:1;
bool isDefaultClassConstructor_:1;
+ bool isAsync_:1;
+
// Add padding so JSScript is gc::Cell aligned. Make padding protected
// instead of private to suppress -Wunused-private-field compiler warnings.
protected:
#if JS_BITS_PER_WORD == 32
// Currently no padding is needed.
#endif
//
@@ -1271,16 +1285,24 @@ class JSScript : public js::gc::TenuredC
bool isStarGenerator() const { return generatorKind() == js::StarGenerator; }
void setGeneratorKind(js::GeneratorKind kind) {
// A script only gets its generator kind set as part of initialization,
// so it can only transition from not being a generator.
MOZ_ASSERT(!isGenerator());
generatorKindBits_ = GeneratorKindAsBits(kind);
}
+ js::FunctionAsyncKind asyncKind() const {
+ return isAsync_ ? js::AsyncFunction : js::SyncFunction;
+ }
+
+ void setAsyncKind(js::FunctionAsyncKind kind) {
+ isAsync_ = kind == js::AsyncFunction;
+ }
+
void setNeedsHomeObject() {
needsHomeObject_ = true;
}
bool needsHomeObject() const {
return needsHomeObject_;
}
bool isDerivedClassConstructor() const {
@@ -1882,17 +1904,18 @@ class LazyScript : public gc::TenuredCel
private:
struct PackedView {
// Assorted bits that should really be in ScriptSourceObject.
uint32_t version : 8;
uint32_t shouldDeclareArguments : 1;
uint32_t hasThisBinding : 1;
- uint32_t numClosedOverBindings : 22;
+ uint32_t isAsync : 1;
+ uint32_t numClosedOverBindings : 21;
uint32_t numInnerFunctions : 20;
uint32_t generatorKindBits : 2;
// N.B. These are booleans but need to be uint32_t to pack correctly on MSVC.
// If you add another boolean here, make sure to initialze it in
// LazyScript::CreateRaw().
uint32_t strict : 1;
@@ -2019,16 +2042,24 @@ class LazyScript : public gc::TenuredCel
// A script only gets its generator kind set as part of initialization,
// so it can only transition from NotGenerator.
MOZ_ASSERT(!isGenerator());
// Legacy generators cannot currently be lazy.
MOZ_ASSERT(kind != LegacyGenerator);
p_.generatorKindBits = GeneratorKindAsBits(kind);
}
+ FunctionAsyncKind asyncKind() const {
+ return p_.isAsync ? AsyncFunction : SyncFunction;
+ }
+
+ void setAsyncKind(FunctionAsyncKind kind) {
+ p_.isAsync = kind == AsyncFunction;
+ }
+
bool strict() const {
return p_.strict;
}
void setStrict() {
p_.strict = true;
}
bool bindingsAccessedDynamically() const {
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -319,16 +319,17 @@ UNIFIED_SOURCES += [
'proxy/OpaqueCrossCompartmentWrapper.cpp',
'proxy/Proxy.cpp',
'proxy/ScriptedProxyHandler.cpp',
'proxy/SecurityWrapper.cpp',
'proxy/Wrapper.cpp',
'threading/Mutex.cpp',
'vm/ArgumentsObject.cpp',
'vm/ArrayBufferObject.cpp',
+ 'vm/AsyncFunction.cpp',
'vm/Caches.cpp',
'vm/CallNonGenericMethod.cpp',
'vm/CharacterEncoding.cpp',
'vm/CodeCoverage.cpp',
'vm/Compression.cpp',
'vm/DateTime.cpp',
'vm/Debugger.cpp',
'vm/DebuggerMemory.cpp',
@@ -744,16 +745,17 @@ GENERATED_FILES += [('selfhosted.out.h',
selfhosted = GENERATED_FILES[('selfhosted.out.h', 'selfhosted.js')]
selfhosted.script = 'builtin/embedjs.py:generate_selfhosted'
selfhosted.inputs = [
'js.msg',
'builtin/TypedObjectConstants.h',
'builtin/SelfHostingDefines.h',
'builtin/Utilities.js',
'builtin/Array.js',
+ 'builtin/AsyncFunctions.js',
'builtin/Classes.js',
'builtin/Date.js',
'builtin/Error.js',
'builtin/Function.js',
'builtin/Generator.js',
'builtin/Intl.js',
'builtin/IntlData.js',
'builtin/IntlTzData.js',
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_5/Object/defineProperties-callable-accessor.js
@@ -0,0 +1,19 @@
+// ObjectDefineProperties with non callable accessor throws.
+const descriptors = [
+ {get: 1}, {set: 1},
+ {get: []}, {set: []},
+ {get: {}}, {set: {}},
+ {get: new Number}, {set: new Number},
+
+ {get: 1, set: 1},
+ {get: [], set: []},
+ {get: {}, set: {}},
+ {get: new Number, set: new Number},
+];
+
+for (const descriptor of descriptors) {
+ assertThrowsInstanceOf(() => Object.create(null, {x: descriptor}), TypeError);
+ assertThrowsInstanceOf(() => Object.defineProperties({}, {x: descriptor}), TypeError);
+}
+
+reportCompare(true, true);
--- a/js/src/tests/ecma_6/Symbol/toStringTag.js
+++ b/js/src/tests/ecma_6/Symbol/toStringTag.js
@@ -144,9 +144,12 @@ testDefault(JSON, "JSON");
testDefault(function* () {}.constructor.prototype, "GeneratorFunction");
// ES6 25.3.1.5 Generator.prototype [ @@toStringTag ]
testDefault(function* () {}().__proto__.__proto__, "Generator");
// ES6 25.4.5.4 Promise.prototype [ @@toStringTag ]
testDefault(Promise.prototype, "Promise");
+// AsyncFunction.prototype [ @@toStringTag ]
+testDefault(async function() {}.constructor.prototype, "AsyncFunction");
+
reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/BoundNames.js
@@ -0,0 +1,21 @@
+var BUGNUMBER = 1185106;
+var summary = "Bound names of async functions";
+
+print(BUGNUMBER + ": " + summary);
+
+async function test() {}
+assertEq(test.name, "test");
+
+var test2 = (async function test2() {});
+assertEq(test2.name, "test2");
+
+var anon = async function() {};
+assertEq(anon.name, "");
+
+if (typeof Reflect !== "undefined" && Reflect.parse) {
+ var tree = Reflect.parse("export default async function() {}", { target: "module" });
+ assertEq(tree.body[0].declaration.id.name, "*default*");
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/EarlyErrors.js
@@ -0,0 +1,51 @@
+var BUGNUMBER = 1185106;
+var summary = "EarlyErrors for async function";
+
+print(BUGNUMBER + ": " + summary);
+
+function assertThrowsSE(code) {
+ assertThrows(() => Reflect.parse(code), SyntaxError);
+}
+
+if (typeof Reflect !== "undefined" && Reflect.parse) {
+ // If FormalParameters Contains AwaitExpression is true.
+ assertThrowsSE("async function a(k = await 3) {}");
+ assertThrowsSE("(async function(k = await 3) {})");
+ assertThrowsSE("(async function a(k = await 3) {})");
+
+ // If BindingIdentifier is `eval` or `arguments`.
+ assertThrowsSE("'use strict'; async function eval() {}");
+ assertThrowsSE("'use strict'; (async function eval() {})");
+
+ assertThrowsSE("'use strict'; async function arguments() {}");
+ assertThrowsSE("'use strict'; (async function arguments() {})");
+
+ // If any element of the BoundNames of FormalParameters also occurs in the
+ // LexicallyDeclaredNames of AsyncFunctionBody.
+ assertThrowsSE("async function a(x) { let x; }");
+ assertThrowsSE("(async function(x) { let x; })");
+ assertThrowsSE("(async function a(x) { let x; })");
+
+ // If FormalParameters contains SuperProperty is true.
+ assertThrowsSE("async function a(k = super.prop) { }");
+ assertThrowsSE("(async function(k = super.prop) {})");
+ assertThrowsSE("(async function a(k = super.prop) {})");
+
+ // If AsyncFunctionBody contains SuperProperty is true.
+ assertThrowsSE("async function a() { super.prop(); }");
+ assertThrowsSE("(async function() { super.prop(); })");
+ assertThrowsSE("(async function a() { super.prop(); })");
+
+ // If FormalParameters contains SuperCall is true.
+ assertThrowsSE("async function a(k = super()) {}");
+ assertThrowsSE("(async function(k = super()) {})");
+ assertThrowsSE("(async function a(k = super()) {})");
+
+ // If AsyncFunctionBody contains SuperCall is true.
+ assertThrowsSE("async function a() { super(); }");
+ assertThrowsSE("(async function() { super(); })");
+ assertThrowsSE("(async function a() { super(); })");
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/arguments_callee.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
+
+var BUGNUMBER = 1185106;
+var summary = "arguments.callee in sloppy mode should return wrapped function";
+
+print(BUGNUMBER + ": " + summary);
+
+async function decl1() {
+ return arguments.callee;
+}
+assertEventuallyEq(decl1(), decl1);
+
+var expr1 = async function foo() {
+ return arguments.callee;
+};
+assertEventuallyEq(expr1(), expr1);
+
+var expr2 = async function() {
+ return arguments.callee;
+};
+assertEventuallyEq(expr2(), expr2);
+
+var obj = {
+ async method1() {
+ return arguments.callee;
+ }
+};
+assertEventuallyEq(obj.method1(), obj.method1);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/constructor.js
@@ -0,0 +1,33 @@
+var BUGNUMBER = 1185106;
+var summary = "async function constructor and prototype";
+
+print(BUGNUMBER + ": " + summary);
+
+var f1 = async function() {};
+
+var AsyncFunction = f1.constructor;
+var AsyncFunctionPrototype = AsyncFunction.prototype;
+
+assertEq(AsyncFunction.name, "AsyncFunction");
+assertEq(AsyncFunction.length, 1);
+assertEq(Object.getPrototypeOf(async function() {}), AsyncFunctionPrototype);
+
+assertEq(AsyncFunctionPrototype.constructor, AsyncFunction);
+
+var f2 = AsyncFunction("await 1");
+assertEq(f2.constructor, AsyncFunction);
+assertEq(f2.length, 0);
+assertEq(Object.getPrototypeOf(f2), AsyncFunctionPrototype);
+
+var f3 = new AsyncFunction("await 1");
+assertEq(f3.constructor, AsyncFunction);
+assertEq(f3.length, 0);
+assertEq(Object.getPrototypeOf(f3), AsyncFunctionPrototype);
+
+var f4 = AsyncFunction("a", "b", "c", "await 1");
+assertEq(f4.constructor, AsyncFunction);
+assertEq(f4.length, 3);
+assertEq(Object.getPrototypeOf(f4), AsyncFunctionPrototype);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/identity.js
@@ -0,0 +1,14 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
+
+var BUGNUMBER = 1185106;
+var summary = "Named async function expression should get wrapped function for the name inside it";
+
+print(BUGNUMBER + ": " + summary);
+
+var expr = async function foo() {
+ return foo;
+};
+assertEventuallyEq(expr(), expr);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/length.js
@@ -0,0 +1,12 @@
+var BUGNUMBER = 1185106;
+var summary = "async function length";
+
+print(BUGNUMBER + ": " + summary);
+
+assertEq(async function() {}.length, 0);
+assertEq(async function(a) {}.length, 1);
+assertEq(async function(a, b, c) {}.length, 3);
+assertEq(async function(a, b, c, ...d) {}.length, 3);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/methods.js
@@ -0,0 +1,61 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
+var BUGNUMBER = 1185106;
+var summary = "async methods semantics";
+
+print(BUGNUMBER + ": " + summary);
+
+class X {
+ constructor() {
+ this.value = 42;
+ }
+ async getValue() {
+ return this.value;
+ }
+ setValue(value) {
+ this.value = value;
+ }
+ async increment() {
+ var value = await this.getValue();
+ this.setValue(value + 1);
+ return this.getValue();
+ }
+ async getBaseClassName() {
+ return 'X';
+ }
+ static async getStaticValue() {
+ return 44;
+ }
+ async 10() {
+ return 46;
+ }
+ async ["foo"]() {
+ return 47;
+ }
+}
+
+class Y extends X {
+ async getBaseClassName() {
+ return super.getBaseClassName();
+ }
+}
+
+var objLiteral = {
+ async get() {
+ return 45;
+ },
+ someStuff: 5
+};
+
+var x = new X();
+var y = new Y();
+
+assertEventuallyEq(x.getValue(), 42);
+assertEventuallyEq(x.increment(), 43);
+assertEventuallyEq(x[10](), 46);
+assertEventuallyEq(x.foo(), 47);
+assertEventuallyEq(X.getStaticValue(), 44);
+assertEventuallyEq(objLiteral.get(), 45);
+assertEventuallyEq(y.getBaseClassName(), 'X');
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/property.js
@@ -0,0 +1,39 @@
+var BUGNUMBER = 1185106;
+var summary = "async name token in property and object destructuring pattern";
+
+print(BUGNUMBER + ": " + summary);
+
+{
+ let a = { async: 10 };
+ assertEq(a.async, 10);
+}
+
+{
+ let a = { async() {} };
+ assertEq(a.async instanceof Function, true);
+ assertEq(a.async.name, "async");
+}
+
+{
+ let async = 11;
+ let a = { async };
+ assertEq(a.async, 11);
+}
+
+{
+ let { async } = { async: 12 };
+ assertEq(async, 12);
+}
+
+{
+ let { async = 13 } = {};
+ assertEq(async, 13);
+}
+
+{
+ let { async: a = 14 } = {};
+ assertEq(a, 14);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/semantics.js
@@ -0,0 +1,169 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
+var BUGNUMBER = 1185106;
+var summary = "async functions semantics";
+
+print(BUGNUMBER + ": " + summary);
+
+async function empty() {
+}
+assertEventuallyEq(empty(), undefined);
+
+async function simpleReturn() {
+ return 1;
+}
+assertEventuallyEq(simpleReturn(), 1);
+
+async function simpleAwait() {
+ var result = await 2;
+ return result;
+}
+assertEventuallyEq(simpleAwait(), 2);
+
+async function simpleAwaitAsync() {
+ var result = await simpleReturn();
+ return 2 + result;
+}
+assertEventuallyEq(simpleAwaitAsync(), 3);
+
+async function returnOtherAsync() {
+ return 1 + await simpleAwaitAsync();
+}
+assertEventuallyEq(returnOtherAsync(), 4);
+
+async function simpleThrower() {
+ throw new Error();
+}
+assertEventuallyThrows(simpleThrower(), Error);
+
+async function delegatedThrower() {
+ var val = await simpleThrower();
+ return val;
+}
+
+async function tryCatch() {
+ try {
+ await delegatedThrower();
+ return 'FAILED';
+ } catch (_) {
+ return 5;
+ }
+}
+assertEventuallyEq(tryCatch(), 5);
+
+async function tryCatchThrow() {
+ try {
+ await delegatedThrower();
+ return 'FAILED';
+ } catch (_) {
+ return delegatedThrower();
+ }
+}
+assertEventuallyThrows(tryCatchThrow(), Error);
+
+async function wellFinally() {
+ try {
+ await delegatedThrower();
+ } catch (_) {
+ return 'FAILED';
+ } finally {
+ return 6;
+ }
+}
+assertEventuallyEq(wellFinally(), 6);
+
+async function finallyMayFail() {
+ try {
+ await delegatedThrower();
+ } catch (_) {
+ return 5;
+ } finally {
+ return delegatedThrower();
+ }
+}
+assertEventuallyThrows(finallyMayFail(), Error);
+
+async function embedded() {
+ async function inner() {
+ return 7;
+ }
+ return await inner();
+}
+assertEventuallyEq(embedded(), 7);
+
+// recursion, it works!
+async function fib(n) {
+ return (n == 0 || n == 1) ? n : await fib(n - 1) + await fib(n - 2);
+}
+assertEventuallyEq(fib(6), 8);
+
+// mutual recursion
+async function isOdd(n) {
+ async function isEven(n) {
+ return n === 0 || await isOdd(n - 1);
+ }
+ return n !== 0 && await isEven(n - 1);
+}
+assertEventuallyEq(isOdd(12).then(v => v ? "oops" : 12), 12);
+
+// recursion, take three!
+var hardcoreFib = async function fib2(n) {
+ return (n == 0 || n == 1) ? n : await fib2(n - 1) + await fib2(n - 2);
+}
+assertEventuallyEq(hardcoreFib(7), 13);
+
+var asyncExpr = async function() {
+ return 10;
+}
+assertEventuallyEq(asyncExpr(), 10);
+
+var namedAsyncExpr = async function simple() {
+ return 11;
+}
+assertEventuallyEq(namedAsyncExpr(), 11);
+
+async function executionOrder() {
+ var value = 0;
+ async function first() {
+ return (value = value === 0 ? 1 : value);
+ }
+ async function second() {
+ return (value = value === 0 ? 2 : value);
+ }
+ async function third() {
+ return (value = value === 0 ? 3 : value);
+ }
+ return await first() + await second() + await third() + 6;
+}
+assertEventuallyEq(executionOrder(), 9);
+
+async function miscellaneous() {
+ if (arguments.length === 3 &&
+ arguments.callee.name === "miscellaneous")
+ return 14;
+}
+assertEventuallyEq(miscellaneous(1, 2, 3), 14);
+
+function thrower() {
+ throw 15;
+}
+
+async function defaultArgs(arg = thrower()) {
+}
+assertEventuallyEq(defaultArgs().catch(e => e), 15);
+
+let arrowAwaitExpr = async () => await 2;
+assertEventuallyEq(arrowAwaitExpr(), 2);
+
+let arrowAwaitBlock = async () => { return await 2; };
+assertEventuallyEq(arrowAwaitBlock(), 2);
+
+// Async functions are not constructible
+assertThrows(() => {
+ async function Person() {
+
+ }
+ new Person();
+}, TypeError);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/shell.js
@@ -0,0 +1,26 @@
+(function(global) {
+ function getPromiseResult(promise) {
+ var result, error, caught = false;
+ promise.then(r => { result = r; },
+ e => { caught = true; error = e; });
+ drainJobQueue();
+ if (caught)
+ throw error;
+ return result;
+ }
+
+ function assertEventuallyEq(promise, expected) {
+ assertEq(getPromiseResult(promise), expected);
+ }
+ global.assertEventuallyEq = assertEventuallyEq;
+
+ function assertEventuallyThrows(promise, expectedErrorType) {
+ assertThrowsInstanceOf(() => getPromiseResult(promise), expectedErrorType);
+ };
+ global.assertEventuallyThrows = assertEventuallyThrows;
+
+ function assertEventuallyDeepEq(promise, expected) {
+ assertDeepEq(getPromiseResult(promise), expected);
+ };
+ global.assertEventuallyDeepEq = assertEventuallyDeepEq;
+})(this);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/syntax-arrow.js
@@ -0,0 +1,104 @@
+var BUGNUMBER = 1185106;
+var summary = "async arrow function syntax";
+
+print(BUGNUMBER + ": " + summary);
+
+if (typeof Reflect !== "undefined" && Reflect.parse) {
+ // Parameters.
+ Reflect.parse("async () => 1");
+ Reflect.parse("async a => 1");
+ Reflect.parse("async (a) => 1");
+ Reflect.parse("async async => 1");
+ Reflect.parse("async (async) => 1");
+ Reflect.parse("async ([a]) => 1");
+ Reflect.parse("async ([a, b]) => 1");
+ Reflect.parse("async ({a}) => 1");
+ Reflect.parse("async ({a, b}) => 1");
+
+ assertThrows(() => Reflect.parse("async await => 1"), SyntaxError);
+ assertThrows(() => Reflect.parse("async (await) => 1"), SyntaxError);
+ assertThrows(() => Reflect.parse("async ([await]) => 1"), SyntaxError);
+ assertThrows(() => Reflect.parse("async ({await}) => 1"), SyntaxError);
+
+ assertThrows(() => Reflect.parse("async (a=await) => 1"), SyntaxError);
+ assertThrows(() => Reflect.parse("async ([a=await]) => 1"), SyntaxError);
+ assertThrows(() => Reflect.parse("async ({a=await}) => 1"), SyntaxError);
+
+ assertThrows(() => Reflect.parse("async (a=await 1) => 1"), SyntaxError);
+ assertThrows(() => Reflect.parse("async ([a=await 1]) => 1"), SyntaxError);
+ assertThrows(() => Reflect.parse("async ({a=await 1}) => 1"), SyntaxError);
+
+ assertThrows(() => Reflect.parse("async [a] => 1"), SyntaxError);
+ assertThrows(() => Reflect.parse("async [a, b] => 1"), SyntaxError);
+ assertThrows(() => Reflect.parse("async {a} => 1"), SyntaxError);
+ assertThrows(() => Reflect.parse("async {a: b} => 1"), SyntaxError);
+
+ // Expression body.
+ Reflect.parse("async a => a == b");
+
+ // Expression body with nested async function.
+ Reflect.parse("async a => async");
+ Reflect.parse("async a => async b => c");
+ Reflect.parse("async a => async function() {}");
+ Reflect.parse("async a => async function b() {}");
+
+ assertThrows(() => Reflect.parse("async a => async b"), SyntaxError);
+ assertThrows(() => Reflect.parse("async a => async function"), SyntaxError);
+ assertThrows(() => Reflect.parse("async a => async function()"), SyntaxError);
+
+ // Expression body with `await`.
+ Reflect.parse("async a => await 1");
+ Reflect.parse("async a => await await 1");
+ Reflect.parse("async a => await await await 1");
+
+ assertThrows(() => Reflect.parse("async a => await"), SyntaxError);
+ assertThrows(() => Reflect.parse("async a => await await"), SyntaxError);
+
+ // `await` is Unary Expression and it cannot have `async` function as an
+ // operand.
+ assertThrows(() => Reflect.parse("async a => await async X => Y"), SyntaxError);
+ Reflect.parse("async a => await (async X => Y)");
+ // But it can have `async` identifier as an operand.
+ Reflect.parse("async async => await async");
+
+ // Block body.
+ Reflect.parse("async X => {yield}");
+
+ // `yield` handling.
+ Reflect.parse("async X => yield");
+ Reflect.parse("async yield => X");
+ Reflect.parse("async yield => yield");
+ Reflect.parse("async X => {yield}");
+
+ Reflect.parse("async X => {yield}");
+ Reflect.parse("async yield => {X}");
+ Reflect.parse("async yield => {yield}");
+ Reflect.parse("function* g() { async X => yield }");
+
+ assertThrows(() => Reflect.parse("'use strict'; async yield => X"), SyntaxError);
+ assertThrows(() => Reflect.parse("'use strict'; async (yield) => X"), SyntaxError);
+ assertThrows(() => Reflect.parse("'use strict'; async X => yield"), SyntaxError);
+ assertThrows(() => Reflect.parse("'use strict'; async X => {yield}"), SyntaxError);
+
+ assertThrows(() => Reflect.parse("function* g() { async yield => X }"));
+ assertThrows(() => Reflect.parse("function* g() { async (yield) => X }"));
+ assertThrows(() => Reflect.parse("function* g() { async ([yield]) => X }"));
+ assertThrows(() => Reflect.parse("function* g() { async ({yield}) => X }"));
+
+ // Not async functions.
+ Reflect.parse("async ()");
+ Reflect.parse("async (a)");
+ Reflect.parse("async (async)");
+ Reflect.parse("async ([a])");
+ Reflect.parse("async ([a, b])");
+ Reflect.parse("async ({a})");
+ Reflect.parse("async ({a, b})");
+
+ // Async arrow function is assignment expression.
+ Reflect.parse("a ? async () => {1} : b");
+ Reflect.parse("a ? b : async () => {1}");
+ assertThrows(() => Reflect.parse("async () => {1} ? a : b"), SyntaxError);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/syntax-modules.js
@@ -0,0 +1,20 @@
+var BUGNUMBER = 1185106;
+var summary = "async/await syntax in module";
+
+print(BUGNUMBER + ": " + summary);
+
+if (typeof parseModule === "function") {
+ parseModule("async function f() { await 3; }");
+ parseModule("async function f() { await 3; }");
+ assertThrows(() => parseModule("var await = 5;"), SyntaxError);
+ assertThrows(() => parseModule("export var await;"), SyntaxError);
+ assertThrows(() => parseModule("async function f() { function g() { await 3; } }"), SyntaxError);
+
+ if (typeof Reflect !== "undefined" && Reflect.parse) {
+ assertThrows(() => Reflect.parse("export default async function() { yield; }", { target: "module" }), SyntaxError);
+ assertThrows(() => Reflect.parse("export default async function() { yield = 1; }", { target: "module" }), SyntaxError);
+ }
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/syntax.js
@@ -0,0 +1,89 @@
+var BUGNUMBER = 1185106;
+var summary = "async/await syntax";
+
+print(BUGNUMBER + ": " + summary);
+
+if (typeof Reflect !== "undefined" && Reflect.parse) {
+ assertEq(Reflect.parse("function a() {}").body[0].async, false);
+ assertEq(Reflect.parse("function* a() {}").body[0].async, false);
+ assertEq(Reflect.parse("async function a() {}").body[0].async, true);
+ assertEq(Reflect.parse("() => {}").body[0].async, undefined);
+
+ // Async generators are not allowed (with regards to spec)
+ assertThrows(() => Reflect.parse("async function* a() {}"), SyntaxError);
+
+ // No line terminator after async
+ assertEq(Reflect.parse("async\nfunction a(){}").body[0].expression.name, "async");
+
+ // Async function expressions
+ assertEq(Reflect.parse("(async function() {})()").body[0].expression.callee.async, true);
+ assertEq(Reflect.parse("var k = async function() {}").body[0].declarations[0].init.async, true);
+ assertEq(Reflect.parse("var nmd = async function named() {}").body[0].declarations[0].init.id.name, "named");
+
+ // `await` handling for function declaration name inherits.
+ assertEq(Reflect.parse("async function await() {}").body[0].id.name, "await");
+ assertThrows(() => Reflect.parse("async function f() { async function await() {} }"), SyntaxError);
+
+ // `await` is not allowed in function expression name.
+ assertThrows(() => Reflect.parse("(async function await() {})"), SyntaxError);
+
+ // Awaiting not directly inside an async function is not allowed
+ assertThrows(() => Reflect.parse("await 4;"), SyntaxError);
+ assertThrows(() => Reflect.parse("function a() { await 4; }"), SyntaxError);
+ assertThrows(() => Reflect.parse("function* a() { await 4; }"), SyntaxError);
+ assertThrows(() => Reflect.parse("async function k() { function a() { await 4; } }"), SyntaxError);
+
+ // No line terminator after await is allowed
+ assertThrows(() => Reflect.parse("async function a() { await\n4; }"), SyntaxError);
+
+ // Await is not allowed as a default expr.
+ assertThrows(() => Reflect.parse("async function a(k = await 3) {}"), SyntaxError);
+ assertThrows(() => Reflect.parse("async function a() { async function b(k = await 3) {} }"), SyntaxError);
+ assertThrows(() => Reflect.parse("async function a() { async function b(k = [await 3]) {} }"), SyntaxError);
+
+ assertThrows(() => Reflect.parse("async function a() { async function b([k = await 3]) {} }"), SyntaxError);
+ assertThrows(() => Reflect.parse("async function a() { async function b([k = [await 3]]) {} }"), SyntaxError);
+ assertThrows(() => Reflect.parse("async function a() { async function b({k = await 3}) {} }"), SyntaxError);
+ assertThrows(() => Reflect.parse("async function a() { async function b({k = [await 3]}) {} }"), SyntaxError);
+
+ // Await is not legal as an identifier in an async function.
+ assertThrows(() => Reflect.parse("async function a() { var await = 4; }"), SyntaxError);
+ assertThrows(() => Reflect.parse("async function a() { return await; }"), SyntaxError);
+
+ // Await is still available as an identifier name in strict mode code.
+ Reflect.parse("function a() { 'use strict'; var await = 3; }");
+ Reflect.parse("'use strict'; var await = 3;");
+
+ // Await is treated differently depending on context. Various cases.
+ Reflect.parse("var await = 3; async function a() { await 4; }");
+ Reflect.parse("async function a() { await 4; } var await = 5");
+ Reflect.parse("async function a() { function b() { return await; } }");
+
+ Reflect.parse("async function a() { var k = { async: 4 } }");
+
+ Reflect.parse("function a() { await: 4 }");
+
+ assertEq(Reflect.parse("async function a() { await 4; }")
+ .body[0].body.body[0].expression.operator, "await");
+
+ assertEq(Reflect.parse("async function a() { async function b() { await 4; } }")
+ .body[0].body.body[0].body.body[0].expression.operator, "await");
+
+ // operator priority test
+ assertEq(Reflect.parse("async function a() { await 2 + 3; }")
+ .body[0].body.body[0].expression.left.argument.value, 2);
+ assertEq(Reflect.parse("async function a() { await 2 + 3; }")
+ .body[0].body.body[0].expression.left.operator, "await");
+ assertEq(Reflect.parse("async function a() { await 2 + 3; }")
+ .body[0].body.body[0].expression.right.value, 3);
+
+ // blocks and other constructions
+ assertEq(Reflect.parse("{ async function a() { return 2; } }")
+ .body[0].body[0].async, true);
+
+ // Async function expression is primary expression.
+ Reflect.parse("(async function a() {}.constructor)");
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/toString.js
@@ -0,0 +1,24 @@
+var BUGNUMBER = 1185106;
+var summary = "async function toString";
+
+print(BUGNUMBER + ": " + summary);
+
+async function f1(a, b, c) { await a; }
+
+assertEq(f1.toString(),
+ "async function f1(a, b, c) { await a; }");
+
+assertEq(async function (a, b, c) { await a; }.toString(),
+ "async function (a, b, c) { await a; }");
+
+assertEq((async (a, b, c) => await a).toString(),
+ "async (a, b, c) => await a");
+
+assertEq((async (a, b, c) => { await a; }).toString(),
+ "async (a, b, c) => { await a; }");
+
+assertEq({ async foo(a, b, c) { await a; } }.foo.toString(),
+ "async function foo(a, b, c) { await a; }");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/yield.js
@@ -0,0 +1,71 @@
+var BUGNUMBER = 1185106;
+var summary = "yield handling in async function";
+
+print(BUGNUMBER + ": " + summary);
+
+function testPassArgsBody(argsbody) {
+ Reflect.parse(`async function a${argsbody}`);
+ Reflect.parse(`(async function a${argsbody})`);
+ Reflect.parse(`(async function ${argsbody})`);
+ Reflect.parse(`({ async m${argsbody} })`);
+}
+
+function testErrorArgsBody(argsbody, prefix="") {
+ assertThrows(() => Reflect.parse(`${prefix} async function a${argsbody}`), SyntaxError);
+ assertThrows(() => Reflect.parse(`${prefix} (async function a${argsbody})`), SyntaxError);
+ assertThrows(() => Reflect.parse(`${prefix} (async function ${argsbody})`), SyntaxError);
+ assertThrows(() => Reflect.parse(`${prefix} ({ async m${argsbody} })`), SyntaxError);
+}
+
+function testErrorArgsBodyStrict(argsbody) {
+ testErrorArgsBody(argsbody);
+ testErrorArgsBody(argsbody, "'use strict'; ");
+ assertThrows(() => Reflect.parse(`class X { async m${argsbody} }`), SyntaxError);
+ assertThrows(() => Reflect.parse(`class X { static async m${argsbody} }`), SyntaxError);
+ assertThrows(() => Reflect.parse(`export default async function ${argsbody}`, { target: "module" }), SyntaxError);
+}
+
+if (typeof Reflect !== "undefined" && Reflect.parse) {
+ // `yield` handling is inherited in async function declaration name.
+ Reflect.parse("async function yield() {}");
+ Reflect.parse("function f() { async function yield() {} }");
+ assertThrows(() => Reflect.parse("function* g() { async function yield() {} }"), SyntaxError);
+ assertThrows(() => Reflect.parse("'use strict'; async function yield() {}"), SyntaxError);
+
+ // `yield` is treated as an identifier in an async function expression name.
+ // `yield` is not allowed as an identifier in strict code.
+ Reflect.parse("(async function yield() {});");
+ Reflect.parse("function f() { (async function yield() {}); }");
+ Reflect.parse("function* g() { (async function yield() {}); }");
+ assertThrows(() => Reflect.parse("'use strict'; (async function yield() {});"), SyntaxError);
+
+ // `yield` handling is inherited in async method name.
+ Reflect.parse("({ async yield() {} });");
+ Reflect.parse("function f() { ({ async yield() {} }); }");
+ Reflect.parse("function* g() { ({ async yield() {} }); }");
+ Reflect.parse("'use strict'; ({ async yield() {} });");
+ Reflect.parse("class X { async yield() {} }");
+
+ Reflect.parse("({ async [yield]() {} });");
+ Reflect.parse("function f() { ({ async [yield]() {} }); }");
+ Reflect.parse("function* g() { ({ async [yield]() {} }); }");
+ assertThrows(() => Reflect.parse("'use strict'; ({ async [yield]() {} });"), SyntaxError);
+ assertThrows(() => Reflect.parse("class X { async [yield]() {} }"), SyntaxError);
+
+ // `yield` is treated as an identifier in an async function parameter
+ // `yield` is not allowed as an identifier in strict code.
+ testPassArgsBody("(yield) {}");
+ testPassArgsBody("(yield = 1) {}");
+ testPassArgsBody("(a = yield) {}");
+ testErrorArgsBodyStrict("(yield 3) {}");
+ testErrorArgsBodyStrict("(a = yield 3) {}");
+
+ // `yield` is treated as an identifier in an async function body
+ // `yield` is not allowed as an identifier in strict code.
+ testPassArgsBody("() { yield; }");
+ testPassArgsBody("() { yield = 1; }");
+ testErrorArgsBodyStrict("() { yield 3; }");
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
--- a/js/src/tests/js1_8_5/reflect-parse/PatternBuilders.js
+++ b/js/src/tests/js1_8_5/reflect-parse/PatternBuilders.js
@@ -42,16 +42,26 @@ function genFunDecl(style, id, params, b
return Pattern({ type: "FunctionDeclaration",
id: id,
params: params,
defaults: [],
body: body,
generator: true,
style: style });
}
+function asyncFunDecl(id, params, body) {
+ return Pattern({ type: "FunctionDeclaration",
+ id: id,
+ params: params,
+ defaults: [],
+ body: body,
+ generator: true,
+ async: true,
+ style: "es6" });
+}
function varDecl(decls) {
return Pattern({ type: "VariableDeclaration", declarations: decls, kind: "var" });
}
function letDecl(decls) {
return Pattern({ type: "VariableDeclaration", declarations: decls, kind: "let" });
}
function constDecl(decls) {
return Pattern({ type: "VariableDeclaration", declarations: decls, kind: "const" });
@@ -161,21 +171,39 @@ function funExpr(id, args, body, gen) {
function genFunExpr(style, id, args, body) {
return Pattern({ type: "FunctionExpression",
id: id,
params: args,
body: body,
generator: true,
style: style });
}
+function asyncFunExpr(id, args, body) {
+ return Pattern({ type: "FunctionExpression",
+ id: id,
+ params: args,
+ body: body,
+ generator: true,
+ async: true,
+ style: "es6" });
+}
function arrowExpr(args, body) {
return Pattern({ type: "ArrowFunctionExpression",
params: args,
body: body });
}
+function asyncArrowExpr(isExpression, args, body) {
+ return Pattern({ type: "ArrowFunctionExpression",
+ params: args,
+ body: body,
+ generator: true,
+ async: true,
+ expression: isExpression,
+ style: "es6" });
+}
function metaProperty(meta, property) {
return Pattern({ type: "MetaProperty",
meta: meta,
property: property });
}
function newTarget() {
return metaProperty(ident("new"), ident("target"));
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/reflect-parse/async.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!xulRuntime.shell)
+
+// async function declaration.
+assertDecl("async function foo() {}", asyncFunDecl(ident("foo"), [], blockStmt([])));
+
+// async function expression.
+assertExpr("(async function() {})", asyncFunExpr(null, [], blockStmt([])));
+assertExpr("(async function foo() {})", asyncFunExpr(ident("foo"), [], blockStmt([])));
+
+// async arrow.
+assertExpr("async a => 1", asyncArrowExpr(true, [ident("a")], literal(1)));
+assertExpr("async a => { 1 }", asyncArrowExpr(false, [ident("a")], blockStmt([exprStmt(literal(1))])));
+assertExpr("async a => { return 1 }", asyncArrowExpr(false, [ident("a")], blockStmt([returnStmt(literal(1))])));
+
+// async method.
+assertExpr("({ async foo() {} })", objExpr([{ key: ident("foo"), value: asyncFunExpr(ident("foo"), [], blockStmt([]))}]));
+
+assertStmt("class C { async foo() {} }", classStmt(ident("C"), null, [classMethod(ident("foo"), asyncFunExpr(ident("foo"), [], blockStmt([])), "method", false)]));
+assertStmt("class C { static async foo() {} }", classStmt(ident("C"), null, [classMethod(ident("foo"), asyncFunExpr(ident("foo"), [], blockStmt([])), "method", true)]));
+
+// await expression.
+assertDecl("async function foo() { await bar }", asyncFunDecl(ident("foo"), [], blockStmt([exprStmt(unExpr("await", ident("bar")))])));
+
+if (typeof reportCompare === 'function')
+ reportCompare(true, true);
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -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/. */
#include "vm/ArgumentsObject-inl.h"
#include "mozilla/PodOperations.h"
#include "jit/JitFrames.h"
+#include "vm/AsyncFunction.h"
#include "vm/GlobalObject.h"
#include "vm/Stack.h"
#include "jsobjinlines.h"
#include "gc/Nursery-inl.h"
#include "vm/Stack-inl.h"
@@ -441,18 +442,23 @@ MappedArgGetter(JSContext* cx, HandleObj
unsigned arg = unsigned(JSID_TO_INT(id));
if (arg < argsobj.initialLength() && !argsobj.isElementDeleted(arg))
vp.set(argsobj.element(arg));
} else if (JSID_IS_ATOM(id, cx->names().length)) {
if (!argsobj.hasOverriddenLength())
vp.setInt32(argsobj.initialLength());
} else {
MOZ_ASSERT(JSID_IS_ATOM(id, cx->names().callee));
- if (!argsobj.hasOverriddenCallee())
- vp.setObject(argsobj.callee());
+ if (!argsobj.hasOverriddenCallee()) {
+ RootedFunction callee(cx, &argsobj.callee());
+ if (callee->isAsync())
+ vp.setObject(*GetWrappedAsyncFunction(callee));
+ else
+ vp.setObject(*callee);
+ }
}
return true;
}
static bool
MappedArgSetter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp,
ObjectOpResult& result)
{
new file mode 100644
--- /dev/null
+++ b/js/src/vm/AsyncFunction.cpp
@@ -0,0 +1,118 @@
+/* -*- 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 "vm/AsyncFunction.h"
+
+#include "jscompartment.h"
+
+#include "builtin/SelfHostingDefines.h"
+#include "vm/GlobalObject.h"
+#include "vm/SelfHosting.h"
+
+using namespace js;
+using namespace js::gc;
+
+/* static */ bool
+GlobalObject::initAsyncFunction(JSContext* cx, Handle<GlobalObject*> global)
+{
+ if (global->getReservedSlot(ASYNC_FUNCTION_PROTO).isObject())
+ return true;
+
+ RootedObject asyncFunctionProto(cx, NewSingletonObjectWithFunctionPrototype(cx, global));
+ if (!asyncFunctionProto)
+ return false;
+
+ if (!DefineToStringTag(cx, asyncFunctionProto, cx->names().AsyncFunction))
+ return false;
+
+ RootedValue function(cx, global->getConstructor(JSProto_Function));
+ if (!function.toObjectOrNull())
+ return false;
+ RootedObject proto(cx, &function.toObject());
+ RootedAtom name(cx, cx->names().AsyncFunction);
+ RootedObject asyncFunction(cx, NewFunctionWithProto(cx, AsyncFunctionConstructor, 1,
+ JSFunction::NATIVE_CTOR, nullptr, name,
+ proto));
+ if (!asyncFunction)
+ return false;
+ if (!LinkConstructorAndPrototype(cx, asyncFunction, asyncFunctionProto))
+ return false;
+
+ global->setReservedSlot(ASYNC_FUNCTION, ObjectValue(*asyncFunction));
+ global->setReservedSlot(ASYNC_FUNCTION_PROTO, ObjectValue(*asyncFunctionProto));
+ return true;
+}
+
+JSFunction*
+js::GetWrappedAsyncFunction(JSFunction* unwrapped)
+{
+ MOZ_ASSERT(unwrapped->isAsync());
+ return &unwrapped->getExtendedSlot(ASYNC_WRAPPED_SLOT).toObject().as<JSFunction>();
+}
+
+JSFunction*
+js::GetUnwrappedAsyncFunction(JSFunction* wrapper)
+{
+ JSFunction* unwrapped = &wrapper->getExtendedSlot(ASYNC_UNWRAPPED_SLOT).toObject().as<JSFunction>();
+ MOZ_ASSERT(unwrapped->isAsync());
+ return unwrapped;
+}
+
+bool
+js::IsWrappedAsyncFunction(JSContext* cx, JSFunction* wrapper)
+{
+ return IsSelfHostedFunctionWithName(wrapper, cx->names().AsyncWrapped);
+}
+
+bool
+js::CreateAsyncFunction(JSContext* cx, HandleFunction wrapper, HandleFunction unwrapped,
+ MutableHandleFunction result)
+{
+ // Create a new function with AsyncFunctionPrototype, reusing the script
+ // and the environment of `wrapper` function, and the name and the length
+ // of `unwrapped` function.
+ RootedObject proto(cx, GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global()));
+ RootedObject scope(cx, wrapper->environment());
+ RootedAtom atom(cx, unwrapped->name());
+ RootedFunction wrapped(cx, NewFunctionWithProto(cx, nullptr, 0,
+ JSFunction::INTERPRETED_LAMBDA,
+ scope, atom, proto,
+ AllocKind::FUNCTION_EXTENDED, TenuredObject));
+ if (!wrapped)
+ return false;
+
+ wrapped->initScript(wrapper->nonLazyScript());
+
+ // Link them each other to make GetWrappedAsyncFunction and
+ // GetUnwrappedAsyncFunction work.
+ unwrapped->setExtendedSlot(ASYNC_WRAPPED_SLOT, ObjectValue(*wrapped));
+ wrapped->setExtendedSlot(ASYNC_UNWRAPPED_SLOT, ObjectValue(*unwrapped));
+
+ // The script of `wrapper` is self-hosted, so `wrapped` should also be
+ // set as self-hosted function.
+ wrapped->setIsSelfHostedBuiltin();
+
+ // Set LAZY_FUNCTION_NAME_SLOT to "AsyncWrapped" to make it detectable in
+ // IsWrappedAsyncFunction.
+ wrapped->setExtendedSlot(LAZY_FUNCTION_NAME_SLOT, StringValue(cx->names().AsyncWrapped));
+
+ // The length of the script of `wrapper` is different than the length of
+ // `unwrapped`. We should set actual length as resolved length, to avoid
+ // using the length of the script.
+ uint16_t length;
+ if (!unwrapped->getLength(cx, &length))
+ return false;
+
+ RootedValue lengthValue(cx, NumberValue(length));
+ if (!DefineProperty(cx, wrapped, cx->names().length, lengthValue,
+ nullptr, nullptr, JSPROP_READONLY))
+ {
+ return false;
+ }
+
+ result.set(wrapped);
+ return true;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/vm/AsyncFunction.h
@@ -0,0 +1,30 @@
+/* -*- 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 vm_AsyncFunction_h
+#define vm_AsyncFunction_h
+
+#include "jscntxt.h"
+#include "jsobj.h"
+
+namespace js {
+
+JSFunction*
+GetWrappedAsyncFunction(JSFunction* unwrapped);
+
+JSFunction*
+GetUnwrappedAsyncFunction(JSFunction* wrapper);
+
+bool
+IsWrappedAsyncFunction(JSContext* cx, JSFunction* wrapper);
+
+bool
+CreateAsyncFunction(JSContext* cx, HandleFunction wrapper, HandleFunction unwrapped,
+ MutableHandleFunction result);
+
+} // namespace js
+
+#endif /* vm_AsyncFunction_h */
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -24,16 +24,21 @@
macro(ArraySpecies, ArraySpecies, "ArraySpecies") \
macro(ArraySpeciesCreate, ArraySpeciesCreate, "ArraySpeciesCreate") \
macro(ArrayToLocaleString, ArrayToLocaleString, "ArrayToLocaleString") \
macro(ArrayType, ArrayType, "ArrayType") \
macro(ArrayValues, ArrayValues, "ArrayValues") \
macro(ArrayValuesAt, ArrayValuesAt, "ArrayValuesAt") \
macro(as, as, "as") \
macro(Async, Async, "Async") \
+ macro(AsyncFunction, AsyncFunction, "AsyncFunction") \
+ macro(AsyncFunction_wrap, AsyncFunction_wrap, "AsyncFunction_wrap") \
+ macro(AsyncWrapped, AsyncWrapped, "AsyncWrapped") \
+ macro(async, async, "async") \
+ macro(await, await, "await") \
macro(Bool8x16, Bool8x16, "Bool8x16") \
macro(Bool16x8, Bool16x8, "Bool16x8") \
macro(Bool32x4, Bool32x4, "Bool32x4") \
macro(Bool64x2, Bool64x2, "Bool64x2") \
macro(boundWithSpace, boundWithSpace, "bound ") \
macro(breakdown, breakdown, "breakdown") \
macro(buffer, buffer, "buffer") \
macro(builder, builder, "builder") \
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -12,16 +12,17 @@
#include "jscompartment.h"
#include "jsiter.h"
#include "builtin/ModuleObject.h"
#include "frontend/ParseNode.h"
#include "gc/Policy.h"
#include "vm/ArgumentsObject.h"
+#include "vm/AsyncFunction.h"
#include "vm/GlobalObject.h"
#include "vm/ProxyObject.h"
#include "vm/Shape.h"
#include "vm/Xdr.h"
#include "jsatominlines.h"
#include "jsobjinlines.h"
#include "jsscriptinlines.h"
@@ -1018,17 +1019,19 @@ const Class LexicalEnvironmentObject::cl
JSCLASS_IS_ANONYMOUS,
JS_NULL_CLASS_OPS,
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
JS_NULL_OBJECT_OPS
};
/* static */ NamedLambdaObject*
-NamedLambdaObject::create(JSContext* cx, HandleFunction callee, HandleObject enclosing,
+NamedLambdaObject::create(JSContext* cx, HandleFunction callee,
+ HandleFunction func,
+ HandleObject enclosing,
gc::InitialHeap heap)
{
MOZ_ASSERT(callee->isNamedLambda());
RootedScope scope(cx, callee->nonLazyScript()->maybeNamedLambdaScope());
MOZ_ASSERT(scope && scope->environmentShape());
MOZ_ASSERT(scope->environmentShape()->slot() == lambdaSlot());
MOZ_ASSERT(!scope->environmentShape()->writable());
@@ -1040,32 +1043,40 @@ NamedLambdaObject::create(JSContext* cx,
#endif
LexicalEnvironmentObject* obj =
LexicalEnvironmentObject::createTemplateObject(cx, scope.as<LexicalScope>(),
enclosing, heap);
if (!obj)
return nullptr;
- obj->initFixedSlot(lambdaSlot(), ObjectValue(*callee));
+ obj->initFixedSlot(lambdaSlot(), ObjectValue(*func));
return static_cast<NamedLambdaObject*>(obj);
}
/* static */ NamedLambdaObject*
NamedLambdaObject::createTemplateObject(JSContext* cx, HandleFunction callee, gc::InitialHeap heap)
{
- return create(cx, callee, nullptr, heap);
+ return create(cx, callee, callee, nullptr, heap);
}
/* static */ NamedLambdaObject*
NamedLambdaObject::create(JSContext* cx, AbstractFramePtr frame)
{
RootedFunction fun(cx, frame.callee());
RootedObject enclosing(cx, frame.environmentChain());
- return create(cx, fun, enclosing, gc::DefaultHeap);
+ return create(cx, fun, fun, enclosing, gc::DefaultHeap);
+}
+
+/* static */ NamedLambdaObject*
+NamedLambdaObject::create(JSContext* cx, AbstractFramePtr frame, HandleFunction replacement)
+{
+ RootedFunction fun(cx, frame.callee());
+ RootedObject enclosing(cx, frame.environmentChain());
+ return create(cx, fun, replacement, enclosing, gc::DefaultHeap);
}
/* static */ size_t
NamedLambdaObject::lambdaSlot()
{
// Named lambda environments have exactly one name.
return JSSLOT_FREE(&LexicalEnvironmentObject::class_);
}
@@ -3360,17 +3371,25 @@ js::InitFunctionEnvironmentObjects(JSCon
{
MOZ_ASSERT(frame.isFunctionFrame());
MOZ_ASSERT(frame.callee()->needsSomeEnvironmentObject());
RootedFunction callee(cx, frame.callee());
// Named lambdas may have an environment that holds itself for recursion.
if (callee->needsNamedLambdaEnvironment()) {
- NamedLambdaObject* declEnv = NamedLambdaObject::create(cx, frame);
+ NamedLambdaObject* declEnv;
+ if (callee->isAsync()) {
+ // Named async function needs special environment to return
+ // wrapped function for the binding.
+ RootedFunction fun(cx, GetWrappedAsyncFunction(callee));
+ declEnv = NamedLambdaObject::create(cx, frame, fun);
+ } else {
+ declEnv = NamedLambdaObject::create(cx, frame);
+ }
if (!declEnv)
return false;
frame.pushOnEnvironmentChain(*declEnv);
}
// If the function has parameter default expressions, there may be an
// extra environment to hold the parameters.
if (callee->needsCallObject()) {
--- a/js/src/vm/EnvironmentObject.h
+++ b/js/src/vm/EnvironmentObject.h
@@ -510,23 +510,26 @@ class LexicalEnvironmentObject : public
// For extensible lexical environments, the 'this' value for its
// scope. Otherwise asserts.
Value thisValue() const;
};
class NamedLambdaObject : public LexicalEnvironmentObject
{
static NamedLambdaObject* create(JSContext* cx, HandleFunction callee,
+ HandleFunction replacement,
HandleObject enclosing, gc::InitialHeap heap);
public:
static NamedLambdaObject* createTemplateObject(JSContext* cx, HandleFunction callee,
gc::InitialHeap heap);
static NamedLambdaObject* create(JSContext* cx, AbstractFramePtr frame);
+ static NamedLambdaObject* create(JSContext* cx, AbstractFramePtr frame,
+ HandleFunction replacement);
// For JITs.
static size_t lambdaSlot();
};
// A non-syntactic dynamic scope object that captures non-lexical
// bindings. That is, a scope object that captures both qualified var
// assignments and unqualified bareword assignments. Its parent is always the
--- a/js/src/vm/GeneratorObject.cpp
+++ b/js/src/vm/GeneratorObject.cpp
@@ -255,18 +255,18 @@ static JSObject*
NewSingletonObjectWithObjectPrototype(JSContext* cx, Handle<GlobalObject*> global)
{
RootedObject proto(cx, global->getOrCreateObjectPrototype(cx));
if (!proto)
return nullptr;
return NewObjectWithGivenProto<PlainObject>(cx, proto, SingletonObject);
}
-static JSObject*
-NewSingletonObjectWithFunctionPrototype(JSContext* cx, Handle<GlobalObject*> global)
+JSObject*
+js::NewSingletonObjectWithFunctionPrototype(JSContext* cx, Handle<GlobalObject*> global)
{
RootedObject proto(cx, global->getOrCreateFunctionPrototype(cx));
if (!proto)
return nullptr;
return NewObjectWithGivenProto<PlainObject>(cx, proto, SingletonObject);
}
/* static */ bool
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -92,16 +92,18 @@ class GlobalObject : public NativeObject
EMPTY_GLOBAL_SCOPE,
ITERATOR_PROTO,
ARRAY_ITERATOR_PROTO,
STRING_ITERATOR_PROTO,
LEGACY_GENERATOR_OBJECT_PROTO,
STAR_GENERATOR_OBJECT_PROTO,
STAR_GENERATOR_FUNCTION_PROTO,
STAR_GENERATOR_FUNCTION,
+ ASYNC_FUNCTION_PROTO,
+ ASYNC_FUNCTION,
MAP_ITERATOR_PROTO,
SET_ITERATOR_PROTO,
COLLATOR_PROTO,
NUMBER_FORMAT_PROTO,
DATE_TIME_FORMAT_PROTO,
MODULE_PROTO,
IMPORT_ENTRY_PROTO,
EXPORT_ENTRY_PROTO,
@@ -571,16 +573,29 @@ class GlobalObject : public NativeObject
}
static JSObject* getOrCreateStarGeneratorFunction(JSContext* cx,
Handle<GlobalObject*> global)
{
return global->getOrCreateObject(cx, STAR_GENERATOR_FUNCTION, initStarGenerators);
}
+ static NativeObject* getOrCreateAsyncFunctionPrototype(JSContext* cx,
+ Handle<GlobalObject*> global)
+ {
+ return MaybeNativeObject(global->getOrCreateObject(cx, ASYNC_FUNCTION_PROTO,
+ initAsyncFunction));
+ }
+
+ static JSObject* getOrCreateAsyncFunction(JSContext* cx,
+ Handle<GlobalObject*> global)
+ {
+ return global->getOrCreateObject(cx, ASYNC_FUNCTION, initAsyncFunction);
+ }
+
static JSObject* getOrCreateMapIteratorPrototype(JSContext* cx,
Handle<GlobalObject*> global)
{
return global->getOrCreateObject(cx, MAP_ITERATOR_PROTO, initMapIteratorProto);
}
static JSObject* getOrCreateSetIteratorPrototype(JSContext* cx,
Handle<GlobalObject*> global)
@@ -720,16 +735,18 @@ class GlobalObject : public NativeObject
static bool initIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
static bool initArrayIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
static bool initStringIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
// Implemented in vm/GeneratorObject.cpp.
static bool initLegacyGeneratorProto(JSContext* cx, Handle<GlobalObject*> global);
static bool initStarGenerators(JSContext* cx, Handle<GlobalObject*> global);
+ static bool initAsyncFunction(JSContext* cx, Handle<GlobalObject*> global);
+
// Implemented in builtin/MapObject.cpp.
static bool initMapIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
static bool initSetIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
// Implemented in Intl.cpp.
static bool initIntlObject(JSContext* cx, Handle<GlobalObject*> global);
// Implemented in builtin/ModuleObject.cpp
@@ -980,16 +997,19 @@ inline JSProtoKey
StandardProtoKeyOrNull(const JSObject* obj)
{
JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(obj->getClass());
if (key == JSProto_Error)
return GetExceptionProtoKey(obj->as<ErrorObject>().type());
return key;
}
+JSObject*
+NewSingletonObjectWithFunctionPrototype(JSContext* cx, Handle<GlobalObject*> global);
+
} // namespace js
template<>
inline bool
JSObject::is<js::GlobalObject>() const
{
return !!(getClass()->flags & JSCLASS_IS_GLOBAL);
}
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -3441,19 +3441,20 @@ END_CASE(JSOP_DEFLET)
CASE(JSOP_DEFFUN)
{
/*
* A top-level function defined in Global or Eval code (see ECMA-262
* Ed. 3), or else a SpiderMonkey extension: a named function statement in
* a compound statement (not at the top statement level of global code, or
* at the top level of a function body).
*/
- ReservedRooted<JSFunction*> fun(&rootFunction0, script->getFunction(GET_UINT32_INDEX(REGS.pc)));
+ ReservedRooted<JSFunction*> fun(&rootFunction0, ®S.sp[-1].toObject().as<JSFunction>());
if (!DefFunOperation(cx, script, REGS.fp()->environmentChain(), fun))
goto error;
+ REGS.sp--;
}
END_CASE(JSOP_DEFFUN)
CASE(JSOP_LAMBDA)
{
/* Load the specified function object literal. */
ReservedRooted<JSFunction*> fun(&rootFunction0, script->getFunction(GET_UINT32_INDEX(REGS.pc)));
JSObject* obj = Lambda(cx, fun, REGS.fp()->environmentChain());
@@ -4325,37 +4326,18 @@ js::LambdaArrow(JSContext* cx, HandleFun
clone->as<JSFunction>().setExtendedSlot(0, newTargetv);
MOZ_ASSERT(fun->global() == clone->global());
return clone;
}
bool
js::DefFunOperation(JSContext* cx, HandleScript script, HandleObject envChain,
- HandleFunction funArg)
-{
- /*
- * If static link is not current scope, clone fun's object to link to the
- * current scope via parent. We do this to enable sharing of compiled
- * functions among multiple equivalent scopes, amortizing the cost of
- * compilation over a number of executions. Examples include XUL scripts
- * and event handlers shared among Firefox or other Mozilla app chrome
- * windows, and user-defined JS functions precompiled and then shared among
- * requests in server-side JS.
- */
- RootedFunction fun(cx, funArg);
- if (fun->isNative() || fun->environment() != envChain) {
- fun = CloneFunctionObjectIfNotSingleton(cx, fun, envChain, nullptr, TenuredObject);
- if (!fun)
- return false;
- } else {
- MOZ_ASSERT(script->treatAsRunOnce());
- MOZ_ASSERT(!script->functionNonDelazifying());
- }
-
+ HandleFunction fun)
+{
/*
* We define the function as a property of the variable object and not the
* current scope chain even for the case of function expression statements
* and functions defined by eval inside let or with blocks.
*/
RootedObject parent(cx, envChain);
while (!parent->isQualifiedVarObj())
parent = parent->enclosingEnvironment();
--- a/js/src/vm/Keywords.h
+++ b/js/src/vm/Keywords.h
@@ -50,16 +50,17 @@
macro(enum, enum_, TOK_RESERVED) \
/* Future reserved keywords, but only in strict mode. */ \
macro(implements, implements, TOK_STRICT_RESERVED) \
macro(interface, interface, TOK_STRICT_RESERVED) \
macro(package, package, TOK_STRICT_RESERVED) \
macro(private, private_, TOK_STRICT_RESERVED) \
macro(protected, protected_, TOK_STRICT_RESERVED) \
macro(public, public_, TOK_STRICT_RESERVED) \
+ macro(await, await, TOK_AWAIT) \
/* \
* Yield is a token inside function*. Outside of a function*, it is a \
* future reserved keyword in strict mode, but a keyword in JS1.7 even \
* when strict. Punt logic to parser. \
*/ \
macro(yield, yield, TOK_YIELD)
#endif /* vm_Keywords_h */
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1295,20 +1295,20 @@ 1234567890123456789012345678901234567890
\
/*
* Defines the given function on the current scope.
*
* This is used for global scripts and also in some cases for function
* scripts where use of dynamic scoping inhibits optimization.
* Category: Variables and Scopes
* Type: Variables
- * Operands: uint32_t funcIndex
- * Stack: =>
+ * Operands:
+ * Stack: fun =>
*/ \
- macro(JSOP_DEFFUN, 127,"deffun", NULL, 5, 0, 0, JOF_OBJECT) \
+ macro(JSOP_DEFFUN, 127,"deffun", NULL, 1, 1, 0, JOF_BYTE) \
/* Defines the new constant binding on global lexical scope.
*
* Throws if a binding with the same name already exists on the scope, or
* if a var binding with the same name exists on the global.
* Category: Variables and Scopes
* Type: Variables
* Operands: uint32_t nameIndex
* Stack: =>
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -33,16 +33,17 @@
#include "builtin/TypedObject.h"
#include "builtin/WeakSetObject.h"
#include "gc/Marking.h"
#include "gc/Policy.h"
#include "jit/AtomicOperations.h"
#include "jit/InlinableNatives.h"
#include "js/CharacterEncoding.h"
#include "js/Date.h"
+#include "vm/AsyncFunction.h"
#include "vm/Compression.h"
#include "vm/GeneratorObject.h"
#include "vm/Interpreter.h"
#include "vm/RegExpObject.h"
#include "vm/String.h"
#include "vm/StringBuffer.h"
#include "vm/TypedArrayCommon.h"
#include "vm/WrapperObject.h"
@@ -1826,16 +1827,33 @@ js::ReportIncompatibleSelfHostedMethod(J
}
++iter;
}
MOZ_ASSERT_UNREACHABLE("How did we not find a useful self-hosted frame?");
return false;
}
+bool
+intrinsic_CreateAsyncFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 2);
+
+ RootedFunction wrapper(cx, &args[0].toObject().as<JSFunction>());
+ RootedFunction unwrapped(cx, &args[1].toObject().as<JSFunction>());
+
+ RootedFunction wrapped(cx);
+ if (!CreateAsyncFunction(cx, wrapper, unwrapped, &wrapped))
+ return false;
+
+ args.rval().setObject(*wrapped);
+ return true;
+}
+
/**
* Returns the default locale as a well-formed, but not necessarily canonicalized,
* BCP-47 language tag.
*/
static bool
intrinsic_RuntimeDefaultLocale(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
@@ -2225,16 +2243,18 @@ static const JSFunctionSpec intrinsic_fu
JS_FN("MakeDefaultConstructor", intrinsic_MakeDefaultConstructor, 2,0),
JS_FN("_ConstructorForTypedArray", intrinsic_ConstructorForTypedArray, 1,0),
JS_FN("_NameForTypedArray", intrinsic_NameForTypedArray, 1,0),
JS_FN("DecompileArg", intrinsic_DecompileArg, 2,0),
JS_FN("_FinishBoundFunctionInit", intrinsic_FinishBoundFunctionInit, 3,0),
JS_FN("RuntimeDefaultLocale", intrinsic_RuntimeDefaultLocale, 0,0),
JS_FN("AddContentTelemetry", intrinsic_AddContentTelemetry, 2,0),
+ JS_FN("CreateAsyncFunction", intrinsic_CreateAsyncFunction, 1,0),
+
JS_INLINABLE_FN("_IsConstructing", intrinsic_IsConstructing, 0,0,
IntrinsicIsConstructing),
JS_INLINABLE_FN("SubstringKernel", intrinsic_SubstringKernel, 3,0,
IntrinsicSubstringKernel),
JS_INLINABLE_FN("_DefineDataProperty", intrinsic_DefineDataProperty, 4,0,
IntrinsicDefineDataProperty),
JS_INLINABLE_FN("ObjectHasPrototype", intrinsic_ObjectHasPrototype, 2,0,
IntrinsicObjectHasPrototype),
@@ -2358,16 +2378,20 @@ static const JSFunctionSpec intrinsic_fu
CallNonGenericSelfhostedMethod<Is<LegacyGeneratorObject>>, 2, 0),
JS_FN("CallStarGeneratorMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<StarGeneratorObject>>, 2, 0),
JS_FN("IsWeakSet", intrinsic_IsInstanceOfBuiltin<WeakSetObject>, 1,0),
JS_FN("CallWeakSetMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<WeakSetObject>>, 2, 0),
+ JS_FN("Promise_static_resolve", Promise_static_resolve, 1, 0),
+ JS_FN("Promise_static_reject", Promise_reject, 1, 0),
+ JS_FN("Promise_then", Promise_then, 2, 0),
+
// See builtin/TypedObject.h for descriptors of the typedobj functions.
JS_FN("NewOpaqueTypedObject", js::NewOpaqueTypedObject, 1, 0),
JS_FN("NewDerivedTypedObject", js::NewDerivedTypedObject, 3, 0),
JS_FN("TypedObjectBuffer", TypedObject::GetBuffer, 1, 0),
JS_FN("TypedObjectByteOffset", TypedObject::GetByteOffset, 1, 0),
JS_FN("AttachTypedObject", js::AttachTypedObject, 3, 0),
JS_FN("TypedObjectIsAttached", js::TypedObjectIsAttached, 1, 0),
JS_FN("TypedObjectTypeDescr", js::TypedObjectTypeDescr, 1, 0),
--- a/mfbt/LinkedList.h
+++ b/mfbt/LinkedList.h
@@ -63,27 +63,75 @@
#ifndef mozilla_LinkedList_h
#define mozilla_LinkedList_h
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Move.h"
+#include "mozilla/RefPtr.h"
#ifdef __cplusplus
namespace mozilla {
template<typename T>
+class LinkedListElement;
+
+namespace detail {
+
+/**
+ * LinkedList supports refcounted elements using this adapter class. Clients
+ * using LinkedList<RefPtr<T>> will get a data structure that holds a strong
+ * reference to T as long as T is in the list.
+ */
+template<typename T>
+struct LinkedListElementTraits
+{
+ typedef T* RawType;
+ typedef const T* ConstRawType;
+ typedef T* ClientType;
+ typedef const T* ConstClientType;
+
+ // These static methods are called when an element is added to or removed from
+ // a linked list. It can be used to keep track ownership in lists that are
+ // supposed to own their elements. If elements are transferred from one list
+ // to another, no enter or exit calls happen since the elements still belong
+ // to a list.
+ static void enterList(LinkedListElement<T>* elt) {}
+ static void exitList(LinkedListElement<T>* elt) {}
+};
+
+template<typename T>
+struct LinkedListElementTraits<RefPtr<T>>
+{
+ typedef T* RawType;
+ typedef const T* ConstRawType;
+ typedef RefPtr<T> ClientType;
+ typedef RefPtr<const T> ConstClientType;
+
+ static void enterList(LinkedListElement<RefPtr<T>>* elt) { elt->asT()->AddRef(); }
+ static void exitList(LinkedListElement<RefPtr<T>>* elt) { elt->asT()->Release(); }
+};
+
+} /* namespace detail */
+
+template<typename T>
class LinkedList;
template<typename T>
class LinkedListElement
{
+ typedef typename detail::LinkedListElementTraits<T> Traits;
+ typedef typename Traits::RawType RawType;
+ typedef typename Traits::ConstRawType ConstRawType;
+ typedef typename Traits::ClientType ClientType;
+ typedef typename Traits::ConstClientType ConstClientType;
+
/*
* It's convenient that we return nullptr when getNext() or getPrevious()
* hits the end of the list, but doing so costs an extra word of storage in
* each linked list node (to keep track of whether |this| is the sentinel
* node) and a branch on this value in getNext/getPrevious.
*
* We could get rid of the extra word of storage by shoving the "is
* sentinel" bit into one of the pointers, although this would, of course,
@@ -150,42 +198,42 @@ public:
remove();
}
}
/*
* Get the next element in the list, or nullptr if this is the last element
* in the list.
*/
- T* getNext() { return mNext->asT(); }
- const T* getNext() const { return mNext->asT(); }
+ RawType getNext() { return mNext->asT(); }
+ ConstRawType getNext() const { return mNext->asT(); }
/*
* Get the previous element in the list, or nullptr if this is the first
* element in the list.
*/
- T* getPrevious() { return mPrev->asT(); }
- const T* getPrevious() const { return mPrev->asT(); }
+ RawType getPrevious() { return mPrev->asT(); }
+ ConstRawType getPrevious() const { return mPrev->asT(); }
/*
* Insert aElem after this element in the list. |this| must be part of a
* linked list when you call setNext(); otherwise, this method will assert.
*/
- void setNext(T* aElem)
+ void setNext(RawType aElem)
{
MOZ_ASSERT(isInList());
setNextUnsafe(aElem);
}
/*
* Insert aElem before this element in the list. |this| must be part of a
* linked list when you call setPrevious(); otherwise, this method will
* assert.
*/
- void setPrevious(T* aElem)
+ void setPrevious(RawType aElem)
{
MOZ_ASSERT(isInList());
setPreviousUnsafe(aElem);
}
/*
* Remove this element from the list which contains it. If this element is
* not currently part of a linked list, this method asserts.
@@ -193,16 +241,42 @@ public:
void remove()
{
MOZ_ASSERT(isInList());
mPrev->mNext = mNext;
mNext->mPrev = mPrev;
mNext = this;
mPrev = this;
+
+ Traits::exitList(this);
+ }
+
+ /*
+ * Remove this element from the list containing it. Returns a pointer to the
+ * element that follows this element (before it was removed). This method
+ * asserts if the element does not belong to a list.
+ */
+ ClientType removeAndGetNext()
+ {
+ ClientType r = getNext();
+ remove();
+ return r;
+ }
+
+ /*
+ * Remove this element from the list containing it. Returns a pointer to the
+ * previous element in the containing list (before the removal). This method
+ * asserts if the element does not belong to a list.
+ */
+ ClientType removeAndGetPrevious()
+ {
+ ClientType r = getPrevious();
+ remove();
+ return r;
}
/*
* Identical to remove(), but also asserts in debug builds that this element
* is in aList.
*/
void removeFrom(const LinkedList<T>& aList)
{
@@ -216,83 +290,92 @@ public:
bool isInList() const
{
MOZ_ASSERT((mNext == this) == (mPrev == this));
return mNext != this;
}
private:
friend class LinkedList<T>;
+ friend struct detail::LinkedListElementTraits<T>;
enum class NodeKind {
Normal,
Sentinel
};
explicit LinkedListElement(NodeKind nodeKind)
: mNext(this),
mPrev(this),
mIsSentinel(nodeKind == NodeKind::Sentinel)
{ }
/*
* Return |this| cast to T* if we're a normal node, or return nullptr if
* we're a sentinel node.
*/
- T* asT()
+ RawType asT()
{
- return mIsSentinel ? nullptr : static_cast<T*>(this);
+ return mIsSentinel ? nullptr : static_cast<RawType>(this);
}
- const T* asT() const
+ ConstRawType asT() const
{
- return mIsSentinel ? nullptr : static_cast<const T*>(this);
+ return mIsSentinel ? nullptr : static_cast<ConstRawType>(this);
}
/*
* Insert aElem after this element, but don't check that this element is in
* the list. This is called by LinkedList::insertFront().
*/
- void setNextUnsafe(T* aElem)
+ void setNextUnsafe(RawType aElem)
{
LinkedListElement *listElem = static_cast<LinkedListElement*>(aElem);
MOZ_ASSERT(!listElem->isInList());
listElem->mNext = this->mNext;
listElem->mPrev = this;
this->mNext->mPrev = listElem;
this->mNext = listElem;
+
+ Traits::enterList(aElem);
}
/*
* Insert aElem before this element, but don't check that this element is in
* the list. This is called by LinkedList::insertBack().
*/
- void setPreviousUnsafe(T* aElem)
+ void setPreviousUnsafe(RawType aElem)
{
LinkedListElement<T>* listElem = static_cast<LinkedListElement<T>*>(aElem);
MOZ_ASSERT(!listElem->isInList());
listElem->mNext = this;
listElem->mPrev = this->mPrev;
this->mPrev->mNext = listElem;
this->mPrev = listElem;
+
+ Traits::enterList(aElem);
}
/*
* Adjust mNext and mPrev for implementing move constructor and move
* assignment.
*/
void adjustLinkForMove(LinkedListElement<T>&& aOther)
{
if (!aOther.isInList()) {
mNext = this;
mPrev = this;
return;
}
+ if (!mIsSentinel) {
+ Traits::enterList(this);
+ }
+
MOZ_ASSERT(aOther.mNext->mPrev == &aOther);
MOZ_ASSERT(aOther.mPrev->mNext == &aOther);
/*
* Initialize |this| with |aOther|'s mPrev/mNext pointers, and adjust those
* element to point to this one.
*/
mNext = aOther.mNext;
@@ -302,36 +385,46 @@ private:
mPrev->mNext = this;
/*
* Adjust |aOther| so it doesn't think it's in a list. This makes it
* safely destructable.
*/
aOther.mNext = &aOther;
aOther.mPrev = &aOther;
+
+ if (!mIsSentinel) {
+ Traits::exitList(&aOther);
+ }
}
LinkedListElement& operator=(const LinkedListElement<T>& aOther) = delete;
LinkedListElement(const LinkedListElement<T>& aOther) = delete;
};
template<typename T>
class LinkedList
{
private:
+ typedef typename detail::LinkedListElementTraits<T> Traits;
+ typedef typename Traits::RawType RawType;
+ typedef typename Traits::ConstRawType ConstRawType;
+ typedef typename Traits::ClientType ClientType;
+ typedef typename Traits::ConstClientType ConstClientType;
+
LinkedListElement<T> sentinel;
public:
class Iterator {
- T* mCurrent;
+ RawType mCurrent;
public:
- explicit Iterator(T* aCurrent) : mCurrent(aCurrent) {}
+ explicit Iterator(RawType aCurrent) : mCurrent(aCurrent) {}
- T* operator *() const {
+ RawType operator *() const {
return mCurrent;
}
const Iterator& operator++() {
mCurrent = mCurrent->getNext();
return *this;
}
@@ -358,64 +451,64 @@ public:
"failing this assertion means this LinkedList's creator is "
"buggy: it should have removed all this list's elements before "
"the list's destruction");
}
/*
* Add aElem to the front of the list.
*/
- void insertFront(T* aElem)
+ void insertFront(RawType aElem)
{
/* Bypass setNext()'s this->isInList() assertion. */
sentinel.setNextUnsafe(aElem);
}
/*
* Add aElem to the back of the list.
*/
- void insertBack(T* aElem)
+ void insertBack(RawType aElem)
{
sentinel.setPreviousUnsafe(aElem);
}
/*
* Get the first element of the list, or nullptr if the list is empty.
*/
- T* getFirst() { return sentinel.getNext(); }
- const T* getFirst() const { return sentinel.getNext(); }
+ RawType getFirst() { return sentinel.getNext(); }
+ ConstRawType getFirst() const { return sentinel.getNext(); }
/*
* Get the last element of the list, or nullptr if the list is empty.
*/
- T* getLast() { return sentinel.getPrevious(); }
- const T* getLast() const { return sentinel.getPrevious(); }
+ RawType getLast() { return sentinel.getPrevious(); }
+ ConstRawType getLast() const { return sentinel.getPrevious(); }
/*
* Get and remove the first element of the list. If the list is empty,
* return nullptr.
*/
- T* popFirst()
+ ClientType popFirst()
{
- T* ret = sentinel.getNext();
+ ClientType ret = sentinel.getNext();
if (ret) {
- static_cast<LinkedListElement<T>*>(ret)->remove();
+ static_cast<LinkedListElement<T>*>(RawType(ret))->remove();
}
return ret;
}
/*
* Get and remove the last element of the list. If the list is empty,
* return nullptr.
*/
- T* popLast()
+ ClientType popLast()
{
- T* ret = sentinel.getPrevious();
+ ClientType ret = sentinel.getPrevious();
if (ret) {
- static_cast<LinkedListElement<T>*>(ret)->remove();
+ static_cast<LinkedListElement<T>*>(RawType(ret))->remove();
}
return ret;
}
/*
* Return true if the list is empty, or false otherwise.
*/
bool isEmpty() const
@@ -526,20 +619,20 @@ public:
cur = cur->mNext;
} while (cur != &sentinel);
#endif /* ifdef DEBUG */
}
private:
friend class LinkedListElement<T>;
- void assertContains(const T* aValue) const
+ void assertContains(const RawType aValue) const
{
#ifdef DEBUG
- for (const T* elem = getFirst(); elem; elem = elem->getNext()) {
+ for (ConstRawType elem = getFirst(); elem; elem = elem->getNext()) {
if (elem == aValue) {
return;
}
}
MOZ_CRASH("element wasn't found in this list!");
#endif
}
--- a/mfbt/tests/TestLinkedList.cpp
+++ b/mfbt/tests/TestLinkedList.cpp
@@ -151,16 +151,34 @@ TestMove()
LinkedList<SomeClass> list3;
list3 = Move(list2);
{ unsigned int check[] { 1, 2 }; CheckListValues(list3, check); }
MOZ_RELEASE_ASSERT(list2.isEmpty());
list3.clear();
}
+static void
+TestRemoveAndGet()
+{
+ LinkedList<SomeClass> list;
+
+ SomeClass one(1), two(2), three(3);
+ list.insertBack(&one);
+ list.insertBack(&two);
+ list.insertBack(&three);
+ { unsigned int check[] { 1, 2, 3 }; CheckListValues(list, check); }
+
+ MOZ_RELEASE_ASSERT(two.removeAndGetNext() == &three);
+ { unsigned int check[] { 1, 3 }; CheckListValues(list, check); }
+
+ MOZ_RELEASE_ASSERT(three.removeAndGetPrevious() == &one);
+ { unsigned int check[] { 1 }; CheckListValues(list, check); }
+}
+
struct PrivateClass : private LinkedListElement<PrivateClass> {
friend class mozilla::LinkedList<PrivateClass>;
friend class mozilla::LinkedListElement<PrivateClass>;
};
static void
TestPrivate()
{
@@ -172,16 +190,79 @@ TestPrivate()
size_t count = 0;
for (PrivateClass* p : list) {
MOZ_RELEASE_ASSERT(p, "cannot have null elements in list");
count++;
}
MOZ_RELEASE_ASSERT(count == 2);
}
+struct CountedClass : public LinkedListElement<RefPtr<CountedClass>>
+{
+ int mCount;
+ void AddRef() { mCount++; }
+ void Release() { mCount--; }
+
+ CountedClass() : mCount(0) {}
+ ~CountedClass() { MOZ_RELEASE_ASSERT(mCount == 0); }
+};
+
+static void
+TestRefPtrList()
+{
+ LinkedList<RefPtr<CountedClass>> list;
+ CountedClass* elt1 = new CountedClass;
+ CountedClass* elt2 = new CountedClass;
+
+ list.insertBack(elt1);
+ list.insertBack(elt2);
+
+ MOZ_RELEASE_ASSERT(elt1->mCount == 1);
+ MOZ_RELEASE_ASSERT(elt2->mCount == 1);
+
+ for (RefPtr<CountedClass> p : list) {
+ MOZ_RELEASE_ASSERT(p->mCount == 2);
+ }
+
+ RefPtr<CountedClass> ptr = list.getFirst();
+ while (ptr) {
+ MOZ_RELEASE_ASSERT(ptr->mCount == 2);
+ RefPtr<CountedClass> next = ptr->getNext();
+ ptr->remove();
+ ptr = Move(next);
+ }
+ ptr = nullptr;
+
+ MOZ_RELEASE_ASSERT(elt1->mCount == 0);
+ MOZ_RELEASE_ASSERT(elt2->mCount == 0);
+
+ list.insertBack(elt1);
+ elt1->setNext(elt2);
+
+ MOZ_RELEASE_ASSERT(elt1->mCount == 1);
+ MOZ_RELEASE_ASSERT(elt2->mCount == 1);
+
+ RefPtr<CountedClass> first = list.popFirst();
+
+ MOZ_RELEASE_ASSERT(elt1->mCount == 1);
+ MOZ_RELEASE_ASSERT(elt2->mCount == 1);
+
+ RefPtr<CountedClass> second = list.popFirst();
+
+ MOZ_RELEASE_ASSERT(elt1->mCount == 1);
+ MOZ_RELEASE_ASSERT(elt2->mCount == 1);
+
+ first = second = nullptr;
+
+ delete elt1;
+ delete elt2;
+}
+
int
main()
{
TestList();
TestPrivate();
TestMove();
+ TestRemoveAndGet();
+ TestRefPtrList();
return 0;
}
--- a/security/apps/AppSignatureVerification.cpp
+++ b/security/apps/AppSignatureVerification.cpp
@@ -638,28 +638,29 @@ VerifyCertificate(CERTCertificate* signe
const VerifyCertificateContext& context =
*static_cast<const VerifyCertificateContext*>(voidContext);
AppTrustDomain trustDomain(context.builtChain, pinArg);
if (trustDomain.SetTrustedRoot(context.trustedRoot) != SECSuccess) {
return MapSECStatus(SECFailure);
}
Input certDER;
- Result rv = certDER.Init(signerCert->derCert.data, signerCert->derCert.len);
+ mozilla::pkix::Result rv = certDER.Init(signerCert->derCert.data,
+ signerCert->derCert.len);
if (rv != Success) {
return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(rv));
}
rv = BuildCertChain(trustDomain, certDER, Now(),
EndEntityOrCA::MustBeEndEntity,
KeyUsage::digitalSignature,
KeyPurposeId::id_kp_codeSigning,
CertPolicyId::anyPolicy,
nullptr/*stapledOCSPResponse*/);
- if (rv == Result::ERROR_EXPIRED_CERTIFICATE) {
+ if (rv == mozilla::pkix::Result::ERROR_EXPIRED_CERTIFICATE) {
// For code-signing you normally need trusted 3rd-party timestamps to
// handle expiration properly. The signer could always mess with their
// system clock so you can't trust the certificate was un-expired when
// the signing took place. The choice is either to ignore expiration
// or to enforce expiration at time of use. The latter leads to the
// user-hostile result that perfectly good code stops working.
//
// Our package format doesn't support timestamps (nor do we have a
--- a/security/certverifier/CTSerialization.cpp
+++ b/security/certverifier/CTSerialization.cpp
@@ -11,16 +11,18 @@
#include "mozilla/Assertions.h"
#include "mozilla/Move.h"
#include "mozilla/TypeTraits.h"
namespace mozilla { namespace ct {
using namespace mozilla::pkix;
+typedef mozilla::pkix::Result Result;
+
// Note: length is always specified in bytes.
// Signed Certificate Timestamp (SCT) Version length
static const size_t kVersionLength = 1;
// Members of a V1 SCT
static const size_t kLogIdLength = 32;
static const size_t kTimestampLength = 8;
static const size_t kExtensionsLengthBytes = 2;
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -21,16 +21,18 @@ namespace mozilla { namespace ct {
// headers and force us to export them in moz.build. Just forward-declare
// the class here instead.
class MultiLogCTVerifier;
} } // namespace mozilla::ct
namespace mozilla { namespace psm {
+typedef mozilla::pkix::Result Result;
+
// These values correspond to the CERT_CHAIN_KEY_SIZE_STATUS telemetry.
enum class KeySizeStatus {
NeverChecked = 0,
LargeMinimumSucceeded = 1,
CompatibilityRisk = 2,
AlreadyBad = 3,
};
--- a/security/certverifier/OCSPCache.cpp
+++ b/security/certverifier/OCSPCache.cpp
@@ -33,16 +33,18 @@
#include "secerr.h"
extern mozilla::LazyLogModule gCertVerifierLog;
using namespace mozilla::pkix;
namespace mozilla { namespace psm {
+typedef mozilla::pkix::Result Result;
+
static SECStatus
DigestLength(UniquePK11Context& context, uint32_t length)
{
// Restrict length to 2 bytes because it should be big enough for all
// inputs this code will actually see and that it is well-defined and
// type-size-independent.
if (length >= 65536) {
return SECFailure;
--- a/security/certverifier/OCSPVerificationTrustDomain.cpp
+++ b/security/certverifier/OCSPVerificationTrustDomain.cpp
@@ -4,16 +4,18 @@
* 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 "OCSPVerificationTrustDomain.h"
using namespace mozilla;
using namespace mozilla::pkix;
+namespace mozilla { namespace psm {
+
OCSPVerificationTrustDomain::OCSPVerificationTrustDomain(
NSSCertDBTrustDomain& certDBTrustDomain)
: mCertDBTrustDomain(certDBTrustDomain)
{
}
Result
OCSPVerificationTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
@@ -117,8 +119,10 @@ OCSPVerificationTrustDomain::NoteAuxilia
Result
OCSPVerificationTrustDomain::DigestBuf(
Input item, DigestAlgorithm digestAlg,
/*out*/ uint8_t* digestBuf, size_t digestBufLen)
{
return mCertDBTrustDomain.DigestBuf(item, digestAlg, digestBuf, digestBufLen);
}
+
+} } // namespace mozilla::psm
--- a/security/certverifier/OCSPVerificationTrustDomain.h
+++ b/security/certverifier/OCSPVerificationTrustDomain.h
@@ -7,16 +7,18 @@
#ifndef mozilla_psm__OCSPVerificationTrustDomain_h
#define mozilla_psm__OCSPVerificationTrustDomain_h
#include "pkix/pkixtypes.h"
#include "NSSCertDBTrustDomain.h"
namespace mozilla { namespace psm {
+typedef mozilla::pkix::Result Result;
+
class OCSPVerificationTrustDomain : public mozilla::pkix::TrustDomain
{
public:
explicit OCSPVerificationTrustDomain(NSSCertDBTrustDomain& certDBTrustDomain);
virtual Result FindIssuer(mozilla::pkix::Input encodedIssuerName,
IssuerChecker& checker,
mozilla::pkix::Time time) override;
--- a/security/manager/ssl/ContentSignatureVerifier.cpp
+++ b/security/manager/ssl/ContentSignatureVerifier.cpp
@@ -159,17 +159,17 @@ ContentSignatureVerifier::CreateContextI
CERTCertListNode* node = CERT_LIST_HEAD(certCertList.get());
if (!node || !node->cert) {
return NS_ERROR_FAILURE;
}
SECItem* certSecItem = &node->cert->derCert;
Input certDER;
- Result result =
+ mozilla::pkix::Result result =
certDER.Init(BitwiseCast<uint8_t*, unsigned char*>(certSecItem->data),
certSecItem->len);
if (result != Success) {
return NS_ERROR_FAILURE;
}
// Check the signerCert chain is good
--- a/security/manager/ssl/nsDataSignatureVerifier.cpp
+++ b/security/manager/ssl/nsDataSignatureVerifier.cpp
@@ -252,21 +252,22 @@ VerifyCertificate(CERTCertificate* cert,
return NS_ERROR_OUT_OF_MEMORY;
}
context->signingCert = xpcomCert;
RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
- Result result = certVerifier->VerifyCert(cert,
- certificateUsageObjectSigner,
- Now(), pinArg,
- nullptr, // hostname
- context->builtChain);
+ mozilla::pkix::Result result =
+ certVerifier->VerifyCert(cert,
+ certificateUsageObjectSigner,
+ Now(), pinArg,
+ nullptr, // hostname
+ context->builtChain);
if (result != Success) {
return GetXPCOMFromNSSError(MapResultToPRErrorCode(result));
}
return NS_OK;
}
} // namespace
--- a/security/manager/ssl/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -184,17 +184,17 @@ struct nsCancelHTTPDownloadEvent : Runna
NS_IMETHOD Run() override {
mListener->FreeLoadGroup(true);
mListener = nullptr;
return NS_OK;
}
};
-Result
+mozilla::pkix::Result
nsNSSHttpServerSession::createSessionFcn(const char* host,
uint16_t portnum,
/*out*/ nsNSSHttpServerSession** pSession)
{
if (!host || !pSession) {
return Result::FATAL_ERROR_INVALID_ARGS;
}
@@ -205,17 +205,17 @@ nsNSSHttpServerSession::createSessionFcn
hss->mHost = host;
hss->mPort = portnum;
*pSession = hss;
return Success;
}
-Result
+mozilla::pkix::Result
nsNSSHttpRequestSession::createFcn(const nsNSSHttpServerSession* session,
const char* http_protocol_variant,
const char* path_and_query_string,
const char* http_request_method,
const PRIntervalTime timeout,
/*out*/ nsNSSHttpRequestSession** pRequest)
{
if (!session || !http_protocol_variant || !path_and_query_string ||
@@ -245,29 +245,29 @@ nsNSSHttpRequestSession::createFcn(const
rs->mURL.Append(path_and_query_string);
rs->mRequestMethod = http_request_method;
*pRequest = rs;
return Success;
}
-Result
+mozilla::pkix::Result
nsNSSHttpRequestSession::setPostDataFcn(const char* http_data,
const uint32_t http_data_len,
const char* http_content_type)
{
mHasPostData = true;
mPostData.Assign(http_data, http_data_len);
mPostContentType.Assign(http_content_type);
return Success;
}
-Result
+mozilla::pkix::Result
nsNSSHttpRequestSession::trySendAndReceiveFcn(PRPollDesc** pPollDesc,
uint16_t* http_response_code,
const char** http_response_content_type,
const char** http_response_headers,
const char** http_response_data,
uint32_t* http_response_data_len)
{
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
@@ -348,17 +348,17 @@ void
nsNSSHttpRequestSession::Release()
{
int32_t newRefCount = --mRefCount;
if (!newRefCount) {
delete this;
}
}
-Result
+mozilla::pkix::Result
nsNSSHttpRequestSession::internal_send_receive_attempt(bool &retryable_error,
PRPollDesc **pPollDesc,
uint16_t *http_response_code,
const char **http_response_content_type,
const char **http_response_headers,
const char **http_response_data,
uint32_t *http_response_data_len)
{
--- a/security/manager/ssl/tests/gtest/OCSPCacheTest.cpp
+++ b/security/manager/ssl/tests/gtest/OCSPCacheTest.cpp
@@ -12,17 +12,16 @@
#include "nss.h"
#include "pkix/pkixtypes.h"
#include "pkixtestutil.h"
#include "prerr.h"
#include "secerr.h"
using namespace mozilla::pkix;
using namespace mozilla::pkix::test;
-using namespace mozilla::psm;
template <size_t N>
inline Input
LiteralInput(const char(&valueString)[N])
{
// Ideally we would use mozilla::BitwiseCast() here rather than
// reinterpret_cast for better type checking, but the |N - 1| part trips
// static asserts.
@@ -41,17 +40,17 @@ protected:
NSS_NoDB_Init(nullptr);
}
const Time now;
mozilla::psm::OCSPCache cache;
};
static void
-PutAndGet(OCSPCache& cache, const CertID& certID, Result result,
+PutAndGet(mozilla::psm::OCSPCache& cache, const CertID& certID, Result result,
Time time, const char* firstPartyDomain = nullptr)
{
// The first time is thisUpdate. The second is validUntil.
// The caller is expecting the validUntil returned with Get
// to be equal to the passed-in time. Since these values will
// be different in practice, make thisUpdate less than validUntil.
Time thisUpdate(time);
ASSERT_EQ(Success, thisUpdate.SubtractSeconds(10));
--- a/testing/mach_commands.py
+++ b/testing/mach_commands.py
@@ -315,17 +315,17 @@ class Test(MachCommandBase):
res = self._mach_context.commands.dispatch(
suite['mach_command'], self._mach_context,
**suite['kwargs'])
if res:
status = res
buckets = {}
for test in run_tests:
- key = (test['flavor'], test.get('subsuite', ''))
+ key = (test['flavor'], test['subsuite'])
buckets.setdefault(key, []).append(test)
for (flavor, subsuite), tests in sorted(buckets.items()):
if flavor not in TEST_FLAVORS:
print(UNKNOWN_FLAVOR % flavor)
status = 1
continue
--- a/testing/mochitest/mach_commands.py
+++ b/testing/mochitest/mach_commands.py
@@ -373,27 +373,27 @@ class MachCommands(MachCommandBase):
suites = defaultdict(list)
unsupported = set()
for test in tests:
# Filter out non-mochitests and unsupported flavors.
if test['flavor'] not in ALL_FLAVORS:
continue
- key = (test['flavor'], test.get('subsuite', ''))
+ key = (test['flavor'], test['subsuite'])
if test['flavor'] not in flavors:
unsupported.add(key)
continue
if subsuite == 'default':
# "--subsuite default" means only run tests that don't have a subsuite
- if test.get('subsuite'):
+ if test['subsuite']:
unsupported.add(key)
continue
- elif subsuite and test.get('subsuite', '') != subsuite:
+ elif subsuite and test['subsuite'] != subsuite:
unsupported.add(key)
continue
suites[key].append(test)
# This is a hack to introduce an option in mach to not send
# filtered tests to the mochitest harness. Mochitest harness will read
# the master manifest in that case.
--- a/testing/mozbase/manifestparser/manifestparser/manifestparser.py
+++ b/testing/mozbase/manifestparser/manifestparser/manifestparser.py
@@ -147,16 +147,20 @@ class ManifestParser(object):
sections = read_ini(fp=fp, variables=defaults, strict=self.strict,
handle_defaults=self._handle_defaults)
self.manifest_defaults[filename] = defaults
parent_section_found = False
# get the tests
for section, data in sections:
+ subsuite = ''
+ if 'subsuite' in data:
+ subsuite = data['subsuite']
+
# In case of defaults only, no other section than parent: has to
# be processed.
if defaults_only and not section.startswith('parent:'):
continue
# read the parent manifest if specified
if section.startswith('parent:'):
parent_section_found = True
@@ -212,16 +216,17 @@ class ManifestParser(object):
# When the rootdir is unknown, the relpath needs to be left
# unchanged. We use an empty string as rootdir in that case,
# which leaves relpath unchanged after slicing.
if path.startswith(rootdir):
_relpath = path[len(rootdir):]
else:
_relpath = relpath(path, rootdir)
+ test['subsuite'] = subsuite
test['path'] = path
test['relpath'] = _relpath
if parentmanifest is not None:
# If a test was included by a parent manifest we may need to
# indicate that in the test object for the sake of identifying
# a test, particularly in the case a test file is included by
# multiple manifests.
--- a/testing/mozbase/manifestparser/tests/test_convert_directory.py
+++ b/testing/mozbase/manifestparser/tests/test_convert_directory.py
@@ -55,22 +55,26 @@ class TestDirectoryConversion(unittest.T
stub = self.create_stub()
try:
stub = stub.replace(os.path.sep, "/")
self.assertTrue(os.path.exists(stub) and os.path.isdir(stub))
# Make a manifest for it
manifest = convert([stub])
out_tmpl = """[%(stub)s/bar]
+subsuite =
[%(stub)s/fleem]
+subsuite =
[%(stub)s/foo]
+subsuite =
[%(stub)s/subdir/subfile]
+subsuite =
""" # noqa
self.assertEqual(str(manifest), out_tmpl % dict(stub=stub))
except:
raise
finally:
shutil.rmtree(stub) # cleanup
--- a/testing/mozbase/manifestparser/tests/test_default_overrides.py
+++ b/testing/mozbase/manifestparser/tests/test_default_overrides.py
@@ -2,17 +2,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/.
import os
import unittest
from manifestparser import ManifestParser
-from manifestparser import combine_fields
here = os.path.dirname(os.path.abspath(__file__))
class TestDefaultSkipif(unittest.TestCase):
"""Tests applying a skip-if condition in [DEFAULT] and || with the value for the test"""
def test_defaults(self):
@@ -89,27 +88,10 @@ class TestOmitDefaults(unittest.TestCase
}
for path, defaults in expected_defaults.items():
self.assertIn(path, parser.manifest_defaults)
actual_defaults = parser.manifest_defaults[path]
for key, value in defaults.items():
self.assertIn(key, actual_defaults)
self.assertEqual(value, actual_defaults[key])
-
-class TestSubsuiteDefaults(unittest.TestCase):
- """Test that subsuites are handled correctly when managing defaults
- outside of the manifest parser."""
- def test_subsuite_defaults(self):
- manifest = os.path.join(here, 'default-subsuite.ini')
- parser = ManifestParser(manifests=(manifest,), handle_defaults=False)
- expected_subsuites = {
- 'test1': 'baz',
- 'test2': 'foo',
- }
- defaults = parser.manifest_defaults[manifest]
- for test in parser.tests:
- value = combine_fields(defaults, test)
- self.assertEqual(expected_subsuites[value['name']],
- value['subsuite'])
-
if __name__ == '__main__':
unittest.main()
--- a/testing/mozbase/manifestparser/tests/test_manifestparser.py
+++ b/testing/mozbase/manifestparser/tests/test_manifestparser.py
@@ -109,20 +109,22 @@ class TestManifestParser(unittest.TestCa
# Write the output to a manifest:
buffer = StringIO()
parser.write(fp=buffer, global_kwargs={'foo': 'bar'})
expected_output = """[DEFAULT]
foo = bar
[fleem]
+subsuite =
[include/flowers]
blue = ocean
red = roses
+subsuite =
yellow = submarine""" # noqa
self.assertEqual(buffer.getvalue().strip(),
expected_output)
def test_invalid_path(self):
"""
Test invalid path should not throw when not strict
@@ -150,17 +152,17 @@ yellow = submarine""" # noqa
# DEFAULT values should be the ones from level 1
self.assertEqual(parser.get('name', x='level_1'),
['test_3'])
# Write the output to a manifest:
buffer = StringIO()
parser.write(fp=buffer, global_kwargs={'x': 'level_1'})
self.assertEqual(buffer.getvalue().strip(),
- '[DEFAULT]\nx = level_1\n\n[test_3]')
+ '[DEFAULT]\nx = level_1\n\n[test_3]\nsubsuite =')
def test_parent_defaults(self):
"""
Test downstream variables should overwrite upstream variables
"""
parent_example = os.path.join(here, 'parent', 'level_1', 'level_2',
'level_3', 'level_3_default.ini')
parser = ManifestParser(manifests=(parent_example,))
@@ -175,17 +177,17 @@ yellow = submarine""" # noqa
# DEFAULT values should be the ones from level 3
self.assertEqual(parser.get('name', x='level_3'),
['test_3'])
# Write the output to a manifest:
buffer = StringIO()
parser.write(fp=buffer, global_kwargs={'x': 'level_3'})
self.assertEqual(buffer.getvalue().strip(),
- '[DEFAULT]\nx = level_3\n\n[test_3]')
+ '[DEFAULT]\nx = level_3\n\n[test_3]\nsubsuite =')
def test_parent_defaults_include(self):
parent_example = os.path.join(here, 'parent', 'include', 'manifest.ini')
parser = ManifestParser(manifests=(parent_example,))
# global defaults should inherit all includes
self.assertEqual(parser.get('name', top='data'),
['testFirst.js', 'testSecond.js'])
--- a/testing/mozharness/configs/builds/releng_base_android_64_builds.py
+++ b/testing/mozharness/configs/builds/releng_base_android_64_builds.py
@@ -37,16 +37,22 @@ config = {
('/builds/mozilla-fennec-geoloc-api.key', '/builds/mozilla-fennec-geoloc-api.key'),
('/builds/crash-stats-api.token', '/builds/crash-stats-api.token'),
('/usr/local/lib/hgext', '/usr/local/lib/hgext'),
],
'secret_files': [
{'filename': '/builds/mozilla-fennec-geoloc-api.key',
'secret_name': 'project/releng/gecko/build/level-%(scm-level)s/mozilla-fennec-geoloc-api.key',
'min_scm_level': 2, 'default': 'try-build-has-no-secrets'},
+ {'filename': '/builds/adjust-sdk.token',
+ 'secret_name': 'project/releng/gecko/build/level-%(scm-level)s/adjust-sdk.token',
+ 'min_scm_level': 2, 'default': 'try-build-has-no-secrets'},
+ {'filename': '/builds/adjust-sdk-beta.token',
+ 'secret_name': 'project/releng/gecko/build/level-%(scm-level)s/adjust-sdk-beta.token',
+ 'min_scm_level': 2, 'default': 'try-build-has-no-secrets'},
],
'enable_ccache': True,
'vcs_share_base': '/builds/hg-shared',
'objdir': 'obj-firefox',
'tooltool_script': ["/builds/tooltool.py"],
'tooltool_bootstrap': "setup.sh",
'enable_count_ctors': False,
'enable_talos_sendchange': True,
--- a/testing/web-platform/meta/html/dom/reflection-embedded.html.ini
+++ b/testing/web-platform/meta/html/dom/reflection-embedded.html.ini
@@ -691,92 +691,8 @@
expected: FAIL
[area.type: IDL set to object "test-toString" followed by IDL get]
expected: FAIL
[area.type: IDL set to object "test-valueOf" followed by IDL get]
expected: FAIL
- [area.ping: setAttribute() to " \\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07 \\b\\t\\n\\v\\f\\r\\x0e\\x0f \\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17 \\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f foo " followed by IDL get]
- expected: FAIL
-
- [area.ping: setAttribute() to undefined followed by IDL get]
- expected: FAIL
-
- [area.ping: setAttribute() to 7 followed by IDL get]
- expected: FAIL
-
- [area.ping: setAttribute() to 1.5 followed by IDL get]
- expected: FAIL
-
- [area.ping: setAttribute() to true followed by IDL get]
- expected: FAIL
-
- [area.ping: setAttribute() to false followed by IDL get]
- expected: FAIL
-
- [area.ping: setAttribute() to object "[object Object\]" followed by IDL get]
- expected: FAIL
-
- [area.ping: setAttribute() to NaN followed by IDL get]
- expected: FAIL
-
- [area.ping: setAttribute() to Infinity followed by IDL get]
- expected: FAIL
-
- [area.ping: setAttribute() to -Infinity followed by IDL get]
- expected: FAIL
-
- [area.ping: setAttribute() to "\\0" followed by IDL get]
- expected: FAIL
-
- [area.ping: setAttribute() to null followed by IDL get]
- expected: FAIL
-
- [area.ping: setAttribute() to object "test-toString" followed by IDL get]
- expected: FAIL
-
- [area.ping: setAttribute() to object "test-valueOf" followed by IDL get]
- expected: FAIL
-
- [area.ping: IDL set to " \\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07 \\b\\t\\n\\v\\f\\r\\x0e\\x0f \\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17 \\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f foo " followed by IDL get]
- expected: FAIL
-
- [area.ping: IDL set to undefined followed by IDL get]
- expected: FAIL
-
- [area.ping: IDL set to 7 followed by IDL get]
- expected: FAIL
-
- [area.ping: IDL set to 1.5 followed by IDL get]
- expected: FAIL
-
- [area.ping: IDL set to true followed by IDL get]
- expected: FAIL
-
- [area.ping: IDL set to false followed by IDL get]
- expected: FAIL
-
- [area.ping: IDL set to object "[object Object\]" followed by IDL get]
- expected: FAIL
-
- [area.ping: IDL set to NaN followed by IDL get]
- expected: FAIL
-
- [area.ping: IDL set to Infinity followed by IDL get]
- expected: FAIL
-
- [area.ping: IDL set to -Infinity followed by IDL get]
- expected: FAIL
-
- [area.ping: IDL set to "\\0" followed by IDL get]
- expected: FAIL
-
- [area.ping: IDL set to null followed by IDL get]
- expected: FAIL
-
- [area.ping: IDL set to object "test-toString" followed by IDL get]
- expected: FAIL
-
- [area.ping: IDL set to object "test-valueOf" followed by IDL get]
- expected: FAIL
-
--- a/testing/web-platform/meta/html/dom/reflection-text.html.ini
+++ b/testing/web-platform/meta/html/dom/reflection-text.html.ini
@@ -169,92 +169,8 @@
expected: FAIL
[wbr.tabIndex: setAttribute() to object "3" followed by getAttribute()]
expected: FAIL
[wbr.tabIndex: setAttribute() to object "3" followed by IDL get]
expected: FAIL
- [a.ping: setAttribute() to " \\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07 \\b\\t\\n\\v\\f\\r\\x0e\\x0f \\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17 \\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f foo " followed by IDL get]
- expected: FAIL
-
- [a.ping: setAttribute() to undefined followed by IDL get]
- expected: FAIL
-
- [a.ping: setAttribute() to 7 followed by IDL get]
- expected: FAIL
-
- [a.ping: setAttribute() to 1.5 followed by IDL get]
- expected: FAIL
-
- [a.ping: setAttribute() to true followed by IDL get]
- expected: FAIL
-
- [a.ping: setAttribute() to false followed by IDL get]
- expected: FAIL
-
- [a.ping: setAttribute() to object "[object Object\]" followed by IDL get]
- expected: FAIL
-
- [a.ping: setAttribute() to NaN followed by IDL get]
- expected: FAIL
-
- [a.ping: setAttribute() to Infinity followed by IDL get]
- expected: FAIL
-
- [a.ping: setAttribute() to -Infinity followed by IDL get]
- expected: FAIL
-
- [a.ping: setAttribute() to "\\0" followed by IDL get]
- expected: FAIL
-
- [a.ping: setAttribute() to null followed by IDL get]
- expected: FAIL
-
- [a.ping: setAttribute() to object "test-toString" followed by IDL get]
- expected: FAIL
-
- [a.ping: setAttribute() to object "test-valueOf" followed by IDL get]
- expected: FAIL
-
- [a.ping: IDL set to " \\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07 \\b\\t\\n\\v\\f\\r\\x0e\\x0f \\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17 \\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f foo " followed by IDL get]
- expected: FAIL
-
- [a.ping: IDL set to undefined followed by IDL get]
- expected: FAIL
-
- [a.ping: IDL set to 7 followed by IDL get]
- expected: FAIL
-
- [a.ping: IDL set to 1.5 followed by IDL get]
- expected: FAIL
-
- [a.ping: IDL set to true followed by IDL get]
- expected: FAIL
-
- [a.ping: IDL set to false followed by IDL get]
- expected: FAIL
-
- [a.ping: IDL set to object "[object Object\]" followed by IDL get]
- expected: FAIL
-
- [a.ping: IDL set to NaN followed by IDL get]
- expected: FAIL
-
- [a.ping: IDL set to Infinity followed by IDL get]
- expected: FAIL
-
- [a.ping: IDL set to -Infinity followed by IDL get]
- expected: FAIL
-
- [a.ping: IDL set to "\\0" followed by IDL get]
- expected: FAIL
-
- [a.ping: IDL set to null followed by IDL get]
- expected: FAIL
-
- [a.ping: IDL set to object "test-toString" followed by IDL get]
- expected: FAIL
-
- [a.ping: IDL set to object "test-valueOf" followed by IDL get]
- expected: FAIL
-
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -1903,20 +1903,26 @@ TelemetryImpl::GetIsOfficialTelemetry(bo
return NS_OK;
}
already_AddRefed<nsITelemetry>
TelemetryImpl::CreateTelemetryInstance()
{
MOZ_ASSERT(sTelemetry == nullptr, "CreateTelemetryInstance may only be called once, via GetService()");
+ bool useTelemetry = false;
+ if (XRE_IsParentProcess() ||
+ XRE_IsContentProcess() ||
+ XRE_IsGPUProcess())
+ {
+ useTelemetry = true;
+ }
+
// First, initialize the TelemetryHistogram and TelemetryScalar global states.
- TelemetryHistogram::InitializeGlobalState(
- XRE_IsParentProcess() || XRE_IsContentProcess(),
- XRE_IsParentProcess() || XRE_IsContentProcess());
+ TelemetryHistogram::InitializeGlobalState(useTelemetry, useTelemetry);
// Only record scalars from the parent process.
TelemetryScalar::InitializeGlobalState(XRE_IsParentProcess(), XRE_IsParentProcess());
// Now, create and initialize the Telemetry global state.
sTelemetry = new TelemetryImpl();
// AddRef for the local reference
@@ -2258,33 +2264,34 @@ TelemetryImpl::RecordThreadHangStats(Tel
NS_IMPL_ISUPPORTS(TelemetryImpl, nsITelemetry, nsIMemoryReporter)
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITelemetry, TelemetryImpl::CreateTelemetryInstance)
#define NS_TELEMETRY_CID \
{0xaea477f2, 0xb3a2, 0x469c, {0xaa, 0x29, 0x0a, 0x82, 0xd1, 0x32, 0xb8, 0x29}}
NS_DEFINE_NAMED_CID(NS_TELEMETRY_CID);
const Module::CIDEntry kTelemetryCIDs[] = {
- { &kNS_TELEMETRY_CID, false, nullptr, nsITelemetryConstructor },
+ { &kNS_TELEMETRY_CID, false, nullptr, nsITelemetryConstructor, Module::ALLOW_IN_GPU_PROCESS },
{ nullptr }
};
const Module::ContractIDEntry kTelemetryContracts[] = {
- { "@mozilla.org/base/telemetry;1", &kNS_TELEMETRY_CID },
+ { "@mozilla.org/base/telemetry;1", &kNS_TELEMETRY_CID, Module::ALLOW_IN_GPU_PROCESS },
{ nullptr }
};
const Module kTelemetryModule = {
Module::kVersion,
kTelemetryCIDs,
kTelemetryContracts,
nullptr,
nullptr,
nullptr,
- TelemetryImpl::ShutdownTelemetry
+ TelemetryImpl::ShutdownTelemetry,
+ Module::ALLOW_IN_GPU_PROCESS
};
NS_IMETHODIMP
TelemetryImpl::GetFileIOReports(JSContext *cx, JS::MutableHandleValue ret)
{
if (sTelemetryIOObserver) {
JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
if (!obj) {
@@ -2824,25 +2831,27 @@ AccumulateCategorical(ID id, const nsCSt
void
AccumulateTimeDelta(ID aHistogram, TimeStamp start, TimeStamp end)
{
Accumulate(aHistogram,
static_cast<uint32_t>((end - start).ToMilliseconds()));
}
void
-AccumulateChild(const nsTArray<Accumulation>& aAccumulations)
+AccumulateChild(GeckoProcessType aProcessType,
+ const nsTArray<Accumulation>& aAccumulations)
{
- TelemetryHistogram::AccumulateChild(aAccumulations);
+ TelemetryHistogram::AccumulateChild(aProcessType, aAccumulations);
}
void
-AccumulateChildKeyed(const nsTArray<KeyedAccumulation>& aAccumulations)
+AccumulateChildKeyed(GeckoProcessType aProcessType,
+ const nsTArray<KeyedAccumulation>& aAccumulations)
{
- TelemetryHistogram::AccumulateChildKeyed(aAccumulations);
+ TelemetryHistogram::AccumulateChildKeyed(aProcessType, aAccumulations);
}
const char*
GetHistogramName(ID id)
{
return TelemetryHistogram::GetHistogramName(id);
}
--- a/toolkit/components/telemetry/Telemetry.h
+++ b/toolkit/components/telemetry/Telemetry.h
@@ -6,16 +6,17 @@
#ifndef Telemetry_h__
#define Telemetry_h__
#include "mozilla/GuardObjects.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/StartupTimeline.h"
#include "nsTArray.h"
#include "nsStringGlue.h"
+#include "nsXULAppAPI.h"
#include "mozilla/TelemetryHistogramEnums.h"
#include "mozilla/TelemetryScalarEnums.h"
/******************************************************************************
* This implements the Telemetry system.
* It allows recording into histograms as well some more specialized data
* points and gives access to the data.
@@ -124,28 +125,28 @@ void AccumulateCategorical(ID id, const
*
* @param id - histogram id
* @param start - start time
* @param end - end time
*/
void AccumulateTimeDelta(ID id, TimeStamp start, TimeStamp end = TimeStamp::Now());
/**
- * Accumulate child data into child histograms
+ * Accumulate child process data into histograms for the given process type.
*
* @param aAccumulations - accumulation actions to perform
*/
-void AccumulateChild(const nsTArray<Accumulation>& aAccumulations);
+void AccumulateChild(GeckoProcessType aProcessType, const nsTArray<Accumulation>& aAccumulations);
/**
- * Accumulate child data into child keyed histograms
+ * Accumulate child process data into keyed histograms for the given process type.
*
* @param aAccumulations - accumulation actions to perform
*/
-void AccumulateChildKeyed(const nsTArray<KeyedAccumulation>& aAccumulations);
+void AccumulateChildKeyed(GeckoProcessType aProcessType, const nsTArray<KeyedAccumulation>& aAccumulations);
/**
* Enable/disable recording for this histogram at runtime.
* Recording is enabled by default, unless listed at kRecordingInitiallyDisabledIDs[].
* id must be a valid telemetry enum, otherwise an assertion is triggered.
*
* @param id - histogram id
* @param enabled - whether or not to enable recording from now on.
--- a/toolkit/components/telemetry/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm
@@ -148,17 +148,16 @@ const DEFAULT_ENVIRONMENT_PREFS = new Ma
["browser.zoom.full", {what: RECORD_PREF_VALUE}],
["devtools.chrome.enabled", {what: RECORD_PREF_VALUE}],
["devtools.debugger.enabled", {what: RECORD_PREF_VALUE}],
["devtools.debugger.remote-enabled", {what: RECORD_PREF_VALUE}],
["dom.ipc.plugins.asyncInit.enabled", {what: RECORD_PREF_VALUE}],
["dom.ipc.plugins.enabled", {what: RECORD_PREF_VALUE}],
["dom.ipc.processCount", {what: RECORD_PREF_VALUE, requiresRestart: true}],
["dom.max_script_run_time", {what: RECORD_PREF_VALUE}],
- ["e10s.rollout.disabledByLongSpinners", {what: RECORD_PREF_VALUE}],
["experiments.manifest.uri", {what: RECORD_PREF_VALUE}],
["extensions.autoDisableScopes", {what: RECORD_PREF_VALUE}],
["extensions.enabledScopes", {what: RECORD_PREF_VALUE}],
["extensions.blocklist.enabled", {what: RECORD_PREF_VALUE}],
["extensions.blocklist.url", {what: RECORD_PREF_VALUE}],
["extensions.strictCompatibility", {what: RECORD_PREF_VALUE}],
["extensions.update.enabled", {what: RECORD_PREF_VALUE}],
["extensions.update.url", {what: RECORD_PREF_VALUE}],
--- a/toolkit/components/telemetry/TelemetryHistogram.cpp
+++ b/toolkit/components/telemetry/TelemetryHistogram.cpp
@@ -11,16 +11,18 @@
#include "nsTHashtable.h"
#include "nsHashKeys.h"
#include "nsBaseHashtable.h"
#include "nsClassHashtable.h"
#include "nsITelemetry.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/gfx/GPUParent.h"
+#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/Atomics.h"
#include "mozilla/StartupTimeline.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/Unused.h"
#include "TelemetryCommon.h"
#include "TelemetryHistogram.h"
@@ -94,17 +96,18 @@ using mozilla::Telemetry::KeyedAccumulat
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
//
// PRIVATE TYPES
#define EXPIRED_ID "__expired__"
#define SUBSESSION_HISTOGRAM_PREFIX "sub#"
#define KEYED_HISTOGRAM_NAME_SEPARATOR "#"
-#define CHILD_HISTOGRAM_SUFFIX "#content"
+#define CONTENT_HISTOGRAM_SUFFIX "#content"
+#define GPU_HISTOGRAM_SUFFIX "#gpu"
namespace {
using mozilla::Telemetry::Common::AutoHashtable;
using mozilla::Telemetry::Common::IsExpiredVersion;
using mozilla::Telemetry::Common::CanRecordDataset;
using mozilla::Telemetry::Common::IsInDataset;
@@ -335,26 +338,16 @@ HistogramInfo::label_id(const char* labe
*labelId = i;
return NS_OK;
}
}
return NS_ERROR_FAILURE;
}
-bool
-StringEndsWith(const std::string& name, const std::string& suffix)
-{
- if (name.size() < suffix.size()) {
- return false;
- }
-
- return name.compare(name.size() - suffix.size(), suffix.size(), suffix) == 0;
-}
-
struct TelemetryShutdownObserver : public nsIObserver
{
NS_DECL_ISUPPORTS
NS_IMETHOD Observe(nsISupports*, const char* aTopic, const char16_t*) override
{
if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) != 0) {
return NS_OK;
@@ -450,25 +443,57 @@ internal_HistogramGet(const char *name,
break;
default:
NS_ASSERTION(false, "Invalid histogram type");
return NS_ERROR_INVALID_ARG;
}
return NS_OK;
}
-CharPtrEntryType*
-internal_GetHistogramMapEntry(const char* name)
+// Read the process type from the given histogram name. The process type, if
+// one exists, is embedded in a suffix.
+GeckoProcessType
+GetProcessFromName(const nsACString& aString)
+{
+ if (StringEndsWith(aString, NS_LITERAL_CSTRING(CONTENT_HISTOGRAM_SUFFIX))) {
+ return GeckoProcessType_Content;
+ }
+ if (StringEndsWith(aString, NS_LITERAL_CSTRING(GPU_HISTOGRAM_SUFFIX))) {
+ return GeckoProcessType_GPU;
+ }
+ return GeckoProcessType_Default;
+}
+
+const char*
+SuffixForProcessType(GeckoProcessType aProcessType)
{
- nsDependentCString histogramName(name);
- NS_NAMED_LITERAL_CSTRING(suffix, CHILD_HISTOGRAM_SUFFIX);
- if (!StringEndsWith(histogramName, suffix)) {
- return gHistogramMap.GetEntry(name);
+ switch (aProcessType) {
+ case GeckoProcessType_Default:
+ return nullptr;
+ case GeckoProcessType_Content:
+ return CONTENT_HISTOGRAM_SUFFIX;
+ case GeckoProcessType_GPU:
+ return GPU_HISTOGRAM_SUFFIX;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown process type");
+ return nullptr;
}
- auto root = Substring(histogramName, 0, histogramName.Length() - suffix.Length());
+}
+
+CharPtrEntryType*
+internal_GetHistogramMapEntry(const char* aName)
+{
+ nsDependentCString name(aName);
+ GeckoProcessType process = GetProcessFromName(name);
+ const char* suffix = SuffixForProcessType(process);
+ if (!suffix) {
+ return gHistogramMap.GetEntry(aName);
+ }
+
+ auto root = Substring(name, 0, name.Length() - strlen(suffix));
return gHistogramMap.GetEntry(PromiseFlatCString(root).get());
}
nsresult
internal_GetHistogramEnumId(const char *name, mozilla::Telemetry::ID *id)
{
if (!gInitDone) {
return NS_ERROR_FAILURE;
@@ -479,36 +504,54 @@ internal_GetHistogramEnumId(const char *
return NS_ERROR_INVALID_ARG;
}
*id = entry->mData;
return NS_OK;
}
// O(1) histogram lookup by numeric id
nsresult
-internal_GetHistogramByEnumId(mozilla::Telemetry::ID id, Histogram **ret,
- bool child = false)
+internal_GetHistogramByEnumId(mozilla::Telemetry::ID id, Histogram **ret, GeckoProcessType aProcessType)
{
static Histogram* knownHistograms[mozilla::Telemetry::HistogramCount] = {0};
- static Histogram* knownChildHistograms[mozilla::Telemetry::HistogramCount] = {0};
- Histogram *h = child ? knownChildHistograms[id] : knownHistograms[id];
+ static Histogram* knownContentHistograms[mozilla::Telemetry::HistogramCount] = {0};
+ static Histogram* knownGPUHistograms[mozilla::Telemetry::HistogramCount] = {0};
+
+ Histogram** knownList = nullptr;
+
+ switch (aProcessType) {
+ case GeckoProcessType_Default:
+ knownList = knownHistograms;
+ break;
+ case GeckoProcessType_Content:
+ knownList = knownContentHistograms;
+ break;
+ case GeckoProcessType_GPU:
+ knownList = knownGPUHistograms;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown process type");
+ return NS_ERROR_FAILURE;
+ }
+
+ Histogram* h = knownList[id];
if (h) {
*ret = h;
return NS_OK;
}
const HistogramInfo &p = gHistograms[id];
if (p.keyed) {
return NS_ERROR_FAILURE;
}
nsCString histogramName;
histogramName.Append(p.id());
- if (child) {
- histogramName.AppendLiteral(CHILD_HISTOGRAM_SUFFIX);
+ if (const char* suffix = SuffixForProcessType(aProcessType)) {
+ histogramName.AppendASCII(suffix);
}
nsresult rv = internal_HistogramGet(histogramName.get(), p.expiration(),
p.histogramType, p.min, p.max,
p.bucketCount, true, &h);
if (NS_FAILED(rv))
return rv;
@@ -523,37 +566,33 @@ internal_GetHistogramByEnumId(mozilla::T
for (int i = 0; i < b.length; ++i) {
MOZ_ASSERT(gBucketLowerBounds[b.offset + i] == h->ranges(i),
"C++/Python bucket mismatch");
}
}
}
#endif
- if (child) {
- *ret = knownChildHistograms[id] = h;
- } else {
- *ret = knownHistograms[id] = h;
- }
+ knownList[id] = h;
+ *ret = h;
return NS_OK;
}
nsresult
internal_GetHistogramByName(const nsACString &name, Histogram **ret)
{
mozilla::Telemetry::ID id;
nsresult rv
= internal_GetHistogramEnumId(PromiseFlatCString(name).get(), &id);
if (NS_FAILED(rv)) {
return rv;
}
- bool isChild = StringEndsWith(name,
- NS_LITERAL_CSTRING(CHILD_HISTOGRAM_SUFFIX));
- rv = internal_GetHistogramByEnumId(id, ret, isChild);
+ GeckoProcessType process = GetProcessFromName(name);
+ rv = internal_GetHistogramByEnumId(id, ret, process);
if (NS_FAILED(rv))
return rv;
return NS_OK;
}
/**
* This clones a histogram |existing| with the id |existingId| to a
@@ -590,61 +629,80 @@ internal_CloneHistogram(const nsACString
* with the name |newName|.
* For simplicity this is limited to registered histograms.
*/
Histogram*
internal_CloneHistogram(const nsACString& newName,
mozilla::Telemetry::ID existingId)
{
Histogram *existing = nullptr;
- nsresult rv = internal_GetHistogramByEnumId(existingId, &existing);
+ nsresult rv = internal_GetHistogramByEnumId(existingId, &existing, GeckoProcessType_Default);
if (NS_FAILED(rv)) {
return nullptr;
}
return internal_CloneHistogram(newName, existingId, *existing);
}
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
+GeckoProcessType
+GetProcessFromName(const std::string& aString)
+{
+ nsDependentCString string(aString.c_str(), aString.length());
+ return GetProcessFromName(string);
+}
+
Histogram*
internal_GetSubsessionHistogram(Histogram& existing)
{
mozilla::Telemetry::ID id;
nsresult rv
= internal_GetHistogramEnumId(existing.histogram_name().c_str(), &id);
if (NS_FAILED(rv) || gHistograms[id].keyed) {
return nullptr;
}
- bool isChild = StringEndsWith(existing.histogram_name(),
- CHILD_HISTOGRAM_SUFFIX);
-
static Histogram* subsession[mozilla::Telemetry::HistogramCount] = {};
- static Histogram* subsessionChild[mozilla::Telemetry::HistogramCount] = {};
- Histogram* cached = isChild ? subsessionChild[id] : subsession[id];
- if (cached) {
+ static Histogram* subsessionContent[mozilla::Telemetry::HistogramCount] = {};
+ static Histogram* subsessionGPU[mozilla::Telemetry::HistogramCount] = {};
+
+ Histogram** cache = nullptr;
+
+ GeckoProcessType process = GetProcessFromName(existing.histogram_name());
+ switch (process) {
+ case GeckoProcessType_Default:
+ cache = subsession;
+ break;
+ case GeckoProcessType_Content:
+ cache = subsessionContent;
+ break;
+ case GeckoProcessType_GPU:
+ cache = subsessionGPU;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown process type");
+ return nullptr;
+ }
+
+ if (Histogram* cached = cache[id]) {
return cached;
}
NS_NAMED_LITERAL_CSTRING(prefix, SUBSESSION_HISTOGRAM_PREFIX);
nsDependentCString existingName(gHistograms[id].id());
if (StringBeginsWith(existingName, prefix)) {
return nullptr;
}
nsCString subsessionName(prefix);
subsessionName.Append(existing.histogram_name().c_str());
Histogram* clone = internal_CloneHistogram(subsessionName, id, existing);
- if (isChild) {
- subsessionChild[id] = clone;
- } else {
- subsession[id] = clone;
- }
+ cache[id] = clone;
return clone;
}
#endif
nsresult
internal_HistogramAdd(Histogram& histogram, int32_t value, uint32_t dataset)
{
// Check if we are allowed to record the data.
@@ -1286,17 +1344,17 @@ internal_SetHistogramRecordingEnabled(mo
const nsDependentCString id(gHistograms[aID].id());
KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
if (keyed) {
keyed->SetRecordingEnabled(aEnabled);
return;
}
} else {
Histogram *h;
- nsresult rv = internal_GetHistogramByEnumId(aID, &h);
+ nsresult rv = internal_GetHistogramByEnumId(aID, &h, GeckoProcessType_Default);
if (NS_SUCCEEDED(rv)) {
h->SetRecordingEnabled(aEnabled);
return;
}
}
MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) id not found");
}
@@ -1378,17 +1436,17 @@ void internal_Accumulate(mozilla::Teleme
{
bool isValid = internal_IsHistogramEnumId(aHistogram);
MOZ_ASSERT(isValid, "Accumulation using invalid id");
if (!internal_CanRecordBase() || !isValid ||
internal_RemoteAccumulate(aHistogram, aSample)) {
return;
}
Histogram *h;
- nsresult rv = internal_GetHistogramByEnumId(aHistogram, &h);
+ nsresult rv = internal_GetHistogramByEnumId(aHistogram, &h, GeckoProcessType_Default);
if (NS_SUCCEEDED(rv)) {
internal_HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset);
}
}
void
internal_Accumulate(mozilla::Telemetry::ID aID,
const nsCString& aKey, uint32_t aSample)
@@ -1432,41 +1490,50 @@ internal_Accumulate(KeyedHistogram& aKey
mozilla::Telemetry::ID id;
if (NS_SUCCEEDED(aKeyed.GetEnumId(id))) {
internal_RemoteAccumulate(id, aKey, aSample);
}
}
void
-internal_AccumulateChild(mozilla::Telemetry::ID aId, uint32_t aSample)
+internal_AccumulateChild(GeckoProcessType aProcessType, mozilla::Telemetry::ID aId, uint32_t aSample)
{
if (!internal_CanRecordBase()) {
return;
}
Histogram* h;
- nsresult rv = internal_GetHistogramByEnumId(aId, &h, true);
+ nsresult rv = internal_GetHistogramByEnumId(aId, &h, aProcessType);
if (NS_SUCCEEDED(rv)) {
internal_HistogramAdd(*h, aSample, gHistograms[aId].dataset);
} else {
NS_WARNING("NS_FAILED GetHistogramByEnumId for CHILD");
}
}
void
-internal_AccumulateChildKeyed(mozilla::Telemetry::ID aId,
+internal_AccumulateChildKeyed(GeckoProcessType aProcessType, mozilla::Telemetry::ID aId,
const nsCString& aKey, uint32_t aSample)
{
if (!gInitDone || !internal_CanRecordBase()) {
return;
}
+
+ const char* suffix = SuffixForProcessType(aProcessType);
+ if (!suffix) {
+ MOZ_ASSERT_UNREACHABLE("suffix should not be null");
+ return;
+ }
+
const HistogramInfo& th = gHistograms[aId];
+
nsCString id;
id.Append(th.id());
- id.AppendLiteral(CHILD_HISTOGRAM_SUFFIX);
+ id.AppendASCII(suffix);
+
KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
MOZ_ASSERT(keyed);
keyed->Add(aKey, aSample);
}
} // namespace
@@ -2023,19 +2090,26 @@ void TelemetryHistogram::InitializeGloba
const nsDependentCString id(h.id());
const nsDependentCString expiration(h.expiration());
gKeyedHistograms.Put(id, new KeyedHistogram(id, expiration, h.histogramType,
h.min, h.max, h.bucketCount, h.dataset));
if (XRE_IsParentProcess()) {
// We must create registered child keyed histograms as well or else the
// same code in TelemetrySession.jsm that fails without parent keyed
// histograms will fail without child keyed histograms.
- nsCString childId(id);
- childId.AppendLiteral(CHILD_HISTOGRAM_SUFFIX);
- gKeyedHistograms.Put(childId,
+ nsCString contentId(id);
+ contentId.AppendLiteral(CONTENT_HISTOGRAM_SUFFIX);
+ gKeyedHistograms.Put(contentId,
+ new KeyedHistogram(id, expiration, h.histogramType,
+ h.min, h.max, h.bucketCount, h.dataset));
+
+
+ nsCString gpuId(id);
+ gpuId.AppendLiteral(GPU_HISTOGRAM_SUFFIX);
+ gKeyedHistograms.Put(gpuId,
new KeyedHistogram(id, expiration, h.histogramType,
h.min, h.max, h.bucketCount, h.dataset));
}
}
// Some Telemetry histograms depend on the value of C++ constants and hardcode
// their values in Histograms.json.
// We add static asserts here for those values to match so that future changes
@@ -2207,48 +2281,51 @@ TelemetryHistogram::AccumulateCategorica
uint32_t labelId = 0;
if (NS_FAILED(gHistograms[aId].label_id(label.get(), &labelId))) {
return;
}
internal_Accumulate(aId, labelId);
}
void
-TelemetryHistogram::AccumulateChild(const nsTArray<Accumulation>& aAccumulations)
+TelemetryHistogram::AccumulateChild(GeckoProcessType aProcessType,
+ const nsTArray<Accumulation>& aAccumulations)
{
MOZ_ASSERT(XRE_IsParentProcess());
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
if (!internal_CanRecordBase()) {
return;
}
for (uint32_t i = 0; i < aAccumulations.Length(); ++i) {
bool isValid = internal_IsHistogramEnumId(aAccumulations[i].mId);
MOZ_ASSERT(isValid);
if (!isValid) {
continue;
}
- internal_AccumulateChild(aAccumulations[i].mId, aAccumulations[i].mSample);
+ internal_AccumulateChild(aProcessType, aAccumulations[i].mId, aAccumulations[i].mSample);
}
}
void
-TelemetryHistogram::AccumulateChildKeyed(const nsTArray<KeyedAccumulation>& aAccumulations)
+TelemetryHistogram::AccumulateChildKeyed(GeckoProcessType aProcessType,
+ const nsTArray<KeyedAccumulation>& aAccumulations)
{
MOZ_ASSERT(XRE_IsParentProcess());
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
if (!internal_CanRecordBase()) {
return;
}
for (uint32_t i = 0; i < aAccumulations.Length(); ++i) {
bool isValid = internal_IsHistogramEnumId(aAccumulations[i].mId);
MOZ_ASSERT(isValid);
if (!isValid) {
continue;
}
- internal_AccumulateChildKeyed(aAccumulations[i].mId,
+ internal_AccumulateChildKeyed(aProcessType,
+ aAccumulations[i].mId,
aAccumulations[i].mKey,
aAccumulations[i].mSample);
}
}
nsresult
TelemetryHistogram::GetHistogramById(const nsACString &name, JSContext *cx,
JS::MutableHandle<JS::Value> ret)
@@ -2322,31 +2399,46 @@ TelemetryHistogram::CreateHistogramSnaps
bool clearSubsession)
{
// Runs without protection from |gTelemetryHistogramMutex|
JS::Rooted<JSObject*> root_obj(cx, JS_NewPlainObject(cx));
if (!root_obj)
return NS_ERROR_FAILURE;
ret.setObject(*root_obj);
+ // Include the GPU process in histogram snapshots only if we actually tried
+ // to launch a process for it.
+ bool includeGPUProcess = false;
+ if (auto gpm = mozilla::gfx::GPUProcessManager::Get()) {
+ includeGPUProcess = gpm->AttemptedGPUProcess();
+ }
+
// Ensure that all the HISTOGRAM_FLAG & HISTOGRAM_COUNT histograms have
// been created, so that their values are snapshotted.
for (size_t i = 0; i < mozilla::Telemetry::HistogramCount; ++i) {
if (gHistograms[i].keyed) {
continue;
}
const uint32_t type = gHistograms[i].histogramType;
if (type == nsITelemetry::HISTOGRAM_FLAG ||
type == nsITelemetry::HISTOGRAM_COUNT) {
Histogram *h;
- mozilla::DebugOnly<nsresult> rv
- = internal_GetHistogramByEnumId(mozilla::Telemetry::ID(i), &h);
+ mozilla::DebugOnly<nsresult> rv;
+ mozilla::Telemetry::ID id = mozilla::Telemetry::ID(i);
+
+ rv = internal_GetHistogramByEnumId(id, &h, GeckoProcessType_Default);
MOZ_ASSERT(NS_SUCCEEDED(rv));
- rv = internal_GetHistogramByEnumId(mozilla::Telemetry::ID(i), &h, true);
+
+ rv = internal_GetHistogramByEnumId(id, &h, GeckoProcessType_Content);
MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (includeGPUProcess) {
+ rv = internal_GetHistogramByEnumId(id, &h, GeckoProcessType_GPU);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
}
}
StatisticsRecorder::Histograms hs;
StatisticsRecorder::GetHistograms(&hs);
// We identify corrupt histograms first, rather than interspersing it
// in the loop below, to ensure that our corruption statistics don't
@@ -2625,23 +2717,42 @@ TelemetryHistogram::IPCTimerFired(nsITim
if (gAccumulations) {
accumulationsToSend.SwapElements(*gAccumulations);
}
if (gKeyedAccumulations) {
keyedAccumulationsToSend.SwapElements(*gKeyedAccumulations);
}
}
- mozilla::dom::ContentChild* contentChild = mozilla::dom::ContentChild::GetSingleton();
- mozilla::Unused << NS_WARN_IF(!contentChild);
- if (contentChild) {
- if (accumulationsToSend.Length()) {
- mozilla::Unused <<
- NS_WARN_IF(!contentChild->SendAccumulateChildHistogram(accumulationsToSend));
+ switch (XRE_GetProcessType()) {
+ case GeckoProcessType_Content: {
+ mozilla::dom::ContentChild* contentChild = mozilla::dom::ContentChild::GetSingleton();
+ mozilla::Unused << NS_WARN_IF(!contentChild);
+ if (contentChild) {
+ if (accumulationsToSend.Length()) {
+ mozilla::Unused <<
+ NS_WARN_IF(!contentChild->SendAccumulateChildHistogram(accumulationsToSend));
+ }
+ if (keyedAccumulationsToSend.Length()) {
+ mozilla::Unused <<
+ NS_WARN_IF(!contentChild->SendAccumulateChildKeyedHistogram(keyedAccumulationsToSend));
+ }
+ }
+ break;
}
- if (keyedAccumulationsToSend.Length()) {
- mozilla::Unused <<
- NS_WARN_IF(!contentChild->SendAccumulateChildKeyedHistogram(keyedAccumulationsToSend));
+ case GeckoProcessType_GPU: {
+ if (mozilla::gfx::GPUParent* gpu = mozilla::gfx::GPUParent::GetSingleton()) {
+ if (accumulationsToSend.Length()) {
+ mozilla::Unused << gpu->SendAccumulateChildHistogram(accumulationsToSend);
+ }
+ if (keyedAccumulationsToSend.Length()) {
+ mozilla::Unused << gpu->SendAccumulateChildKeyedHistogram(keyedAccumulationsToSend);
+ }
+ }
+ break;
}
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unsupported process type");
+ break;
}
gIPCTimerArmed = false;
}
--- a/toolkit/components/telemetry/TelemetryHistogram.h
+++ b/toolkit/components/telemetry/TelemetryHistogram.h
@@ -4,16 +4,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef TelemetryHistogram_h__
#define TelemetryHistogram_h__
#include "mozilla/TelemetryHistogramEnums.h"
#include "mozilla/TelemetryComms.h"
+#include "nsXULAppAPI.h"
// This module is internal to Telemetry. It encapsulates Telemetry's
// histogram accumulation and storage logic. It should only be used by
// Telemetry.cpp. These functions should not be used anywhere else.
// For the public interface to Telemetry functionality, see Telemetry.h.
namespace TelemetryHistogram {
@@ -39,18 +40,20 @@ nsresult SetHistogramRecordingEnabled(co
void Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample);
void Accumulate(mozilla::Telemetry::ID aID, const nsCString& aKey,
uint32_t aSample);
void Accumulate(const char* name, uint32_t sample);
void Accumulate(const char* name, const nsCString& key, uint32_t sample);
void AccumulateCategorical(mozilla::Telemetry::ID aId, const nsCString& aLabel);
-void AccumulateChild(const nsTArray<mozilla::Telemetry::Accumulation>& aAccumulations);
-void AccumulateChildKeyed(const nsTArray<mozilla::Telemetry::KeyedAccumulation>& aAccumulations);
+void AccumulateChild(GeckoProcessType aProcessType,
+ const nsTArray<mozilla::Telemetry::Accumulation>& aAccumulations);
+void AccumulateChildKeyed(GeckoProcessType aProcessType,
+ const nsTArray<mozilla::Telemetry::KeyedAccumulation>& aAccumulations);
nsresult
GetHistogramById(const nsACString &name, JSContext *cx,
JS::MutableHandle<JS::Value> ret);
nsresult
GetKeyedHistogramById(const nsACString &name, JSContext *cx,
JS::MutableHandle<JS::Value> ret);
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -39,16 +39,17 @@ const REASON_GATHER_PAYLOAD = "gather-pa
const REASON_GATHER_SUBSESSION_PAYLOAD = "gather-subsession-payload";
const REASON_TEST_PING = "test-ping";
const REASON_ENVIRONMENT_CHANGE = "environment-change";
const REASON_SHUTDOWN = "shutdown";
const HISTOGRAM_SUFFIXES = {
PARENT: "",
CONTENT: "#content",
+ GPU: "#gpu",
}
const ENVIRONMENT_CHANGE_LISTENER = "TelemetrySession::onEnvironmentChange";
const MS_IN_ONE_HOUR = 60 * 60 * 1000;
const MIN_SUBSESSION_LENGTH_MS = Preferences.get("toolkit.telemetry.minSubsessionLength", 10 * 60) * 1000;
const LOGGER_NAME = "Toolkit.Telemetry";
@@ -1310,16 +1311,26 @@ var Impl = {
keyedScalars: protect(() => this.getScalars(isSubsession, clearSubsession, true)),
},
content: {
histograms: histograms[HISTOGRAM_SUFFIXES.CONTENT],
keyedHistograms: keyedHistograms[HISTOGRAM_SUFFIXES.CONTENT],
},
};
+ // Only include the GPU process if we've accumulated data for it.
+ if (HISTOGRAM_SUFFIXES.GPU in histograms ||
+ HISTOGRAM_SUFFIXES.GPU in keyedHistograms)
+ {
+ payloadObj.processes.gpu = {
+ histograms: histograms[HISTOGRAM_SUFFIXES.GPU],
+ keyedHistograms: keyedHistograms[HISTOGRAM_SUFFIXES.GPU],
+ };
+ }
+
payloadObj.info = info;
// Add extended set measurements for chrome process.
if (Telemetry.canRecordExtended) {
payloadObj.slowSQL = protect(() => Telemetry.slowSQL);
payloadObj.fileIOReports = protect(() => Telemetry.fileIOReports);
payloadObj.lateWrites = protect(() => Telemetry.lateWrites);
--- a/xpcom/build/XPCOMInit.cpp
+++ b/xpcom/build/XPCOMInit.cpp
@@ -797,16 +797,17 @@ NS_InitMinimalXPCOM()
// Global cycle collector initialization.
if (!nsCycleCollector_init()) {
return NS_ERROR_UNEXPECTED;
}
AbstractThread::InitStatics();
SharedThreadPool::InitStatics();
+ mozilla::Telemetry::Init();
mozilla::HangMonitor::Startup();
mozilla::BackgroundHangMonitor::Startup();
return NS_OK;
}
//
// NS_ShutdownXPCOM()