diff --git a/caps/BasePrincipal.cpp b/caps/BasePrincipal.cpp index 0924791d23..7589b18889 100644 --- a/caps/BasePrincipal.cpp +++ b/caps/BasePrincipal.cpp @@ -263,6 +263,44 @@ BasePrincipal::SubsumesConsideringDomain(nsIPrincipal *aOther, bool *aResult) return NS_OK; } +NS_IMETHODIMP +BasePrincipal::CheckMayLoad(nsIURI* aURI, bool aReport, bool aAllowIfInheritsPrincipal) +{ + // Check the internal method first, which allows us to quickly approve loads + // for the System Principal. + if (MayLoadInternal(aURI)) { + return NS_OK; + } + + nsresult rv; + if (aAllowIfInheritsPrincipal) { + // If the caller specified to allow loads of URIs that inherit + // our principal, allow the load if this URI inherits its principal. + bool doesInheritSecurityContext; + rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, + &doesInheritSecurityContext); + if (NS_SUCCEEDED(rv) && doesInheritSecurityContext) { + return NS_OK; + } + } + + bool fetchableByAnyone; + rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_FETCHABLE_BY_ANYONE, &fetchableByAnyone); + if (NS_SUCCEEDED(rv) && fetchableByAnyone) { + return NS_OK; + } + + if (aReport) { + nsCOMPtr prinURI; + rv = GetURI(getter_AddRefs(prinURI)); + if (NS_SUCCEEDED(rv) && prinURI) { + nsScriptSecurityManager::ReportError(nullptr, NS_LITERAL_STRING("CheckSameOriginError"), prinURI, aURI); + } + } + + return NS_ERROR_DOM_BAD_URI; +} + NS_IMETHODIMP BasePrincipal::GetCsp(nsIContentSecurityPolicy** aCsp) { diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h index c32f8d705e..dc73bcb8e3 100644 --- a/caps/BasePrincipal.h +++ b/caps/BasePrincipal.h @@ -18,6 +18,8 @@ class nsILoadContext; class nsIObjectOutputStream; class nsIObjectInputStream; +class nsExpandedPrincipal; + namespace mozilla { class OriginAttributes : public dom::OriginAttributesDictionary @@ -140,6 +142,7 @@ public: NS_IMETHOD EqualsConsideringDomain(nsIPrincipal* other, bool* _retval) final; NS_IMETHOD Subsumes(nsIPrincipal* other, bool* _retval) final; NS_IMETHOD SubsumesConsideringDomain(nsIPrincipal* other, bool* _retval) final; + NS_IMETHOD CheckMayLoad(nsIURI* uri, bool report, bool allowIfInheritsPrincipal) final; NS_IMETHOD GetCsp(nsIContentSecurityPolicy** aCsp) override; NS_IMETHOD SetCsp(nsIContentSecurityPolicy* aCsp) override; NS_IMETHOD GetCspJSON(nsAString& outCSPinJSON) override; @@ -172,6 +175,12 @@ protected: virtual nsresult GetOriginInternal(nsACString& aOrigin) = 0; virtual bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsider) = 0; + // Internal, side-effect-free check to determine whether the concrete + // principal would allow the load ignoring any common behavior implemented in + // BasePrincipal::CheckMayLoad. + virtual bool MayLoadInternal(nsIURI* aURI) = 0; + friend class ::nsExpandedPrincipal; + // Helper to check whether this principal is associated with an addon that // allows unprivileged code to load aURI. bool AddonAllowsLoad(nsIURI* aURI); diff --git a/caps/nsJSPrincipals.cpp b/caps/nsJSPrincipals.cpp index 6623c6bcd9..023697a3d0 100644 --- a/caps/nsJSPrincipals.cpp +++ b/caps/nsJSPrincipals.cpp @@ -15,8 +15,13 @@ #include "nsMemory.h" #include "nsStringBuffer.h" +#include "mozilla/dom/StructuredCloneTags.h" // for mozilla::dom::workers::kJSPrincipalsDebugToken #include "mozilla/dom/workers/Workers.h" +#include "mozilla/ipc/BackgroundUtils.h" + +using namespace mozilla; +using namespace mozilla::ipc; NS_IMETHODIMP_(MozExternalRefCountType) nsJSPrincipals::AddRef() @@ -85,7 +90,7 @@ JSPrincipals::dump() nsAutoCString str; static_cast(this)->GetScriptLocation(str); fprintf(stderr, "nsIPrincipal (%p) = %s\n", static_cast(this), str.get()); - } else if (debugToken == mozilla::dom::workers::kJSPrincipalsDebugToken) { + } else if (debugToken == dom::workers::kJSPrincipalsDebugToken) { fprintf(stderr, "Web Worker principal singleton (%p)\n", this); } else { fprintf(stderr, @@ -95,4 +100,104 @@ JSPrincipals::dump() } } -#endif +#endif + +/* static */ bool +nsJSPrincipals::ReadPrincipals(JSContext* aCx, JSStructuredCloneReader* aReader, + JSPrincipals** aOutPrincipals) +{ + uint32_t tag; + uint32_t unused; + if (!JS_ReadUint32Pair(aReader, &tag, &unused)) { + return false; + } + + if (!(tag == SCTAG_DOM_NULL_PRINCIPAL || + tag == SCTAG_DOM_SYSTEM_PRINCIPAL || + tag == SCTAG_DOM_CONTENT_PRINCIPAL)) { + xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + return false; + } + + return ReadKnownPrincipalType(aCx, aReader, tag, aOutPrincipals); +} + +/* static */ bool +nsJSPrincipals::ReadKnownPrincipalType(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + JSPrincipals** aOutPrincipals) +{ + MOZ_ASSERT(aTag == SCTAG_DOM_NULL_PRINCIPAL || + aTag == SCTAG_DOM_SYSTEM_PRINCIPAL || + aTag == SCTAG_DOM_CONTENT_PRINCIPAL); + + if (NS_WARN_IF(!NS_IsMainThread())) { + xpc::Throw(aCx, NS_ERROR_UNCATCHABLE_EXCEPTION); + return false; + } + + PrincipalInfo info; + if (aTag == SCTAG_DOM_SYSTEM_PRINCIPAL) { + info = SystemPrincipalInfo(); + } else if (aTag == SCTAG_DOM_NULL_PRINCIPAL) { + info = NullPrincipalInfo(); + } else { + uint32_t suffixLength, specLength; + if (!JS_ReadUint32Pair(aReader, &suffixLength, &specLength)) { + return false; + } + + nsAutoCString suffix; + suffix.SetLength(suffixLength); + if (!JS_ReadBytes(aReader, suffix.BeginWriting(), suffixLength)) { + return false; + } + + nsAutoCString spec; + spec.SetLength(specLength); + if (!JS_ReadBytes(aReader, spec.BeginWriting(), specLength)) { + return false; + } + + OriginAttributes attrs; + attrs.PopulateFromSuffix(suffix); + info = ContentPrincipalInfo(attrs, spec); + } + + nsresult rv; + nsCOMPtr prin = PrincipalInfoToPrincipal(info, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + return false; + } + + *aOutPrincipals = get(prin.forget().take()); + return true; +} + +bool +nsJSPrincipals::write(JSContext* aCx, JSStructuredCloneWriter* aWriter) +{ + PrincipalInfo info; + if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(this, &info)))) { + xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + return false; + } + + if (info.type() == PrincipalInfo::TNullPrincipalInfo) { + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_NULL_PRINCIPAL, 0); + } + if (info.type() == PrincipalInfo::TSystemPrincipalInfo) { + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_SYSTEM_PRINCIPAL, 0); + } + + MOZ_ASSERT(info.type() == PrincipalInfo::TContentPrincipalInfo); + const ContentPrincipalInfo& cInfo = info; + nsAutoCString suffix; + cInfo.attrs().CreateSuffix(suffix); + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_CONTENT_PRINCIPAL, 0) && + JS_WriteUint32Pair(aWriter, suffix.Length(), cInfo.spec().Length()) && + JS_WriteBytes(aWriter, suffix.get(), suffix.Length()) && + JS_WriteBytes(aWriter, cInfo.spec().get(), cInfo.spec().Length()); +} diff --git a/caps/nsJSPrincipals.h b/caps/nsJSPrincipals.h index 878c1a6a0c..c108ef1960 100644 --- a/caps/nsJSPrincipals.h +++ b/caps/nsJSPrincipals.h @@ -16,6 +16,17 @@ public: static bool Subsume(JSPrincipals *jsprin, JSPrincipals *other); static void Destroy(JSPrincipals *jsprin); + /* JSReadPrincipalsOp for nsJSPrincipals */ + static bool ReadPrincipals(JSContext* aCx, JSStructuredCloneReader* aReader, + JSPrincipals** aOutPrincipals); + + static bool ReadKnownPrincipalType(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + JSPrincipals** aOutPrincipals); + + bool write(JSContext* aCx, JSStructuredCloneWriter* aWriter) final; + /* * Get a weak reference to nsIPrincipal associated with the given JS * principal, and vice-versa. diff --git a/caps/nsNullPrincipal.cpp b/caps/nsNullPrincipal.cpp index cc32748425..b155b82981 100644 --- a/caps/nsNullPrincipal.cpp +++ b/caps/nsNullPrincipal.cpp @@ -108,15 +108,9 @@ nsNullPrincipal::GetOriginInternal(nsACString& aOrigin) return mURI->GetSpec(aOrigin); } -NS_IMETHODIMP -nsNullPrincipal::CheckMayLoad(nsIURI* aURI, bool aReport, bool aAllowIfInheritsPrincipal) - { - if (aAllowIfInheritsPrincipal) { - if (nsPrincipal::IsPrincipalInherited(aURI)) { - return NS_OK; - } - } - +bool +nsNullPrincipal::MayLoadInternal(nsIURI* aURI) +{ // Also allow the load if we are the principal of the URI being checked. nsCOMPtr uriPrinc = do_QueryInterface(aURI); if (uriPrinc) { @@ -124,16 +118,11 @@ nsNullPrincipal::CheckMayLoad(nsIURI* aURI, bool aReport, bool aAllowIfInheritsP uriPrinc->GetPrincipal(getter_AddRefs(principal)); if (principal == this) { - return NS_OK; + return true; } } - if (aReport) { - nsScriptSecurityManager::ReportError( - nullptr, NS_LITERAL_STRING("CheckSameOriginError"), mURI, aURI); - } - - return NS_ERROR_DOM_BAD_URI; + return false; } NS_IMETHODIMP diff --git a/caps/nsNullPrincipal.h b/caps/nsNullPrincipal.h index 49f7e2de2d..f2852ba7d2 100644 --- a/caps/nsNullPrincipal.h +++ b/caps/nsNullPrincipal.h @@ -44,7 +44,6 @@ public: NS_IMETHOD GetURI(nsIURI** aURI) override; NS_IMETHOD GetDomain(nsIURI** aDomain) override; NS_IMETHOD SetDomain(nsIURI* aDomain) override; - NS_IMETHOD CheckMayLoad(nsIURI* uri, bool report, bool allowIfInheritsPrincipal) override; NS_IMETHOD GetIsNullPrincipal(bool* aIsNullPrincipal) override; NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override; nsresult GetOriginInternal(nsACString& aOrigin) override; @@ -68,6 +67,8 @@ public: return aOther == this; } + bool MayLoadInternal(nsIURI* aURI) override; + nsCOMPtr mURI; nsCOMPtr mCSP; }; diff --git a/caps/nsPrincipal.cpp b/caps/nsPrincipal.cpp index 5f215655a0..4e19baff87 100644 --- a/caps/nsPrincipal.cpp +++ b/caps/nsPrincipal.cpp @@ -248,17 +248,9 @@ nsPrincipal::GetURI(nsIURI** aURI) return NS_EnsureSafeToReturn(mCodebase, aURI); } -NS_IMETHODIMP -nsPrincipal::CheckMayLoad(nsIURI* aURI, bool aReport, bool aAllowIfInheritsPrincipal) +bool +nsPrincipal::MayLoadInternal(nsIURI* aURI) { - if (aAllowIfInheritsPrincipal) { - // If the caller specified to allow loads of URIs that inherit - // our principal, allow the load if this URI inherits its principal - if (nsPrincipal::IsPrincipalInherited(aURI)) { - return NS_OK; - } - } - // See if aURI is something like a Blob URI that is actually associated with // a principal. nsCOMPtr uriWithPrin = do_QueryInterface(aURI); @@ -267,17 +259,17 @@ nsPrincipal::CheckMayLoad(nsIURI* aURI, bool aReport, bool aAllowIfInheritsPrinc uriWithPrin->GetPrincipal(getter_AddRefs(uriPrin)); } if (uriPrin && nsIPrincipal::Subsumes(uriPrin)) { - return NS_OK; + return true; } // If this principal is associated with an addon, check whether that addon // has been given permission to load from this domain. if (AddonAllowsLoad(aURI)) { - return NS_OK; + return true; } if (nsScriptSecurityManager::SecurityCompareURIs(mCodebase, aURI)) { - return NS_OK; + return true; } // If strict file origin policy is in effect, local files will always fail @@ -286,13 +278,10 @@ nsPrincipal::CheckMayLoad(nsIURI* aURI, bool aReport, bool aAllowIfInheritsPrinc if (nsScriptSecurityManager::GetStrictFileOriginPolicy() && NS_URIIsLocalFile(aURI) && NS_RelaxStrictFileOriginPolicy(aURI, mCodebase)) { - return NS_OK; + return true; } - if (aReport) { - nsScriptSecurityManager::ReportError(nullptr, NS_LITERAL_STRING("CheckSameOriginError"), mCodebase, aURI); - } - return NS_ERROR_DOM_BAD_URI; + return false; } void @@ -753,17 +742,16 @@ nsExpandedPrincipal::SubsumesInternal(nsIPrincipal* aOther, return false; } -NS_IMETHODIMP -nsExpandedPrincipal::CheckMayLoad(nsIURI* uri, bool aReport, bool aAllowIfInheritsPrincipal) +bool +nsExpandedPrincipal::MayLoadInternal(nsIURI* uri) { - nsresult rv; for (uint32_t i = 0; i < mPrincipals.Length(); ++i){ - rv = mPrincipals[i]->CheckMayLoad(uri, aReport, aAllowIfInheritsPrincipal); - if (NS_SUCCEEDED(rv)) - return rv; + if (BasePrincipal::Cast(mPrincipals[i])->MayLoadInternal(uri)) { + return true; + } } - return NS_ERROR_DOM_BAD_URI; + return false; } NS_IMETHODIMP diff --git a/caps/nsPrincipal.h b/caps/nsPrincipal.h index 62f0cfa1ce..1c43606290 100644 --- a/caps/nsPrincipal.h +++ b/caps/nsPrincipal.h @@ -26,7 +26,6 @@ public: NS_IMETHOD GetURI(nsIURI** aURI) override; NS_IMETHOD GetDomain(nsIURI** aDomain) override; NS_IMETHOD SetDomain(nsIURI* aDomain) override; - NS_IMETHOD CheckMayLoad(nsIURI* uri, bool report, bool allowIfInheritsPrincipal) override; NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override; virtual bool IsOnCSSUnprefixingWhitelist() override; bool IsCodebasePrincipal() const override { return true; } @@ -40,23 +39,6 @@ public: virtual void GetScriptLocation(nsACString& aStr) override; void SetURI(nsIURI* aURI); - static bool IsPrincipalInherited(nsIURI* aURI) { - // return true if the loadee URI has - // the URI_INHERITS_SECURITY_CONTEXT flag set. - bool doesInheritSecurityContext; - nsresult rv = - NS_URIChainHasFlags(aURI, - nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, - &doesInheritSecurityContext); - - if (NS_SUCCEEDED(rv) && doesInheritSecurityContext) { - return true; - } - - return false; - } - - /** * Computes the puny-encoded origin of aURI. */ @@ -79,6 +61,7 @@ protected: virtual ~nsPrincipal(); bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) override; + bool MayLoadInternal(nsIURI* aURI) override; }; class nsExpandedPrincipal : public nsIExpandedPrincipal, public mozilla::BasePrincipal @@ -95,7 +78,6 @@ public: NS_IMETHOD GetURI(nsIURI** aURI) override; NS_IMETHOD GetDomain(nsIURI** aDomain) override; NS_IMETHOD SetDomain(nsIURI* aDomain) override; - NS_IMETHOD CheckMayLoad(nsIURI* uri, bool report, bool allowIfInheritsPrincipal) override; NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override; virtual bool IsOnCSSUnprefixingWhitelist() override; virtual void GetScriptLocation(nsACString &aStr) override; @@ -105,6 +87,7 @@ protected: virtual ~nsExpandedPrincipal(); bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) override; + bool MayLoadInternal(nsIURI* aURI) override; private: nsTArray< nsCOMPtr > mPrincipals; diff --git a/caps/nsSystemPrincipal.cpp b/caps/nsSystemPrincipal.cpp index a6857117b4..e785b7ae30 100644 --- a/caps/nsSystemPrincipal.cpp +++ b/caps/nsSystemPrincipal.cpp @@ -41,12 +41,6 @@ nsSystemPrincipal::GetScriptLocation(nsACString &aStr) // Methods implementing nsIPrincipal // /////////////////////////////////////// -NS_IMETHODIMP -nsSystemPrincipal::CheckMayLoad(nsIURI* uri, bool aReport, bool aAllowIfInheritsPrincipal) -{ - return NS_OK; -} - NS_IMETHODIMP nsSystemPrincipal::GetHashValue(uint32_t *result) { diff --git a/caps/nsSystemPrincipal.h b/caps/nsSystemPrincipal.h index ae614af840..06c456226c 100644 --- a/caps/nsSystemPrincipal.h +++ b/caps/nsSystemPrincipal.h @@ -29,7 +29,6 @@ public: NS_IMETHOD GetURI(nsIURI** aURI) override; NS_IMETHOD GetDomain(nsIURI** aDomain) override; NS_IMETHOD SetDomain(nsIURI* aDomain) override; - NS_IMETHOD CheckMayLoad(nsIURI* uri, bool report, bool allowIfInheritsPrincipal) override; NS_IMETHOD GetCsp(nsIContentSecurityPolicy** aCsp) override; NS_IMETHOD SetCsp(nsIContentSecurityPolicy* aCsp) override; NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override; @@ -46,6 +45,11 @@ protected: { return true; } + + bool MayLoadInternal(nsIURI* aURI) override + { + return true; + } }; #endif // nsSystemPrincipal_h__ diff --git a/dom/base/StructuredCloneHolder.cpp b/dom/base/StructuredCloneHolder.cpp index 97632745f8..bc375c9853 100644 --- a/dom/base/StructuredCloneHolder.cpp +++ b/dom/base/StructuredCloneHolder.cpp @@ -416,49 +416,19 @@ StructuredCloneHolder::ReadFullySerializableObjects(JSContext* aCx, if (aTag == SCTAG_DOM_NULL_PRINCIPAL || aTag == SCTAG_DOM_SYSTEM_PRINCIPAL || aTag == SCTAG_DOM_CONTENT_PRINCIPAL) { - if (!NS_IsMainThread()) { - return nullptr; - } - - mozilla::ipc::PrincipalInfo info; - if (aTag == SCTAG_DOM_SYSTEM_PRINCIPAL) { - info = mozilla::ipc::SystemPrincipalInfo(); - } else if (aTag == SCTAG_DOM_NULL_PRINCIPAL) { - info = mozilla::ipc::NullPrincipalInfo(); - } else { - - uint32_t suffixLength, specLength; - if (!JS_ReadUint32Pair(aReader, &suffixLength, &specLength)) { - return nullptr; - } - - nsAutoCString suffix; - suffix.SetLength(suffixLength); - if (!JS_ReadBytes(aReader, suffix.BeginWriting(), suffixLength)) { - return nullptr; - } - - nsAutoCString spec; - spec.SetLength(specLength); - if (!JS_ReadBytes(aReader, spec.BeginWriting(), specLength)) { - return nullptr; - } - - OriginAttributes attrs; - attrs.PopulateFromSuffix(suffix); - info = mozilla::ipc::ContentPrincipalInfo(attrs, spec); - } - - nsresult rv; - nsCOMPtr principal = PrincipalInfoToPrincipal(info, &rv); - if (NS_WARN_IF(NS_FAILED(rv))) { - xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + JSPrincipals* prin; + if (!nsJSPrincipals::ReadKnownPrincipalType(aCx, aReader, aTag, &prin)) { return nullptr; } + // nsJSPrincipals::ReadKnownPrincipalType addrefs for us, but because of the + // casting between JSPrincipals* and nsIPrincipal* we can't use + // getter_AddRefs above and have to already_AddRefed here. + nsCOMPtr principal = already_AddRefed(nsJSPrincipals::get(prin)); JS::RootedValue result(aCx); - rv = nsContentUtils::WrapNative(aCx, principal, &NS_GET_IID(nsIPrincipal), - &result); + nsresult rv = nsContentUtils::WrapNative(aCx, principal, + &NS_GET_IID(nsIPrincipal), + &result); if (NS_FAILED(rv)) { xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); return nullptr; @@ -558,27 +528,8 @@ StructuredCloneHolder::WriteFullySerializableObjects(JSContext* aCx, nsCOMPtr base = xpc::UnwrapReflectorToISupports(aObj); nsCOMPtr principal = do_QueryInterface(base); if (principal) { - mozilla::ipc::PrincipalInfo info; - if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(principal, &info)))) { - xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); - return false; - } - - if (info.type() == mozilla::ipc::PrincipalInfo::TNullPrincipalInfo) { - return JS_WriteUint32Pair(aWriter, SCTAG_DOM_NULL_PRINCIPAL, 0); - } - if (info.type() == mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo) { - return JS_WriteUint32Pair(aWriter, SCTAG_DOM_SYSTEM_PRINCIPAL, 0); - } - - MOZ_ASSERT(info.type() == mozilla::ipc::PrincipalInfo::TContentPrincipalInfo); - const mozilla::ipc::ContentPrincipalInfo& cInfo = info; - nsAutoCString suffix; - cInfo.attrs().CreateSuffix(suffix); - return JS_WriteUint32Pair(aWriter, SCTAG_DOM_CONTENT_PRINCIPAL, 0) && - JS_WriteUint32Pair(aWriter, suffix.Length(), cInfo.spec().Length()) && - JS_WriteBytes(aWriter, suffix.get(), suffix.Length()) && - JS_WriteBytes(aWriter, cInfo.spec().get(), cInfo.spec().Length()); + auto nsjsprincipals = nsJSPrincipals::get(principal); + return nsjsprincipals->write(aCx, aWriter); } } diff --git a/dom/base/nsDOMDataChannel.cpp b/dom/base/nsDOMDataChannel.cpp index 5bb85bbb00..d86784bc92 100644 --- a/dom/base/nsDOMDataChannel.cpp +++ b/dom/base/nsDOMDataChannel.cpp @@ -213,6 +213,12 @@ nsDOMDataChannel::BufferedAmount() const return mDataChannel->GetBufferedAmount(); } +uint32_t +nsDOMDataChannel::BufferedAmountLowThreshold() const +{ + return mDataChannel->GetBufferedAmountLowThreshold(); +} + NS_IMETHODIMP nsDOMDataChannel::GetBufferedAmount(uint32_t* aBufferedAmount) { @@ -220,6 +226,12 @@ nsDOMDataChannel::GetBufferedAmount(uint32_t* aBufferedAmount) return NS_OK; } +void +nsDOMDataChannel::SetBufferedAmountLowThreshold(uint32_t aThreshold) +{ + mDataChannel->SetBufferedAmountLowThreshold(aThreshold); +} + NS_IMETHODIMP nsDOMDataChannel::GetBinaryType(nsAString & aBinaryType) { switch (mBinaryType) { @@ -468,6 +480,14 @@ nsDOMDataChannel::OnChannelClosed(nsISupports* aContext) return OnSimpleEvent(aContext, NS_LITERAL_STRING("close")); } +nsresult +nsDOMDataChannel::OnBufferLow(nsISupports* aContext) +{ + LOG(("%p(%p): %s - Dispatching\n",this,(void*)mDataChannel,__FUNCTION__)); + + return OnSimpleEvent(aContext, NS_LITERAL_STRING("bufferedamountlow")); +} + void nsDOMDataChannel::AppReady() { diff --git a/dom/base/nsDOMDataChannel.h b/dom/base/nsDOMDataChannel.h index ebe2054db5..7369a15e96 100644 --- a/dom/base/nsDOMDataChannel.h +++ b/dom/base/nsDOMDataChannel.h @@ -54,11 +54,14 @@ public: bool Reliable() const; mozilla::dom::RTCDataChannelState ReadyState() const; uint32_t BufferedAmount() const; + uint32_t BufferedAmountLowThreshold() const; + void SetBufferedAmountLowThreshold(uint32_t aThreshold); IMPL_EVENT_HANDLER(open) IMPL_EVENT_HANDLER(error) IMPL_EVENT_HANDLER(close) // Uses XPIDL Close. IMPL_EVENT_HANDLER(message) + IMPL_EVENT_HANDLER(bufferedamountlow) mozilla::dom::RTCDataChannelType BinaryType() const { return static_cast( @@ -97,6 +100,9 @@ public: virtual nsresult OnChannelClosed(nsISupports* aContext) override; + virtual nsresult + OnBufferLow(nsISupports* aContext) override; + virtual void AppReady(); diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h index c6f523493c..9cae16e188 100644 --- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -696,6 +696,7 @@ GK_ATOM(onblocked, "onblocked") GK_ATOM(onblur, "onblur") GK_ATOM(onbroadcast, "onbroadcast") GK_ATOM(onbusy, "onbusy") +GK_ATOM(onbufferedamountlow, "onbufferedamountlow") GK_ATOM(oncached, "oncached") GK_ATOM(oncallschanged, "oncallschanged") GK_ATOM(oncancel, "oncancel") diff --git a/dom/base/nsObjectLoadingContent.cpp b/dom/base/nsObjectLoadingContent.cpp index d3f93e13b0..e929777578 100644 --- a/dom/base/nsObjectLoadingContent.cpp +++ b/dom/base/nsObjectLoadingContent.cpp @@ -2807,7 +2807,7 @@ nsObjectLoadingContent::ScriptRequestPluginInstance(JSContext* aCx, aCx == nsContentUtils::GetCurrentJSContext()); bool callerIsContentJS = (!nsContentUtils::IsCallerChrome() && !nsContentUtils::IsCallerContentXBL() && - js::IsContextRunningJS(aCx)); + JS_IsRunning(aCx)); nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); diff --git a/dom/media/omx/RtspOmxReader.cpp b/dom/media/omx/RtspOmxReader.cpp index c5fc174e7f..6915c1bcd1 100644 --- a/dom/media/omx/RtspOmxReader.cpp +++ b/dom/media/omx/RtspOmxReader.cpp @@ -48,6 +48,7 @@ RtspOmxReader::Seek(int64_t aTime, int64_t aEndTime) // seek operation. The function will clear the |mVideoQueue| and |mAudioQueue| // that store the decoded data and also call the |DecodeToTarget| to pass // the seek time to OMX a/v decoders. + mEnsureActiveFromSeek = true; return MediaOmxReader::Seek(aTime, aEndTime); } @@ -71,9 +72,13 @@ void RtspOmxReader::EnsureActive() { if (mRtspResource) { nsIStreamingProtocolController* controller = mRtspResource->GetMediaStreamController(); - if (controller) { + // We do not have to call Play if the EnsureActive request is from Seek + // operation because RTSP connection must already be established before + // performing Seek. + if (controller && !mEnsureActiveFromSeek) { controller->Play(); } + mEnsureActiveFromSeek = false; mRtspResource->SetSuspend(false); } diff --git a/dom/media/omx/RtspOmxReader.h b/dom/media/omx/RtspOmxReader.h index 97e6bb77af..f3883a5aee 100644 --- a/dom/media/omx/RtspOmxReader.h +++ b/dom/media/omx/RtspOmxReader.h @@ -32,7 +32,9 @@ protected: public: RtspOmxReader(AbstractMediaDecoder* aDecoder) - : MediaOmxReader(aDecoder) { + : MediaOmxReader(aDecoder) + , mEnsureActiveFromSeek(false) + { MOZ_COUNT_CTOR(RtspOmxReader); NS_ASSERTION(mDecoder, "RtspOmxReader mDecoder is null."); NS_ASSERTION(mDecoder->GetResource(), @@ -75,6 +77,8 @@ private: // holds the MediaDecoderStateMachine and RtspMediaResource. // And MediaDecoderStateMachine holds this RtspOmxReader. RtspMediaResource* mRtspResource; + + bool mEnsureActiveFromSeek; }; } // namespace mozilla diff --git a/dom/push/test/xpcshell/test_notification_http2.js b/dom/push/test/xpcshell/test_notification_http2.js index 59b5d35bfe..681a66181b 100644 --- a/dom/push/test/xpcshell/test_notification_http2.js +++ b/dom/push/test/xpcshell/test_notification_http2.js @@ -14,7 +14,7 @@ var serverPort = -1; function run_test() { var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); - serverPort = env.get("MOZHTTP2-PORT"); + serverPort = env.get("MOZHTTP2_PORT"); do_check_neq(serverPort, null); dump("using port " + serverPort + "\n"); diff --git a/dom/push/test/xpcshell/test_register_error_http2.js b/dom/push/test/xpcshell/test_register_error_http2.js index c3f558625f..0b2296e617 100644 --- a/dom/push/test/xpcshell/test_register_error_http2.js +++ b/dom/push/test/xpcshell/test_register_error_http2.js @@ -15,7 +15,7 @@ var serverPort = -1; function run_test() { var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); - serverPort = env.get("MOZHTTP2-PORT"); + serverPort = env.get("MOZHTTP2_PORT"); do_check_neq(serverPort, null); do_get_profile(); diff --git a/dom/push/test/xpcshell/test_register_success_http2.js b/dom/push/test/xpcshell/test_register_success_http2.js index 84c12e914e..a993e64e6c 100644 --- a/dom/push/test/xpcshell/test_register_success_http2.js +++ b/dom/push/test/xpcshell/test_register_success_http2.js @@ -14,7 +14,7 @@ var serverPort = -1; function run_test() { var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); - serverPort = env.get("MOZHTTP2-PORT"); + serverPort = env.get("MOZHTTP2_PORT"); do_check_neq(serverPort, null); do_get_profile(); diff --git a/dom/push/test/xpcshell/test_registration_success_http2.js b/dom/push/test/xpcshell/test_registration_success_http2.js index d50d1541d4..f59bae9000 100644 --- a/dom/push/test/xpcshell/test_registration_success_http2.js +++ b/dom/push/test/xpcshell/test_registration_success_http2.js @@ -13,7 +13,7 @@ var serverPort = -1; function run_test() { var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); - serverPort = env.get("MOZHTTP2-PORT"); + serverPort = env.get("MOZHTTP2_PORT"); do_check_neq(serverPort, null); do_get_profile(); diff --git a/dom/push/test/xpcshell/test_unregister_success_http2.js b/dom/push/test/xpcshell/test_unregister_success_http2.js index 0242aa1bef..0ad221e57a 100644 --- a/dom/push/test/xpcshell/test_unregister_success_http2.js +++ b/dom/push/test/xpcshell/test_unregister_success_http2.js @@ -14,7 +14,7 @@ var serverPort = -1; function run_test() { var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); - serverPort = env.get("MOZHTTP2-PORT"); + serverPort = env.get("MOZHTTP2_PORT"); do_check_neq(serverPort, null); do_get_profile(); diff --git a/dom/webidl/DataChannel.webidl b/dom/webidl/DataChannel.webidl index 5599403baa..db99111d04 100644 --- a/dom/webidl/DataChannel.webidl +++ b/dom/webidl/DataChannel.webidl @@ -21,11 +21,13 @@ interface DataChannel : EventTarget readonly attribute boolean reliable; readonly attribute RTCDataChannelState readyState; readonly attribute unsigned long bufferedAmount; + attribute unsigned long bufferedAmountLowThreshold; attribute EventHandler onopen; attribute EventHandler onerror; attribute EventHandler onclose; void close(); attribute EventHandler onmessage; + attribute EventHandler onbufferedamountlow; attribute RTCDataChannelType binaryType; [Throws] void send(DOMString data); diff --git a/dom/webidl/RTCConfiguration.webidl b/dom/webidl/RTCConfiguration.webidl index 673967f7e3..71aa436004 100644 --- a/dom/webidl/RTCConfiguration.webidl +++ b/dom/webidl/RTCConfiguration.webidl @@ -14,6 +14,12 @@ dictionary RTCIceServer { DOMString? username = null; }; +enum RTCIceTransportPolicy { + "none", + "relay", + "all" +}; + enum RTCBundlePolicy { "balanced", "max-compat", @@ -22,6 +28,7 @@ enum RTCBundlePolicy { dictionary RTCConfiguration { sequence iceServers; + RTCIceTransportPolicy iceTransportPolicy = "all"; RTCBundlePolicy bundlePolicy = "balanced"; DOMString? peerIdentity = null; sequence certificates; diff --git a/dom/workers/Principal.cpp b/dom/workers/Principal.cpp index 1abb8c0fc1..8fcd9ed105 100644 --- a/dom/workers/Principal.cpp +++ b/dom/workers/Principal.cpp @@ -11,10 +11,18 @@ BEGIN_WORKERS_NAMESPACE +struct WorkerPrincipal final : public JSPrincipals +{ + bool write(JSContext* aCx, JSStructuredCloneWriter* aWriter) override { + MOZ_CRASH("WorkerPrincipal::write not implemented"); + return false; + } +}; + JSPrincipals* GetWorkerPrincipal() { - static JSPrincipals sPrincipal; + static WorkerPrincipal sPrincipal; /* * To make sure the the principals refcount is initialized to one, atomically diff --git a/intl/icu/source/common/uinvchar.c b/intl/icu/source/common/uinvchar.c index 79dd05143f..69955c743a 100644 --- a/intl/icu/source/common/uinvchar.c +++ b/intl/icu/source/common/uinvchar.c @@ -431,7 +431,7 @@ uprv_copyEbcdic(const UDataSwapper *ds, while(count>0) { c=*s++; if(c!=0 && ((c=asciiFromEbcdic[c])==0 || !UCHAR_IS_INVARIANT(c))) { - udata_printError(ds, "uprv_copyEbcdic() string[%] contains a variant character in position %d\n", + udata_printError(ds, "uprv_copyEbcdic() string[%d] contains a variant character in position %d\n", length, length-count); *pErrorCode=U_INVALID_CHAR_FOUND; return 0; diff --git a/js/ductwork/debugger/moz.build b/js/ductwork/debugger/moz.build index 4e96b9b8ee..0a40c7adbe 100644 --- a/js/ductwork/debugger/moz.build +++ b/js/ductwork/debugger/moz.build @@ -21,3 +21,6 @@ EXTRA_JS_MODULES += [ ] FINAL_LIBRARY = 'xul' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wshadow'] diff --git a/js/public/HashTable.h b/js/public/HashTable.h index 801af57972..7c42758da8 100644 --- a/js/public/HashTable.h +++ b/js/public/HashTable.h @@ -1131,11 +1131,24 @@ class HashTable : private AllocPolicy return keyHash & ~sCollisionBit; } - static Entry* createTable(AllocPolicy& alloc, uint32_t capacity) + enum FailureBehavior { DontReportFailure = false, ReportFailure = true }; + + static Entry* createTable(AllocPolicy& alloc, uint32_t capacity, + FailureBehavior reportFailure = ReportFailure) { static_assert(sFreeKey == 0, "newly-calloc'd tables have to be considered empty"); - return alloc.template pod_calloc(capacity); + if (reportFailure) + return alloc.template pod_calloc(capacity); + + return alloc.template maybe_pod_calloc(capacity); + } + + static Entry* maybeCreateTable(AllocPolicy& alloc, uint32_t capacity) + { + static_assert(sFreeKey == 0, + "newly-calloc'd tables have to be considered empty"); + return alloc.template maybe_pod_calloc(capacity); } static void destroyTable(AllocPolicy& alloc, Entry* oldTable, uint32_t capacity) @@ -1374,7 +1387,7 @@ class HashTable : private AllocPolicy enum RebuildStatus { NotOverloaded, Rehashed, RehashFailed }; - RebuildStatus changeTableSize(int deltaLog2) + RebuildStatus changeTableSize(int deltaLog2, FailureBehavior reportFailure = ReportFailure) { // Look, but don't touch, until we succeed in getting new entry store. Entry* oldTable = table; @@ -1382,11 +1395,12 @@ class HashTable : private AllocPolicy uint32_t newLog2 = sHashBits - hashShift + deltaLog2; uint32_t newCapacity = JS_BIT(newLog2); if (MOZ_UNLIKELY(newCapacity > sMaxCapacity)) { - this->reportAllocOverflow(); + if (reportFailure) + this->reportAllocOverflow(); return RehashFailed; } - Entry* newTable = createTable(*this, newCapacity); + Entry* newTable = createTable(*this, newCapacity, reportFailure); if (!newTable) return RehashFailed; @@ -1412,14 +1426,19 @@ class HashTable : private AllocPolicy return Rehashed; } - RebuildStatus checkOverloaded() + bool shouldCompressTable() + { + // Compress if a quarter or more of all entries are removed. + return removedCount >= (capacity() >> 2); + } + + RebuildStatus checkOverloaded(FailureBehavior reportFailure = ReportFailure) { if (!overloaded()) return NotOverloaded; - // Compress if a quarter or more of all entries are removed. int deltaLog2; - if (removedCount >= (capacity() >> 2)) { + if (shouldCompressTable()) { METER(stats.compresses++); deltaLog2 = 0; } else { @@ -1427,14 +1446,14 @@ class HashTable : private AllocPolicy deltaLog2 = 1; } - return changeTableSize(deltaLog2); + return changeTableSize(deltaLog2, reportFailure); } // Infallibly rehash the table if we are overloaded with removals. void checkOverRemoved() { if (overloaded()) { - if (checkOverloaded() == RehashFailed) + if (checkOverloaded(DontReportFailure) == RehashFailed) rehashTableInPlace(); } } @@ -1461,7 +1480,7 @@ class HashTable : private AllocPolicy { if (underloaded()) { METER(stats.shrinks++); - (void) changeTableSize(-1); + (void) changeTableSize(-1, DontReportFailure); } } @@ -1477,9 +1496,8 @@ class HashTable : private AllocPolicy resizeLog2--; } - if (resizeLog2 != 0) { - changeTableSize(resizeLog2); - } + if (resizeLog2 != 0) + (void) changeTableSize(resizeLog2, DontReportFailure); } // This is identical to changeTableSize(currentSize), but without requiring diff --git a/js/public/Principals.h b/js/public/Principals.h index 49db1f9878..9dcb3e9b1a 100644 --- a/js/public/Principals.h +++ b/js/public/Principals.h @@ -15,6 +15,8 @@ #include "jspubtd.h" +#include "js/StructuredClone.h" + namespace js { struct PerformanceGroup; } // namespace js @@ -36,6 +38,12 @@ struct JSPrincipals { # endif } + /* + * Write the principals with the given |writer|. Return false on failure, + * true on success. + */ + virtual bool write(JSContext* cx, JSStructuredCloneWriter* writer) = 0; + /* * This is not defined by the JS engine but should be provided by the * embedding. @@ -99,4 +107,26 @@ typedef void extern JS_PUBLIC_API(void) JS_InitDestroyPrincipalsCallback(JSRuntime* rt, JSDestroyPrincipalsOp destroyPrincipals); +/* + * Read a JSPrincipals instance from the given |reader| and initialize the out + * paratemer |outPrincipals| to the JSPrincipals instance read. + * + * Return false on failure, true on success. The |outPrincipals| parameter + * should not be modified if false is returned. + * + * The caller is not responsible for calling JS_HoldPrincipals on the resulting + * JSPrincipals instance, the JSReadPrincipalsOp must increment the refcount of + * the resulting JSPrincipals on behalf of the caller. + */ +using JSReadPrincipalsOp = bool (*)(JSContext* cx, JSStructuredCloneReader* reader, + JSPrincipals** outPrincipals); + +/* + * Initialize the callback that is called to read JSPrincipals instances from a + * buffer. The initialization can be done only once per JS runtime. + */ +extern JS_PUBLIC_API(void) +JS_InitReadPrincipalsCallback(JSRuntime* rt, JSReadPrincipalsOp read); + + #endif /* js_Principals_h */ diff --git a/js/public/ProfilingStack.h b/js/public/ProfilingStack.h index 4fd8fe0949..2f1a148c20 100644 --- a/js/public/ProfilingStack.h +++ b/js/public/ProfilingStack.h @@ -52,7 +52,8 @@ class ProfileEntry enum Flags { // Indicate whether a profile entry represents a CPP frame. If not set, // a JS frame is assumed by default. You're not allowed to publicly - // change the frame type. Instead, call `setJsFrame` or `setCppFrame`. + // change the frame type. Instead, initialize the ProfileEntry as either + // a JS or CPP frame with `initJsFrame` or `initCppFrame` respectively. IS_CPP_ENTRY = 0x01, // Indicate that copying the frame label is not necessary when taking a @@ -106,12 +107,12 @@ class ProfileEntry void setLabel(const char* aString) volatile { string = aString; } const char* label() const volatile { return string; } - void setJsFrame(JSScript* aScript, jsbytecode* aPc) volatile { + void initJsFrame(JSScript* aScript, jsbytecode* aPc) volatile { flags_ = 0; spOrScript = aScript; setPC(aPc); } - void setCppFrame(void* aSp, uint32_t aLine) volatile { + void initCppFrame(void* aSp, uint32_t aLine) volatile { flags_ = IS_CPP_ENTRY; spOrScript = aSp; lineOrPc = static_cast(aLine); @@ -137,6 +138,8 @@ class ProfileEntry return flags_ & CATEGORY_MASK; } void setCategory(Category c) volatile { + MOZ_ASSERT(c >= Category::FIRST); + MOZ_ASSERT(c <= Category::LAST); flags_ &= ~CATEGORY_MASK; setFlag(static_cast(c)); } diff --git a/js/public/RootingAPI.h b/js/public/RootingAPI.h index dc44e8498f..47d43eeeee 100644 --- a/js/public/RootingAPI.h +++ b/js/public/RootingAPI.h @@ -987,7 +987,8 @@ class PersistentRooted : public js::PersistentRootedBase, kind == js::THING_ROOT_SCRIPT || kind == js::THING_ROOT_STRING || kind == js::THING_ROOT_ID || - kind == js::THING_ROOT_VALUE); + kind == js::THING_ROOT_VALUE || + kind == js::THING_ROOT_TRACEABLE); } public: @@ -1067,7 +1068,13 @@ class PersistentRooted : public js::PersistentRootedBase, ptr = value; } - T ptr; + // See the comment above Rooted::ptr. + using MaybeWrapped = typename mozilla::Conditional< + mozilla::IsBaseOf::value, + js::DispatchWrapper, + T>::Type; + + MaybeWrapped ptr; }; class JS_PUBLIC_API(ObjectPtr) @@ -1140,6 +1147,30 @@ CallTraceCallbackOnNonHeap(T* v, const TraceCallbacks& aCallbacks, const char* a } /* namespace gc */ } /* namespace js */ +// mozilla::Swap uses a stack temporary, which prevents classes like Heap +// from being declared MOZ_HEAP_CLASS. +namespace mozilla { + +template +inline void +Swap(JS::Heap& aX, JS::Heap& aY) +{ + T tmp = aX; + aX = aY; + aY = tmp; +} + +template +inline void +Swap(JS::TenuredHeap& aX, JS::TenuredHeap& aY) +{ + T tmp = aX; + aX = aY; + aY = tmp; +} + +} /* namespace mozilla */ + #undef DELETE_ASSIGNMENT_OPS #endif /* js_RootingAPI_h */ diff --git a/js/public/StructuredClone.h b/js/public/StructuredClone.h index e95b4ef834..252114c775 100644 --- a/js/public/StructuredClone.h +++ b/js/public/StructuredClone.h @@ -236,9 +236,6 @@ class JS_PUBLIC_API(JSAutoStructuredCloneBuffer) { #define JS_SCERR_TRANSFERABLE 1 #define JS_SCERR_DUP_TRANSFERABLE 2 -JS_PUBLIC_API(void) -JS_SetStructuredCloneCallbacks(JSRuntime* rt, const JSStructuredCloneCallbacks* callbacks); - JS_PUBLIC_API(bool) JS_ReadUint32Pair(JSStructuredCloneReader* r, uint32_t* p1, uint32_t* p2); diff --git a/js/public/TracingAPI.h b/js/public/TracingAPI.h index 57e3ed79e0..f5f0fed2ee 100644 --- a/js/public/TracingAPI.h +++ b/js/public/TracingAPI.h @@ -339,9 +339,12 @@ extern JS_PUBLIC_API(void) JS_CallTenuredObjectTracer(JSTracer* trc, JS::TenuredHeap* objp, const char* name); extern JS_PUBLIC_API(void) -JS_TraceChildren(JSTracer* trc, void* thing, JS::TraceKind kind); +JS_TraceRuntime(JSTracer* trc); namespace JS { +extern JS_PUBLIC_API(void) +TraceChildren(JSTracer* trc, GCCellPtr thing); + typedef js::HashSet, js::SystemAllocPolicy> ZoneSet; } // namespace JS diff --git a/js/public/UbiNode.h b/js/public/UbiNode.h index 8bbf4b008e..d584e98aea 100644 --- a/js/public/UbiNode.h +++ b/js/public/UbiNode.h @@ -25,6 +25,7 @@ #include "js/RootingAPI.h" #include "js/TracingAPI.h" #include "js/TypeDecls.h" +#include "js/Value.h" #include "js/Vector.h" // JS::ubi::Node @@ -97,7 +98,6 @@ // represented by a "rope", a structure that points to the two original // strings. // -// // We intend to use ubi::Node to write tools that report memory usage, so it's // important that ubi::Node accurately portray how much memory nodes consume. // Thus, for example, when data that apparently belongs to multiple nodes is @@ -142,6 +142,25 @@ // If this restriction prevents us from implementing interesting tools, we may // teach the GC how to root ubi::Nodes, fix up hash tables that use them as // keys, etc. +// +// +// Hostile Graph Structure +// +// Analyses consuming ubi::Node graphs must be robust when presented with graphs +// that are deliberately constructed to exploit their weaknesses. When operating +// on live graphs, web content has control over the object graph, and less +// direct control over shape and string structure, and analyses should be +// prepared to handle extreme cases gracefully. For example, if an analysis were +// to use the C++ stack in a depth-first traversal, carefully constructed +// content could cause the analysis to overflow the stack. +// +// When ubi::Nodes refer to nodes deserialized from a heap snapshot, analyses +// must be even more careful: since snapshots often come from potentially +// compromised e10s content processes, even properties normally guaranteed by +// the platform (the proper linking of DOM nodes, for example) might be +// corrupted. While it is the deserializer's responsibility to check the basic +// structure of the snapshot file, the analyses should be prepared for ubi::Node +// graphs constructed from snapshots to be even more bizarre. class JSAtom; @@ -168,6 +187,7 @@ class DefaultDelete : public JS::DeletePolicy; +class JS_FRIEND_API(AtomOrTwoByteChars) : public Variant { + using Base = Variant; + + public: + template + MOZ_IMPLICIT AtomOrTwoByteChars(T&& rhs) : Base(Forward(rhs)) { } + + template + AtomOrTwoByteChars& operator=(T&& rhs) { + MOZ_ASSERT(this != &rhs, "self-move disallowed"); + this->~AtomOrTwoByteChars(); + new (this) AtomOrTwoByteChars(Forward(rhs)); + return *this; + } + + // Return the length of the given AtomOrTwoByteChars string. + size_t length(); + + // Copy the given AtomOrTwoByteChars string into the destination buffer, + // inflating if necessary. Does NOT null terminate. Returns the number of + // characters written to destination. + size_t copyToBuffer(RangedPtr destination, size_t length); +}; // The base class implemented by each ConcreteStackFrame type. Subclasses // must not add data members to this class. @@ -551,7 +593,7 @@ class JS_FRIEND_API(Base) { // // If wantNames is true, compute names for edges. Doing so can be expensive // in time and memory. - virtual UniquePtr edges(JSContext* cx, bool wantNames) const = 0; + virtual UniquePtr edges(JSRuntime* rt, bool wantNames) const = 0; // Return the Zone to which this node's referent belongs, or nullptr if the // referent is not of a type allocated in SpiderMonkey Zones. @@ -738,8 +780,8 @@ class JS_FRIEND_API(Node) { return base()->size(mallocSizeof); } - UniquePtr edges(JSContext* cx, bool wantNames = true) const { - return base()->edges(cx, wantNames); + UniquePtr edges(JSRuntime* rt, bool wantNames = true) const { + return base()->edges(rt, wantNames); } bool hasAllocationStack() const { return base()->hasAllocationStack(); } @@ -768,18 +810,35 @@ class JS_FRIEND_API(Node) { /*** Edge and EdgeRange ***************************************************************************/ -// Edge is the abstract base class representing an outgoing edge of a node. -// Edges are owned by EdgeRanges, and need not have assignment operators or copy -// constructors. -// -// Each Edge class should inherit from this base class, overriding as -// appropriate. -class Edge { - protected: - Edge() : name(nullptr), referent() { } - virtual ~Edge() { } +using EdgeName = UniquePtr; +// An outgoing edge to a referent node. +class Edge { public: + Edge() : name(nullptr), referent() { } + + // Construct an initialized Edge, taking ownership of |name|. + Edge(char16_t* name, const Node& referent) + : name(name) + , referent(referent) + { } + + // Move construction and assignment. + Edge(Edge&& rhs) + : name(mozilla::Move(rhs.name)) + , referent(rhs.referent) + { } + + Edge& operator=(Edge&& rhs) { + MOZ_ASSERT(&rhs != this); + this->~Edge(); + new (this) Edge(mozilla::Move(rhs)); + return *this; + } + + Edge(const Edge&) = delete; + Edge& operator=(const Edge&) = delete; + // This edge's name. This may be nullptr, if Node::edges was called with // false as the wantNames parameter. // @@ -789,24 +848,19 @@ class Edge { // (In real life we'll want a better representation for names, to avoid // creating tons of strings when the names follow a pattern; and we'll need // to think about lifetimes carefully to ensure traversal stays cheap.) - const char16_t* name; + EdgeName name; // This edge's referent. Node referent; - - private: - Edge(const Edge&) = delete; - Edge& operator=(const Edge&) = delete; }; - // EdgeRange is an abstract base class for iterating over a node's outgoing // edges. (This is modeled after js::HashTable::Range.) // // Concrete instances of this class need not be as lightweight as Node itself, // since they're usually only instantiated while iterating over a particular // object's edges. For example, a dumb implementation for JS Cells might use -// JS_TraceChildren to to get the outgoing edges, and then store them in an +// JS::TraceChildren to to get the outgoing edges, and then store them in an // array internal to the EdgeRange. class EdgeRange { protected: @@ -824,7 +878,8 @@ class EdgeRange { // The front edge of this range. This is owned by the EdgeRange, and is // only guaranteed to live until the next call to popFront, or until // the EdgeRange is destructed. - const Edge& front() { return *front_; } + const Edge& front() const { return *front_; } + Edge& front() { return *front_; } // Remove the front edge from this range. This should only be called if // !empty(). @@ -836,55 +891,22 @@ class EdgeRange { }; -// A dumb Edge concrete class. All but the most essential members have the -// default behavior. -class SimpleEdge : public Edge { - SimpleEdge(SimpleEdge&) = delete; - SimpleEdge& operator=(const SimpleEdge&) = delete; - - public: - SimpleEdge() : Edge() { } - - // Construct an initialized SimpleEdge, taking ownership of |name|. - SimpleEdge(char16_t* name, const Node& referent) { - this->name = name; - this->referent = referent; - } - ~SimpleEdge() { - js_free(const_cast(name)); - } - - // Move construction and assignment. - SimpleEdge(SimpleEdge&& rhs) { - name = rhs.name; - referent = rhs.referent; - - rhs.name = nullptr; - } - SimpleEdge& operator=(SimpleEdge&& rhs) { - MOZ_ASSERT(&rhs != this); - this->~SimpleEdge(); - new(this) SimpleEdge(mozilla::Move(rhs)); - return *this; - } -}; - -typedef mozilla::Vector SimpleEdgeVector; +typedef mozilla::Vector EdgeVector; // An EdgeRange concrete class that holds a pre-existing vector of -// SimpleEdges. A PreComputedEdgeRange does not take ownership of its -// SimpleEdgeVector; it is up to the PreComputedEdgeRange's consumer to manage +// Edges. A PreComputedEdgeRange does not take ownership of its +// EdgeVector; it is up to the PreComputedEdgeRange's consumer to manage // that lifetime. class PreComputedEdgeRange : public EdgeRange { - SimpleEdgeVector& edges; - size_t i; + EdgeVector& edges; + size_t i; void settle() { front_ = i < edges.length() ? &edges[i] : nullptr; } public: - explicit PreComputedEdgeRange(JSContext* cx, SimpleEdgeVector& edges) + explicit PreComputedEdgeRange(EdgeVector& edges) : edges(edges), i(0) { @@ -917,7 +939,7 @@ class PreComputedEdgeRange : public EdgeRange { // // { // mozilla::Maybe maybeNoGC; -// JS::ubi::RootList rootList(cx, maybeNoGC); +// JS::ubi::RootList rootList(rt, maybeNoGC); // if (!rootList.init()) // return false; // @@ -930,13 +952,13 @@ class PreComputedEdgeRange : public EdgeRange { // } class MOZ_STACK_CLASS JS_FRIEND_API(RootList) { Maybe& noGC; - JSContext* cx; public: - SimpleEdgeVector edges; - bool wantNames; + JSRuntime* rt; + EdgeVector edges; + bool wantNames; - RootList(JSContext* cx, Maybe& noGC, bool wantNames = false); + RootList(JSRuntime* rt, Maybe& noGC, bool wantNames = false); // Find all GC roots. bool init(); @@ -960,7 +982,7 @@ class MOZ_STACK_CLASS JS_FRIEND_API(RootList) { template<> struct JS_FRIEND_API(Concrete) : public Base { - UniquePtr edges(JSContext* cx, bool wantNames) const override; + UniquePtr edges(JSRuntime* rt, bool wantNames) const override; const char16_t* typeName() const override { return concreteTypeName; } protected: @@ -973,11 +995,11 @@ struct JS_FRIEND_API(Concrete) : public Base { }; // A reusable ubi::Concrete specialization base class for types supported by -// JS_TraceChildren. +// JS::TraceChildren. template class JS_FRIEND_API(TracerConcrete) : public Base { const char16_t* typeName() const override { return concreteTypeName; } - UniquePtr edges(JSContext*, bool wantNames) const override; + UniquePtr edges(JSRuntime* rt, bool wantNames) const override; JS::Zone* zone() const override; protected: @@ -989,7 +1011,7 @@ class JS_FRIEND_API(TracerConcrete) : public Base { static void construct(void* storage, Referent* ptr) { new (storage) TracerConcrete(ptr); } }; -// For JS_TraceChildren-based types that have a 'compartment' method. +// For JS::TraceChildren-based types that have a 'compartment' method. template class JS_FRIEND_API(TracerConcreteWithCompartment) : public TracerConcrete { typedef TracerConcrete TracerBase; @@ -1006,10 +1028,22 @@ class JS_FRIEND_API(TracerConcreteWithCompartment) : public TracerConcrete struct Concrete : TracerConcrete { }; +template<> +struct Concrete : TracerConcrete { + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; + + protected: + explicit Concrete(JS::Symbol* ptr) : TracerConcrete(ptr) { } + + public: + static void construct(void* storage, JS::Symbol* ptr) { + new (storage) Concrete(ptr); + } +}; template<> struct Concrete : TracerConcreteWithCompartment { CoarseType coarseType() const final { return CoarseType::Script; } + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; protected: explicit Concrete(JSScript *ptr) : TracerConcreteWithCompartment(ptr) { } @@ -1058,7 +1092,7 @@ template<> class JS_FRIEND_API(Concrete) : public Base { const char16_t* typeName() const override; Size size(mozilla::MallocSizeOf mallocSizeOf) const override; - UniquePtr edges(JSContext* cx, bool wantNames) const override; + UniquePtr edges(JSRuntime* rt, bool wantNames) const override; JS::Zone* zone() const override; JSCompartment* compartment() const override; CoarseType coarseType() const final; diff --git a/js/public/UbiNodeTraverse.h b/js/public/UbiNodeBreadthFirst.h similarity index 92% rename from js/public/UbiNodeTraverse.h rename to js/public/UbiNodeBreadthFirst.h index 5d8650f047..c677ed422d 100644 --- a/js/public/UbiNodeTraverse.h +++ b/js/public/UbiNodeBreadthFirst.h @@ -4,8 +4,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef js_UbiNodeTraverse_h -#define js_UbiNodeTraverse_h +#ifndef js_UbiNodeBreadthFirst_h +#define js_UbiNodeBreadthFirst_h #include "js/UbiNode.h" #include "js/Utility.h" @@ -78,13 +78,13 @@ template struct BreadthFirst { // Construct a breadth-first traversal object that reports the nodes it - // reaches to |handler|. The traversal object reports OOM on |cx|, and - // asserts that no GC happens in |cx|'s runtime during its lifetime. + // reaches to |handler|. The traversal asserts that no GC happens in its + // runtime during its lifetime. // // We do nothing with noGC, other than require it to exist, with a lifetime // that encloses our own. - BreadthFirst(JSContext* cx, Handler& handler, const JS::AutoCheckCannotGC& noGC) - : wantNames(true), cx(cx), visited(cx), handler(handler), pending(cx), + BreadthFirst(JSRuntime* rt, Handler& handler, const JS::AutoCheckCannotGC& noGC) + : wantNames(true), rt(rt), visited(), handler(handler), pending(), traversalBegun(false), stopRequested(false), abandonRequested(false) { } @@ -126,7 +126,7 @@ struct BreadthFirst { pending.popFront(); // Get a range containing all origin's outgoing edges. - auto range = origin.edges(cx, wantNames); + auto range = origin.edges(rt, wantNames); if (!range) return false; @@ -181,13 +181,14 @@ struct BreadthFirst { // Other edges *to* that referent will still be traversed. void abandonReferent() { abandonRequested = true; } - // The context with which we were constructed. - JSContext* cx; + // The runtime with which we were constructed. + JSRuntime* rt; // A map associating each node N that we have reached with a // Handler::NodeData, for |handler|'s use. This is public, so that // |handler| can access it to see the traversal thus far. - typedef js::HashMap NodeMap; + using NodeMap = js::HashMap, + js::SystemAllocPolicy>; NodeMap visited; private: @@ -199,10 +200,10 @@ struct BreadthFirst { // current population. template class Queue { - js::Vector head, tail; + js::Vector head, tail; size_t frontIndex; public: - explicit Queue(JSContext* cx) : head(cx), tail(cx), frontIndex(0) { } + Queue() : head(), tail(), frontIndex(0) { } bool empty() { return frontIndex >= head.length(); } T& front() { MOZ_ASSERT(!empty()); @@ -240,4 +241,4 @@ struct BreadthFirst { } // namespace ubi } // namespace JS -#endif // js_UbiNodeTraverse.h +#endif // js_UbiNodeBreadthFirst_h diff --git a/js/public/UbiNodeCensus.h b/js/public/UbiNodeCensus.h index c75472ee2a..9c6b21a836 100644 --- a/js/public/UbiNodeCensus.h +++ b/js/public/UbiNodeCensus.h @@ -12,7 +12,7 @@ #include "jsapi.h" #include "js/UbiNode.h" -#include "js/UbiNodeTraverse.h" +#include "js/UbiNodeBreadthFirst.h" // A census is a ubi::Node traversal that assigns each node to one or more // buckets, and returns a report with the size of each bucket. diff --git a/js/public/UbiNodePostOrder.h b/js/public/UbiNodePostOrder.h new file mode 100644 index 0000000000..ed35e71ad5 --- /dev/null +++ b/js/public/UbiNodePostOrder.h @@ -0,0 +1,165 @@ +/* -*- 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 js_UbiNodePostOrder_h +#define js_UbiNodePostOrder_h + +#include "mozilla/DebugOnly.h" +#include "mozilla/Move.h" + +#include "jsalloc.h" + +#include "js/UbiNode.h" +#include "js/Utility.h" +#include "js/Vector.h" + +namespace JS { +namespace ubi { + +// A post-order depth-first traversal of `ubi::Node` graphs. +// +// NB: This traversal visits each node reachable from the start set exactly +// once, and does not visit edges at all. Therefore, this traversal would be a +// very poor choice for recording multiple paths to the same node, for example. +// If your analysis needs to consider edges, use `JS::ubi::BreadthFirst` +// instead. +// +// No GC may occur while an instance of `PostOrder` is live. +// +// The `Visitor` type provided to `PostOrder::traverse` must have the following +// member: +// +// bool operator()(Node& node) +// +// The visitor method. This method is called once for each `node` reachable +// from the start set in post-order. +// +// The visitor function should return true on success, or false if an error +// occurs. A false return value terminates the traversal immediately, and +// causes `PostOrder::traverse` to return false. +struct PostOrder { + private: + struct OriginAndEdges { + Node origin; + EdgeVector edges; + + OriginAndEdges(const Node& node, EdgeVector&& edges) + : origin(node) + , edges(mozilla::Move(edges)) + { } + + OriginAndEdges(const OriginAndEdges& rhs) = delete; + OriginAndEdges& operator=(const OriginAndEdges& rhs) = delete; + + OriginAndEdges(OriginAndEdges&& rhs) + : origin(rhs.origin) + , edges(mozilla::Move(rhs.edges)) + { + MOZ_ASSERT(&rhs != this, "self-move disallowed"); + } + + OriginAndEdges& operator=(OriginAndEdges&& rhs) { + this->~OriginAndEdges(); + new (this) OriginAndEdges(mozilla::Move(rhs)); + return *this; + } + }; + + using Stack = js::Vector; + using Set = js::HashSet, js::SystemAllocPolicy>; + + JSRuntime* rt; + Set seen; + Stack stack; + mozilla::DebugOnly traversed; + + private: + bool fillEdgesFromRange(EdgeVector& edges, UniquePtr& range) { + MOZ_ASSERT(range); + for ( ; !range->empty(); range->popFront()) { + if (!edges.append(mozilla::Move(range->front()))) + return false; + } + return true; + } + + bool pushForTraversing(const Node& node) { + EdgeVector edges; + auto range = node.edges(rt, /* wantNames */ false); + return range && + fillEdgesFromRange(edges, range) && + stack.append(OriginAndEdges(node, mozilla::Move(edges))); + } + + + public: + // Construct a post-order traversal object. + // + // The traversal asserts that no GC happens in its runtime during its + // lifetime via the `AutoCheckCannotGC&` parameter. We do nothing with it, + // other than require it to exist with a lifetime that encloses our own. + PostOrder(JSRuntime* rt, AutoCheckCannotGC&) + : rt(rt) + , seen() + , stack() + , traversed(false) + { } + + // Initialize this traversal object. Return false on OOM. + bool init() { return seen.init(); } + + // Add `node` as a starting point for the traversal. You may add + // as many starting points as you like. Returns false on OOM. + bool addStart(const Node& node) { + if (!seen.put(node)) + return false; + return pushForTraversing(node); + } + + // Traverse the graph in post-order, starting with the set of nodes passed + // to `addStart` and applying `visitor::operator()` for each node in + // the graph, as described above. + // + // This should be called only once per instance of this class. + // + // Return false on OOM or error return from `visitor::operator()`. + template + bool traverse(Visitor visitor) { + MOZ_ASSERT(!traversed, "Can only traverse() once!"); + traversed = true; + + while (!stack.empty()) { + auto& origin = stack.back().origin; + auto& edges = stack.back().edges; + + if (edges.empty()) { + if (!visitor(origin)) + return false; + stack.popBack(); + continue; + } + + Edge edge = mozilla::Move(edges.back()); + edges.popBack(); + + auto ptr = seen.lookupForAdd(edge.referent); + // We've already seen this node, don't follow its edges. + if (ptr) + continue; + + // Mark the referent as seen and follow its edges. + if (!seen.add(ptr, edge.referent) || !pushForTraversing(edge.referent)) + return false; + } + + return true; + } +}; + +} // namespace ubi +} // namespace JS + +#endif // js_UbiNodePostOrder_h diff --git a/js/public/Utility.h b/js/public/Utility.h index 9f26c176c6..2e32c1ae80 100644 --- a/js/public/Utility.h +++ b/js/public/Utility.h @@ -62,6 +62,45 @@ JS_Assert(const char* s, const char* file, int ln); #if defined JS_USE_CUSTOM_ALLOCATOR # include "jscustomallocator.h" #else + +namespace js { +namespace oom { + +/* + * To make testing OOM in certain helper threads more effective, + * allow restricting the OOM testing to a certain helper thread + * type. This allows us to fail e.g. in off-thread script parsing + * without causing an OOM in the main thread first. + */ +enum ThreadType { + THREAD_TYPE_NONE = 0, // 0 + THREAD_TYPE_MAIN, // 1 + THREAD_TYPE_ASMJS, // 2 + THREAD_TYPE_ION, // 3 + THREAD_TYPE_PARSE, // 4 + THREAD_TYPE_COMPRESS, // 5 + THREAD_TYPE_GCHELPER, // 6 + THREAD_TYPE_GCPARALLEL, // 7 + THREAD_TYPE_MAX // Used to check shell function arguments +}; + +/* + * Getter/Setter functions to encapsulate mozilla::ThreadLocal, + * implementation is in jsutil.cpp. + */ +# if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) +extern bool InitThreadType(void); +extern void SetThreadType(ThreadType); +extern uint32_t GetThreadType(void); +# else +inline bool InitThreadType(void) { return true; } +inline void SetThreadType(ThreadType t) {}; +inline uint32_t GetThreadType(void) { return 0; } +# endif + +} /* namespace oom */ +} /* namespace js */ + # if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) /* @@ -83,16 +122,27 @@ static MOZ_NEVER_INLINE void js_failedAllocBreakpoint() { asm(""); } namespace js { namespace oom { +extern JS_PUBLIC_DATA(uint32_t) targetThread; + +static inline bool +IsThreadSimulatingOOM() +{ + return js::oom::targetThread && js::oom::targetThread == js::oom::GetThreadType(); +} + static inline bool IsSimulatedOOMAllocation() { - return OOM_counter == OOM_maxAllocations || - (OOM_counter > OOM_maxAllocations && OOM_failAlways); + return IsThreadSimulatingOOM() && (OOM_counter == OOM_maxAllocations || + (OOM_counter > OOM_maxAllocations && OOM_failAlways)); } static inline bool ShouldFailWithOOM() { + if (!IsThreadSimulatingOOM()) + return false; + OOM_counter++; if (IsSimulatedOOMAllocation()) { JS_OOM_CALL_BP_FUNC(); @@ -143,7 +193,8 @@ struct MOZ_RAII AutoEnterOOMUnsafeRegion #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) AutoEnterOOMUnsafeRegion() - : oomEnabled_(OOM_maxAllocations != UINT32_MAX), oomAfter_(0) + : oomEnabled_(oom::IsThreadSimulatingOOM() && OOM_maxAllocations != UINT32_MAX), + oomAfter_(0) { if (oomEnabled_) { oomAfter_ = int64_t(OOM_maxAllocations) - OOM_counter; @@ -152,11 +203,8 @@ struct MOZ_RAII AutoEnterOOMUnsafeRegion } ~AutoEnterOOMUnsafeRegion() { - // TODO: This class is not thread safe. If another thread has modified - // OOM_maxAllocations, don't try to restore it. - if (OOM_maxAllocations != UINT32_MAX) - return; if (oomEnabled_) { + MOZ_ASSERT(OOM_maxAllocations == UINT32_MAX); int64_t maxAllocations = OOM_counter + oomAfter_; MOZ_ASSERT(maxAllocations >= 0 && maxAllocations < UINT32_MAX); OOM_maxAllocations = uint32_t(maxAllocations); @@ -416,15 +464,15 @@ namespace JS { template struct DeletePolicy { - void operator()(T* ptr) { - js_delete(ptr); + void operator()(const T* ptr) { + js_delete(const_cast(ptr)); } }; struct FreePolicy { - void operator()(void* ptr) { - js_free(ptr); + void operator()(const void* ptr) { + js_free(const_cast(ptr)); } }; diff --git a/js/src/NamespaceImports.h b/js/src/NamespaceImports.h index adde176d0b..05a0e5dcf5 100644 --- a/js/src/NamespaceImports.h +++ b/js/src/NamespaceImports.h @@ -82,7 +82,6 @@ using JS::AutoVectorRooter; typedef AutoVectorRooter AutoValueVector; typedef AutoVectorRooter AutoIdVector; typedef AutoVectorRooter AutoObjectVector; -typedef AutoVectorRooter AutoScriptVector; using JS::ValueVector; using JS::IdVector; diff --git a/js/src/asmjs/AsmJSValidate.cpp b/js/src/asmjs/AsmJSValidate.cpp index b0ff10b2b3..6db00d20bc 100644 --- a/js/src/asmjs/AsmJSValidate.cpp +++ b/js/src/asmjs/AsmJSValidate.cpp @@ -8174,7 +8174,11 @@ CheckModule(ExclusiveContext* cx, AsmJSParser& parser, ParseNode* stmtList, static bool Warn(AsmJSParser& parser, int errorNumber, const char* str) { - parser.reportNoOffset(ParseWarning, /* strict = */ false, errorNumber, str ? str : ""); + ParseReportKind reportKind = parser.options().throwOnAsmJSValidationFailureOption && + errorNumber == JSMSG_USE_ASM_TYPE_FAIL + ? ParseError + : ParseWarning; + parser.reportNoOffset(reportKind, /* strict = */ false, errorNumber, str ? str : ""); return false; } diff --git a/js/src/builtin/MapObject.cpp b/js/src/builtin/MapObject.cpp index 30efb760ea..aae2288c8f 100644 --- a/js/src/builtin/MapObject.cpp +++ b/js/src/builtin/MapObject.cpp @@ -395,7 +395,7 @@ MapObject::set(JSContext* cx, HandleObject obj, HandleValue k, HandleValue v) if (!map) return false; - AutoHashableValueRooter key(cx); + Rooted key(cx); if (!key.setValue(cx, k)) return false; @@ -404,7 +404,7 @@ MapObject::set(JSContext* cx, HandleObject obj, HandleValue k, HandleValue v) ReportOutOfMemory(cx); return false; } - WriteBarrierPost(cx->runtime(), map, key.get()); + WriteBarrierPost(cx->runtime(), map, key.value()); return true; } @@ -463,7 +463,7 @@ MapObject::construct(JSContext* cx, unsigned argc, Value* vp) return false; RootedValue pairVal(cx); RootedObject pairObj(cx); - AutoHashableValueRooter hkey(cx); + Rooted hkey(cx); ValueMap* map = obj->getData(); while (true) { bool done; @@ -531,7 +531,7 @@ MapObject::is(HandleObject o) } #define ARG0_KEY(cx, args, key) \ - AutoHashableValueRooter key(cx); \ + Rooted key(cx); \ if (args.length() > 0 && !key.setValue(cx, args[0])) \ return false @@ -579,7 +579,7 @@ MapObject::get(JSContext* cx, HandleObject obj, HandleValue key, MutableHandleValue rval) { ValueMap& map = extract(obj); - AutoHashableValueRooter k(cx); + Rooted k(cx); if (!k.setValue(cx, key)) return false; @@ -610,7 +610,7 @@ bool MapObject::has(JSContext* cx, HandleObject obj, HandleValue key, bool* rval) { ValueMap& map = extract(obj); - AutoHashableValueRooter k(cx); + Rooted k(cx); if (!k.setValue(cx, key)) return false; @@ -650,7 +650,7 @@ MapObject::set_impl(JSContext* cx, const CallArgs& args) ReportOutOfMemory(cx); return false; } - WriteBarrierPost(cx->runtime(), &map, key.get()); + WriteBarrierPost(cx->runtime(), &map, key.value()); args.rval().set(args.thisv()); return true; } @@ -666,7 +666,7 @@ bool MapObject::delete_(JSContext* cx, HandleObject obj, HandleValue key, bool* rval) { ValueMap &map = extract(obj); - AutoHashableValueRooter k(cx); + Rooted k(cx); if (!k.setValue(cx, key)) return false; @@ -1045,7 +1045,7 @@ SetObject::add(JSContext* cx, HandleObject obj, HandleValue k) if (!set) return false; - AutoHashableValueRooter key(cx); + Rooted key(cx); if (!key.setValue(cx, k)) return false; @@ -1053,7 +1053,7 @@ SetObject::add(JSContext* cx, HandleObject obj, HandleValue k) ReportOutOfMemory(cx); return false; } - WriteBarrierPost(cx->runtime(), set, key.get()); + WriteBarrierPost(cx->runtime(), set, key.value()); return true; } @@ -1121,7 +1121,7 @@ SetObject::construct(JSContext* cx, unsigned argc, Value* vp) ForOfIterator iter(cx); if (!iter.init(args[0])) return false; - AutoHashableValueRooter key(cx); + Rooted key(cx); ValueSet* set = obj->getData(); while (true) { bool done; @@ -1229,7 +1229,7 @@ SetObject::has(JSContext* cx, HandleObject obj, HandleValue key, bool* rval) MOZ_ASSERT(SetObject::is(obj)); ValueSet &set = extract(obj); - AutoHashableValueRooter k(cx); + Rooted k(cx); if (!k.setValue(cx, key)) return false; @@ -1256,7 +1256,7 @@ SetObject::add_impl(JSContext* cx, const CallArgs& args) ReportOutOfMemory(cx); return false; } - WriteBarrierPost(cx->runtime(), &set, key.get()); + WriteBarrierPost(cx->runtime(), &set, key.value()); args.rval().set(args.thisv()); return true; } @@ -1274,7 +1274,7 @@ SetObject::delete_(JSContext* cx, HandleObject obj, HandleValue key, bool* rval) MOZ_ASSERT(SetObject::is(obj)); ValueSet &set = extract(obj); - AutoHashableValueRooter k(cx); + Rooted k(cx); if (!k.setValue(cx, key)) return false; diff --git a/js/src/builtin/MapObject.h b/js/src/builtin/MapObject.h index 2bd98f1f52..835905594d 100644 --- a/js/src/builtin/MapObject.h +++ b/js/src/builtin/MapObject.h @@ -22,7 +22,8 @@ namespace js { * * All values except ropes are hashable as-is. */ -class HashableValue { +class HashableValue : public JS::Traceable +{ PreBarrieredValue value; public: @@ -41,34 +42,21 @@ class HashableValue { bool operator==(const HashableValue& other) const; HashableValue mark(JSTracer* trc) const; Value get() const { return value.get(); } + + static void trace(HashableValue* value, JSTracer* trc) { + TraceEdge(trc, &value->value, "HashableValue"); + } }; -class AutoHashableValueRooter : private JS::AutoGCRooter -{ +template <> +class RootedBase { public: - explicit AutoHashableValueRooter(JSContext* cx - MOZ_GUARD_OBJECT_NOTIFIER_PARAM) - : JS::AutoGCRooter(cx, HASHABLEVALUE) - { - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - } - bool setValue(JSContext* cx, HandleValue v) { - return value.setValue(cx, v); + return static_cast*>(this)->get().setValue(cx, v); } - - operator const HashableValue & () { - return value; + Value value() const { + return static_cast*>(this)->get().get(); } - - Value get() const { return value.get(); } - - friend void JS::AutoGCRooter::trace(JSTracer* trc); - void trace(JSTracer* trc); - - private: - HashableValue value; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; template diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index dfa4588a20..cced4d3fa1 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -25,7 +25,7 @@ #include "js/HashTable.h" #include "js/StructuredClone.h" #include "js/UbiNode.h" -#include "js/UbiNodeTraverse.h" +#include "js/UbiNodeBreadthFirst.h" #include "js/Vector.h" #include "vm/GlobalObject.h" #include "vm/Interpreter.h" @@ -977,39 +977,57 @@ DisableTrackAllocations(JSContext* cx, unsigned argc, Value* vp) #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) static bool -OOMAfterAllocations(JSContext* cx, unsigned argc, Value* vp) +OOMThreadTypes(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - if (args.length() != 1) { - JS_ReportError(cx, "count argument required"); + args.rval().setInt32(js::oom::THREAD_TYPE_MAX); + return true; +} + +static bool +SetupOOMFailure(JSContext* cx, bool failAlways, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 1) { + JS_ReportError(cx, "Count argument required"); + return false; + } + + if (args.length() > 2) { + JS_ReportError(cx, "Too many arguments"); return false; } uint32_t count; - if (!JS::ToUint32(cx, args[0], &count)) + if (!JS::ToUint32(cx, args.get(0), &count)) return false; + uint32_t targetThread = js::oom::THREAD_TYPE_MAIN; + if (args.length() > 1 && !ToUint32(cx, args[1], &targetThread)) + return false; + + if (targetThread == js::oom::THREAD_TYPE_NONE || targetThread >= js::oom::THREAD_TYPE_MAX) { + JS_ReportError(cx, "Invalid thread type specified"); + return false; + } + + HelperThreadState().waitForAllThreads(); + js::oom::targetThread = targetThread; OOM_maxAllocations = OOM_counter + count; - OOM_failAlways = true; + OOM_failAlways = failAlways; return true; } +static bool +OOMAfterAllocations(JSContext* cx, unsigned argc, Value* vp) +{ + return SetupOOMFailure(cx, true, argc, vp); +} + static bool OOMAtAllocation(JSContext* cx, unsigned argc, Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); - if (args.length() != 1) { - JS_ReportError(cx, "count argument required"); - return false; - } - - uint32_t count; - if (!JS::ToUint32(cx, args[0], &count)) - return false; - - OOM_maxAllocations = OOM_counter + count; - OOM_failAlways = false; - return true; + return SetupOOMFailure(cx, false, argc, vp); } static bool @@ -1017,6 +1035,7 @@ ResetOOMFailure(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setBoolean(OOM_counter >= OOM_maxAllocations); + js::oom::targetThread = js::oom::THREAD_TYPE_NONE; OOM_maxAllocations = UINT32_MAX; return true; } @@ -2088,9 +2107,9 @@ struct FindPathHandler { typedef BackEdge NodeData; typedef JS::ubi::BreadthFirst Traversal; - FindPathHandler(JS::ubi::Node start, JS::ubi::Node target, + FindPathHandler(JSContext*cx, JS::ubi::Node start, JS::ubi::Node target, AutoValueVector& nodes, Vector& edges) - : start(start), target(target), foundPath(false), + : cx(cx), start(start), target(target), foundPath(false), nodes(nodes), edges(edges) { } bool @@ -2104,7 +2123,7 @@ struct FindPathHandler { // Record how we reached this node. This is the last edge on a // shortest path to this node. - EdgeName edgeName = DuplicateString(traversal.cx, edge.name); + EdgeName edgeName = DuplicateString(cx, edge.name.get()); if (!edgeName) return false; *backEdge = mozilla::Move(BackEdge(origin, Move(edgeName))); @@ -2141,6 +2160,8 @@ struct FindPathHandler { return true; } + JSContext* cx; + // The node we're starting from. JS::ubi::Node start; @@ -2199,8 +2220,8 @@ FindPath(JSContext* cx, unsigned argc, Value* vp) JS::ubi::Node start(args[0]), target(args[1]); - heaptools::FindPathHandler handler(start, target, nodes, edges); - heaptools::FindPathHandler::Traversal traversal(cx, handler, autoCannotGC); + heaptools::FindPathHandler handler(cx, start, target, nodes, edges); + heaptools::FindPathHandler::Traversal traversal(cx->runtime(), handler, autoCannotGC); if (!traversal.init() || !traversal.addStart(start)) return false; @@ -2424,6 +2445,34 @@ ByteSize(JSContext* cx, unsigned argc, Value* vp) return true; } +static bool +ByteSizeOfScript(JSContext*cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (!args.requireAtLeast(cx, "byteSizeOfScript", 1)) + return false; + if (!args[0].isObject() || !args[0].toObject().is()) { + JS_ReportError(cx, "Argument must be a Function object"); + return false; + } + + RootedScript script(cx, args[0].toObject().as().getOrCreateScript(cx)); + mozilla::MallocSizeOf mallocSizeOf = cx->runtime()->debuggerMallocSizeOf; + + { + // We can't tolerate the GC moving things around while we're using a + // ubi::Node. Check that nothing we do causes a GC. + JS::AutoCheckCannotGC autoCannotGC; + + JS::ubi::Node node = script; + if (node) + args.rval().setNumber(uint32_t(node.size(mallocSizeOf))); + else + args.rval().setUndefined(); + } + return true; +} + static bool ImmutablePrototypesEnabled(JSContext* cx, unsigned argc, Value* vp) { @@ -2852,15 +2901,22 @@ static const JSFunctionSpecWithHelp TestingFunctions[] = { " Stop capturing the JS stack at every allocation."), #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) - JS_FN_HELP("oomAfterAllocations", OOMAfterAllocations, 1, 0, -"oomAfterAllocations(count)", -" After 'count' js_malloc memory allocations, fail every following allocation\n" -" (return NULL)."), + JS_FN_HELP("oomThreadTypes", OOMThreadTypes, 0, 0, +"oomThreadTypes()", +" Get the number of thread types that can be used as an argument for\n" +"oomAfterAllocations() and oomAtAllocation()."), - JS_FN_HELP("oomAtAllocation", OOMAtAllocation, 1, 0, -"oomAtAllocation(count)", + JS_FN_HELP("oomAfterAllocations", OOMAfterAllocations, 2, 0, +"oomAfterAllocations(count [,threadType])", +" After 'count' js_malloc memory allocations, fail every following allocation\n" +" (return nullptr). The optional thread type limits the effect to the\n" +" specified type of helper thread."), + + JS_FN_HELP("oomAtAllocation", OOMAtAllocation, 2, 0, +"oomAtAllocation(count [,threadType])", " After 'count' js_malloc memory allocations, fail the next allocation\n" -" (return NULL)."), +" (return nullptr). The optional thread type limits the effect to the\n" +" specified type of helper thread."), JS_FN_HELP("resetOOMFailure", ResetOOMFailure, 0, 0, "resetOOMFailure()", @@ -3174,6 +3230,10 @@ gc::ZealModeHelpText), " Return the size in bytes occupied by |value|, or |undefined| if value\n" " is not allocated in memory.\n"), + JS_FN_HELP("byteSizeOfScript", ByteSizeOfScript, 1, 0, +"byteSizeOfScript(f)", +" Return the size in bytes occupied by the function |f|'s JSScript.\n"), + JS_FN_HELP("immutablePrototypesEnabled", ImmutablePrototypesEnabled, 0, 0, "immutablePrototypesEnabled()", " Returns true if immutable-prototype behavior (triggered by setImmutablePrototype)\n" diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h index 470dc5c1ef..319d46a26e 100644 --- a/js/src/builtin/TypedObject.h +++ b/js/src/builtin/TypedObject.h @@ -593,7 +593,7 @@ class TypedObject : public JSObject static bool GetBuffer(JSContext* cx, unsigned argc, Value* vp); static bool GetByteOffset(JSContext* cx, unsigned argc, Value* vp); - Shape** addressOfShapeFromGC() { return shape_.unsafeGet(); } + Shape** addressOfShapeFromGC() { return shape_.unsafeUnbarrieredForTracing(); } }; typedef Handle HandleTypedObject; diff --git a/js/src/ds/LifoAlloc.h b/js/src/ds/LifoAlloc.h index cbe9ab91a3..e9f99ae5d1 100644 --- a/js/src/ds/LifoAlloc.h +++ b/js/src/ds/LifoAlloc.h @@ -532,7 +532,7 @@ class LifoAllocPolicy : alloc_(alloc) {} template - T* pod_malloc(size_t numElems) { + T* maybe_pod_malloc(size_t numElems) { size_t bytes; if (MOZ_UNLIKELY(!CalculateAllocSize(numElems, &bytes))) return nullptr; @@ -540,7 +540,7 @@ class LifoAllocPolicy return static_cast(p); } template - T* pod_calloc(size_t numElems) { + T* maybe_pod_calloc(size_t numElems) { T* p = pod_malloc(numElems); if (MOZ_UNLIKELY(!p)) return nullptr; @@ -548,7 +548,7 @@ class LifoAllocPolicy return p; } template - T* pod_realloc(T* p, size_t oldSize, size_t newSize) { + T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) { T* n = pod_malloc(newSize); if (MOZ_UNLIKELY(!n)) return nullptr; @@ -556,6 +556,18 @@ class LifoAllocPolicy memcpy(n, p, Min(oldSize * sizeof(T), newSize * sizeof(T))); return n; } + template + T* pod_malloc(size_t numElems) { + return maybe_pod_malloc(numElems); + } + template + T* pod_calloc(size_t numElems) { + return maybe_pod_calloc(numElems); + } + template + T* pod_realloc(T* p, size_t oldSize, size_t newSize) { + return maybe_pod_realloc(p, oldSize, newSize); + } void free_(void* p) { } void reportAllocOverflow() const { diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index cb5710895e..0e56102dd9 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -221,7 +221,8 @@ BytecodeCompiler::canLazilyParse() !HasNonSyntacticStaticScopeChain(enclosingStaticScope) && !cx->compartment()->options().disableLazyParsing() && !cx->compartment()->options().discardSource() && - !options.sourceIsLazy; + !options.sourceIsLazy && + !cx->lcovEnabled(); } bool diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 825e5f4a0e..b989cb4df7 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -908,6 +908,12 @@ BytecodeEmitter::enterNestedScope(StmtInfoBCE* stmt, ObjectBox* objbox, StmtType if (!emitInternedObjectOp(scopeObjectIndex, JSOP_PUSHBLOCKSCOPE)) return false; } + + // Non-global block scopes are non-extensible. At this point the + // Parser has added all bindings to the StaticBlockObject, so we make + // it non-extensible. + if (!blockObj->makeNonExtensible(cx)) + return false; break; } case StmtType::WITH: diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 4136afa7ad..b54f8accf9 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -624,7 +624,7 @@ class ParseNode union { unsigned iflags; /* JSITER_* flags for PNK_FOR node */ ObjectBox* objbox; /* Only for PN_BINARY_OBJ */ - bool isStatic; /* Only for PNK_CLASSMETHOD */ + bool isStatic; /* Only for PNK_CLASSMETHOD */ }; } binary; struct { /* one kid if unary */ @@ -1128,7 +1128,7 @@ struct LexicalScopeNode : public ParseNode pn_blockid = blockNode->pn_blockid; } - static bool test(const ParseNode &node) { + static bool test(const ParseNode& node) { return node.isKind(PNK_LEXICALSCOPE); } }; @@ -1369,22 +1369,22 @@ struct ClassMethod : public BinaryNode { * Method defintions often keep a name and function body that overlap, * so explicitly define the beginning and end here. */ - ClassMethod(ParseNode *name, ParseNode *body, JSOp op, bool isStatic) + ClassMethod(ParseNode* name, ParseNode* body, JSOp op, bool isStatic) : BinaryNode(PNK_CLASSMETHOD, op, TokenPos(name->pn_pos.begin, body->pn_pos.end), name, body) { pn_u.binary.isStatic = isStatic; } - static bool test(const ParseNode &node) { + static bool test(const ParseNode& node) { bool match = node.isKind(PNK_CLASSMETHOD); MOZ_ASSERT_IF(match, node.isArity(PN_BINARY)); return match; } - ParseNode &name() const { + ParseNode& name() const { return *pn_u.binary.left; } - ParseNode &method() const { + ParseNode& method() const { return *pn_u.binary.right; } bool isStatic() const { diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index bc390e2bd2..ed325d31ec 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -4914,9 +4914,13 @@ Parser::exportDeclaration() switch (tt) { case TOK_FUNCTION: kid = functionStmt(YieldIsKeyword, AllowDefaultName); + if (!kid) + return null(); break; case TOK_CLASS: kid = classDefinition(YieldIsKeyword, ClassStatement, AllowDefaultName); + if (!kid) + return null(); break; default: tokenStream.ungetToken(); @@ -4925,14 +4929,12 @@ Parser::exportDeclaration() if (!binding) return null(); kid = assignExpr(InAllowed, YieldIsKeyword); - if (kid) { - if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) - return null(); - } + if (!kid) + return null(); + if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) + return null(); break; } - if (!kid) - return null(); return handler.newExportDefaultDeclaration(kid, binding, TokenPos(begin, pos().end)); } diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index 0522c06dd4..773a52a4bb 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -772,7 +772,10 @@ TokenStream::reportAsmJSError(uint32_t offset, unsigned errorNumber, ...) { va_list args; va_start(args, errorNumber); - reportCompileErrorNumberVA(offset, JSREPORT_WARNING, errorNumber, args); + unsigned flags = options().throwOnAsmJSValidationFailureOption + ? JSREPORT_ERROR + : JSREPORT_WARNING; + reportCompileErrorNumberVA(offset, flags, errorNumber, args); va_end(args); } diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h index 1750073181..e3601b0ba6 100644 --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -223,7 +223,7 @@ struct Token PropertyName* name() const { MOZ_ASSERT(type == TOK_NAME); - return u.name->asPropertyName(); // poor-man's type verification + return u.name->JSAtom::asPropertyName(); // poor-man's type verification } bool nameContainsEscape() const { diff --git a/js/src/gc/Barrier.cpp b/js/src/gc/Barrier.cpp index 4014f4a8e6..8c96726e36 100644 --- a/js/src/gc/Barrier.cpp +++ b/js/src/gc/Barrier.cpp @@ -17,6 +17,21 @@ #ifdef DEBUG +template +void +js::BarrieredBase::assertTypeConstraints() const +{ + static_assert(mozilla::IsBaseOf::Type>::value || + mozilla::IsSame::value || + mozilla::IsSame::value || + mozilla::IsSame::value, + "ensure only supported types are instantiated with barriers"); +} +#define INSTANTIATE_ALL_VALID_TYPES(type) \ + template void js::BarrieredBase::assertTypeConstraints() const; +FOR_EACH_GC_POINTER_TYPE(INSTANTIATE_ALL_VALID_TYPES) +#undef INSTANTIATE_ALL_VALID_TYPES + bool js::HeapSlot::preconditionForSet(NativeObject* owner, Kind kind, uint32_t slot) { diff --git a/js/src/gc/Barrier.h b/js/src/gc/Barrier.h index 22adbad4ab..2c43362ce3 100644 --- a/js/src/gc/Barrier.h +++ b/js/src/gc/Barrier.h @@ -135,20 +135,27 @@ * * This file implements four classes, illustrated here: * - * BarrieredBase abstract base class which provides common operations - * | | | | - * | | | PreBarriered provides pre-barriers only - * | | | - * | | HeapPtr provides pre- and post-barriers + * BarrieredBase base class of all barriers * | | - * | RelocatablePtr provides pre- and post-barriers and is relocatable + * | WriteBarrieredBase base class which provides common write operations + * | | | | | + * | | | | PreBarriered provides pre-barriers only + * | | | | + * | | | HeapPtr provides pre- and post-barriers + * | | | + * | | RelocatablePtr provides pre- and post-barriers and is relocatable + * | | + * | HeapSlot similar to HeapPtr, but tailored to slots storage * | - * HeapSlot similar to HeapPtr, but tailored to slots storage + * ReadBarrieredBase base class which provides common read operations + * | + * ReadBarriered provides read barriers only + * * * The implementation of the barrier logic is implemented on T::writeBarrier.*, * via: * - * BarrieredBase::pre + * WriteBarrieredBase::pre * -> InternalGCMethods::preBarrier * -> T::writeBarrierPre * -> InternalGCMethods::preBarrier @@ -302,48 +309,71 @@ struct InternalGCMethods static void postBarrier(jsid* idp, jsid prev, jsid next) {} }; +// Barrier classes can use Mixins to add methods to a set of barrier +// instantiations, to make the barriered thing look and feel more like the +// thing itself. template class BarrieredBaseMixins {}; - // Base class of all barrier types. -// -// This is marked non-memmovable since post barriers added by derived classes -// can add pointers to class instances to the store buffer. +// Base class of all barrier types. template class MOZ_NON_MEMMOVABLE BarrieredBase : public BarrieredBaseMixins { protected: + // BarrieredBase is not directly instantiable. + explicit BarrieredBase(T v) : value(v) { +#ifdef DEBUG + assertTypeConstraints(); +#endif + } + + // Storage for all barrier classes. |value| must be a GC thing reference + // type: either a direct pointer to a GC thing or a supported tagged + // pointer that can reference GC things, such as JS::Value or jsid. Nested + // barrier types are NOT supported. See assertTypeConstraints. T value; - explicit BarrieredBase(T v) : value(v) {} + public: + // Note: this is public because C++ cannot friend to a specific template instantiation. + // Friending to the generic template leads to a number of unintended consequences, including + // template resolution ambiguity and a circular dependency with Tracing.h. + T* unsafeUnbarrieredForTracing() { return &value; } + + private: +#ifdef DEBUG + // Static type assertions about T must be moved out of line to avoid + // circular dependencies between Barrier classes and GC memory definitions. + void assertTypeConstraints() const; +#endif +}; + +// Base class for barriered pointer types that intercept only writes. +template +class WriteBarrieredBase : public BarrieredBase +{ + protected: + // WriteBarrieredBase is not directly instantiable. + explicit WriteBarrieredBase(T v) : BarrieredBase(v) {} public: DECLARE_POINTER_COMPARISON_OPS(T); DECLARE_POINTER_CONSTREF_OPS(T); - /* Use this if the automatic coercion to T isn't working. */ - const T& get() const { return value; } + // Use this if the automatic coercion to T isn't working. + const T& get() const { return this->value; } - /* - * Use these if you want to change the value without invoking barriers. - * Obviously this is dangerous unless you know the barrier is not needed. - */ - T* unsafeGet() { return &value; } - const T* unsafeGet() const { return &value; } - void unsafeSet(T v) { value = v; } + // Use this if you want to change the value without invoking barriers. + // Obviously this is dangerous unless you know the barrier is not needed. + void unsafeSet(T v) { this->value = v; } - /* For users who need to manually barrier the raw types. */ + // For users who need to manually barrier the raw types. static void writeBarrierPre(const T& v) { InternalGCMethods::preBarrier(v); } protected: - void pre() { InternalGCMethods::preBarrier(value); } - void post(T prev, T next) { InternalGCMethods::postBarrier(&value, prev, next); } + void pre() { InternalGCMethods::preBarrier(this->value); } + void post(T prev, T next) { InternalGCMethods::postBarrier(&this->value, prev, next); } }; -template <> -class BarrieredBaseMixins : public ValueOperations > -{}; - /* * PreBarriered only automatically handles pre-barriers. Post-barriers must * be manually implemented when using this class. HeapPtr and RelocatablePtr @@ -351,15 +381,15 @@ class BarrieredBaseMixins : public ValueOperations -class PreBarriered : public BarrieredBase +class PreBarriered : public WriteBarrieredBase { public: - PreBarriered() : BarrieredBase(GCMethods::initial()) {} + PreBarriered() : WriteBarrieredBase(GCMethods::initial()) {} /* * Allow implicit construction for use in generic contexts, such as DebuggerWeakMap::markKeys. */ - MOZ_IMPLICIT PreBarriered(T v) : BarrieredBase(v) {} - explicit PreBarriered(const PreBarriered& v) : BarrieredBase(v.value) {} + MOZ_IMPLICIT PreBarriered(T v) : WriteBarrieredBase(v) {} + explicit PreBarriered(const PreBarriered& v) : WriteBarrieredBase(v.value) {} ~PreBarriered() { this->pre(); } void init(T v) { @@ -396,14 +426,14 @@ class PreBarriered : public BarrieredBase * automatically handling deletion or movement. */ template -class HeapPtr : public BarrieredBase +class HeapPtr : public WriteBarrieredBase { public: - HeapPtr() : BarrieredBase(GCMethods::initial()) {} - explicit HeapPtr(T v) : BarrieredBase(v) { + HeapPtr() : WriteBarrieredBase(GCMethods::initial()) {} + explicit HeapPtr(T v) : WriteBarrieredBase(v) { this->post(GCMethods::initial(), v); } - explicit HeapPtr(const HeapPtr& v) : BarrieredBase(v) { + explicit HeapPtr(const HeapPtr& v) : WriteBarrieredBase(v) { this->post(GCMethods::initial(), v); } #ifdef DEBUG @@ -448,11 +478,11 @@ class HeapPtr : public BarrieredBase * used in contexts where this ability is necessary. */ template -class RelocatablePtr : public BarrieredBase +class RelocatablePtr : public WriteBarrieredBase { public: - RelocatablePtr() : BarrieredBase(GCMethods::initial()) {} - explicit RelocatablePtr(T v) : BarrieredBase(v) { + RelocatablePtr() : WriteBarrieredBase(GCMethods::initial()) {} + explicit RelocatablePtr(T v) : WriteBarrieredBase(v) { this->post(GCMethods::initial(), this->value); } @@ -462,7 +492,7 @@ class RelocatablePtr : public BarrieredBase * function that will be used for both lvalue and rvalue copies, so we can * simply omit the rvalue variant. */ - RelocatablePtr(const RelocatablePtr& v) : BarrieredBase(v) { + RelocatablePtr(const RelocatablePtr& v) : WriteBarrieredBase(v) { this->post(GCMethods::initial(), this->value); } @@ -498,52 +528,84 @@ class RelocatablePtr : public BarrieredBase } }; -/* - * Incremental GC requires that weak pointers have read barriers. This is mostly - * an issue for empty shapes stored in JSCompartment. The problem happens when, - * during an incremental GC, some JS code stores one of the compartment's empty - * shapes into an object already marked black. Normally, this would not be a - * problem, because the empty shape would have been part of the initial snapshot - * when the GC started. However, since this is a weak pointer, it isn't. So we - * may collect the empty shape even though a live object points to it. To fix - * this, we mark these empty shapes black whenever they get read out. - */ -template -class ReadBarriered +// Base class for barriered pointer types that intercept reads and writes. +template +class ReadBarrieredBase : public BarrieredBase { - T value; + protected: + // ReadBarrieredBase is not directly instantiable. + explicit ReadBarrieredBase(T v) : BarrieredBase(v) {} - public: - ReadBarriered() : value(nullptr) {} - explicit ReadBarriered(T value) : value(value) {} - explicit ReadBarriered(const Rooted& rooted) : value(rooted) {} - - T get() const { - if (!InternalGCMethods::isMarkable(value)) - return GCMethods::initial(); - InternalGCMethods::readBarrier(value); - return value; - } - - T unbarrieredGet() const { - return value; - } - - operator T() const { return get(); } - - T& operator*() const { return *get(); } - T operator->() const { return get(); } - - T* unsafeGet() { return &value; } - T const * unsafeGet() const { return &value; } - - void set(T v) { value = v; } + protected: + void read() const { InternalGCMethods::readBarrier(this->value); } + void post(T prev, T next) { InternalGCMethods::postBarrier(&this->value, prev, next); } }; +// Incremental GC requires that weak pointers have read barriers. This is mostly +// an issue for empty shapes stored in JSCompartment. The problem happens when, +// during an incremental GC, some JS code stores one of the compartment's empty +// shapes into an object already marked black. Normally, this would not be a +// problem, because the empty shape would have been part of the initial snapshot +// when the GC started. However, since this is a weak pointer, it isn't. So we +// may collect the empty shape even though a live object points to it. To fix +// this, we mark these empty shapes black whenever they get read out. +// +// Note that this class also has post-barriers, so is safe to use with nursery +// pointers. However, when used as a hashtable key, care must still be taken to +// insert manual post-barriers on the table for rekeying if the key is based in +// any way on the address of the object. +template +class ReadBarriered : public ReadBarrieredBase +{ + public: + ReadBarriered() : ReadBarrieredBase(GCMethods::initial()) {} + explicit ReadBarriered(const T& v) : ReadBarrieredBase(v) { + this->post(GCMethods::initial(), v); + } + ~ReadBarriered() { + this->post(this->value, GCMethods::initial()); + } + + const T get() const { + if (!InternalGCMethods::isMarkable(this->value)) + return GCMethods::initial(); + this->read(); + return this->value; + } + + const T unbarrieredGet() const { + return this->value; + } + + operator const T() const { return get(); } + + const T operator->() const { return get(); } + + T const* unsafeGet() const { return &this->value; } + + void set(const T& v) + { + T tmp = this->value; + this->value = v; + this->post(tmp, v); + } +}; + +// A WeakRef pointer does not hold its target live and is automatically nulled +// out when the GC discovers that it is not reachable from any other path. +template +using WeakRef = ReadBarriered; + +// Add Value operations to all Barrier types. Note, this must be defined before +// HeapSlot for HeapSlot's base to get these operations. +template <> +class BarrieredBaseMixins : public ValueOperations> +{}; + // A pre- and post-barriered Value that is specialized to be aware that it // resides in a slots or elements vector. This allows it to be relocated in // memory, but with substantially less overhead than a RelocatablePtr. -class HeapSlot : public BarrieredBase +class HeapSlot : public WriteBarrieredBase { public: enum Kind { @@ -554,13 +616,13 @@ class HeapSlot : public BarrieredBase explicit HeapSlot() = delete; explicit HeapSlot(NativeObject* obj, Kind kind, uint32_t slot, const Value& v) - : BarrieredBase(v) + : WriteBarrieredBase(v) { post(obj, kind, slot, v); } explicit HeapSlot(NativeObject* obj, Kind kind, uint32_t slot, const HeapSlot& s) - : BarrieredBase(s.value) + : WriteBarrieredBase(s.value) { post(obj, kind, slot, s); } @@ -591,8 +653,6 @@ class HeapSlot : public BarrieredBase reinterpret_cast(const_cast(&target))->post(owner, kind, slot, target); } - Value* unsafeGet() { return &value; } - private: void post(NativeObject* owner, Kind kind, uint32_t slot, const Value& target) { MOZ_ASSERT(preconditionForWriteBarrierPost(owner, kind, slot, target)); diff --git a/js/src/gc/GCInternals.h b/js/src/gc/GCInternals.h index 79cab1b3f3..03124f86fd 100644 --- a/js/src/gc/GCInternals.h +++ b/js/src/gc/GCInternals.h @@ -56,6 +56,7 @@ class MOZ_RAII AutoTraceSession void operator=(const AutoTraceSession&) = delete; JS::HeapState prevState; + AutoSPSEntry pseudoFrame; }; struct MOZ_RAII AutoPrepareForTracing diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index 7afe24d4da..588f9c0257 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -678,7 +678,7 @@ class GCRuntime bool onBackgroundThread() { return helperState.onBackgroundThread(); } bool currentThreadOwnsGCLock() { - return lockOwner == PR_GetCurrentThread(); + return lockOwner.value == PR_GetCurrentThread(); } #endif // DEBUG @@ -689,15 +689,17 @@ class GCRuntime void lockGC() { PR_Lock(lock); - MOZ_ASSERT(!lockOwner); #ifdef DEBUG - lockOwner = PR_GetCurrentThread(); + MOZ_ASSERT(!lockOwner.value); + lockOwner.value = PR_GetCurrentThread(); #endif } void unlockGC() { - MOZ_ASSERT(lockOwner == PR_GetCurrentThread()); - lockOwner = nullptr; +#ifdef DEBUG + MOZ_ASSERT(lockOwner.value == PR_GetCurrentThread()); + lockOwner.value = nullptr; +#endif PR_Unlock(lock); } @@ -726,19 +728,19 @@ class GCRuntime void setAlwaysPreserveCode() { alwaysPreserveCode = true; } - bool isIncrementalGCAllowed() { return incrementalAllowed; } + bool isIncrementalGCAllowed() const { return incrementalAllowed; } void disallowIncrementalGC() { incrementalAllowed = false; } - bool isIncrementalGCEnabled() { return mode == JSGC_MODE_INCREMENTAL && incrementalAllowed; } - bool isIncrementalGCInProgress() { return state() != gc::NO_INCREMENTAL; } + bool isIncrementalGCEnabled() const { return mode == JSGC_MODE_INCREMENTAL && incrementalAllowed; } + bool isIncrementalGCInProgress() const { return state() != gc::NO_INCREMENTAL; } - bool isGenerationalGCEnabled() { return generationalDisabled == 0; } + bool isGenerationalGCEnabled() const { return generationalDisabled == 0; } void disableGenerationalGC(); void enableGenerationalGC(); void disableCompactingGC(); void enableCompactingGC(); - bool isCompactingGCEnabled(); + bool isCompactingGCEnabled() const; void setGrayRootsTracer(JSTraceDataOp traceOp, void* data); bool addBlackRootsTracer(JSTraceDataOp traceOp, void* data); @@ -892,12 +894,22 @@ class GCRuntime void requestMajorGC(JS::gcreason::Reason reason); SliceBudget defaultBudget(JS::gcreason::Reason reason, int64_t millis); - void collect(bool incremental, SliceBudget budget, JS::gcreason::Reason reason); - bool gcCycle(bool incremental, SliceBudget& budget, JS::gcreason::Reason reason); - gcstats::ZoneGCStats scanZonesBeforeGC(); void budgetIncrementalGC(SliceBudget& budget); void resetIncrementalGC(const char* reason); + + // Assert if the system state is such that we should never + // receive a request to do GC work. + void checkCanCallAPI(); + + // Check if the system state is such that GC has been supressed + // or otherwise delayed. + bool checkIfGCAllowedInCurrentState(JS::gcreason::Reason reason); + + gcstats::ZoneGCStats scanZonesBeforeGC(); + void collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::Reason reason); + bool gcCycle(bool nonincrementalByAPI, SliceBudget& budget, JS::gcreason::Reason reason); void incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason reason); + void pushZealSelectedObjects(); void purgeRuntime(); bool beginMarkPhase(JS::gcreason::Reason reason); @@ -1296,7 +1308,7 @@ class GCRuntime /* Synchronize GC heap access between main thread and GCHelperState. */ PRLock* lock; - mozilla::DebugOnly lockOwner; + mozilla::DebugOnly> lockOwner; BackgroundAllocTask allocTask; GCHelperState helperState; diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 5c19e6657d..1a661fa1fd 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -352,46 +352,6 @@ AssertRootMarkingPhase(JSTracer* trc) /*** Tracing Interface ***************************************************************************/ -#define FOR_EACH_GC_POINTER_TYPE(D) \ - D(AccessorShape*) \ - D(BaseShape*) \ - D(UnownedBaseShape*) \ - D(jit::JitCode*) \ - D(NativeObject*) \ - D(ArrayObject*) \ - D(ArgumentsObject*) \ - D(ArrayBufferObject*) \ - D(ArrayBufferObjectMaybeShared*) \ - D(ArrayBufferViewObject*) \ - D(DebugScopeObject*) \ - D(GlobalObject*) \ - D(JSObject*) \ - D(JSFunction*) \ - D(ModuleObject*) \ - D(ModuleEnvironmentObject*) \ - D(NestedScopeObject*) \ - D(PlainObject*) \ - D(SavedFrame*) \ - D(ScopeObject*) \ - D(ScriptSourceObject*) \ - D(SharedArrayBufferObject*) \ - D(SharedTypedArrayObject*) \ - D(ImportEntryObject*) \ - D(ExportEntryObject*) \ - D(JSScript*) \ - D(LazyScript*) \ - D(Shape*) \ - D(JSAtom*) \ - D(JSString*) \ - D(JSFlatString*) \ - D(JSLinearString*) \ - D(PropertyName*) \ - D(JS::Symbol*) \ - D(js::ObjectGroup*) \ - D(Value) \ - D(jsid) \ - D(TaggedProto) - // The second parameter to BaseGCType is derived automatically based on T. The // relation here is that for any T, the TraceKind will automatically, // statically select the correct Cell layout for marking. Below, we instantiate @@ -438,12 +398,14 @@ template void DispatchToTracer(JSTracer* trc, T* thingp, const char template T DoCallback(JS::CallbackTracer* trc, T* thingp, const char* name); template void DoMarking(GCMarker* gcmarker, T* thing); template void DoMarking(GCMarker* gcmarker, T thing); +template void NoteWeakEdge(GCMarker* gcmarker, T** thingp); +template void NoteWeakEdge(GCMarker* gcmarker, T* thingp); template void -js::TraceEdge(JSTracer* trc, BarrieredBase* thingp, const char* name) +js::TraceEdge(JSTracer* trc, WriteBarrieredBase* thingp, const char* name) { - DispatchToTracer(trc, ConvertToBase(thingp->unsafeGet()), name); + DispatchToTracer(trc, ConvertToBase(thingp->unsafeUnbarrieredForTracing()), name); } template @@ -453,6 +415,18 @@ js::TraceManuallyBarrieredEdge(JSTracer* trc, T* thingp, const char* name) DispatchToTracer(trc, ConvertToBase(thingp), name); } +template +void +js::TraceWeakEdge(JSTracer* trc, WeakRef* thingp, const char* name) +{ + // Non-marking tracers treat the edge strongly. + if (!trc->isMarkingTracer()) + DispatchToTracer(trc, ConvertToBase(thingp->unsafeUnbarrieredForTracing()), name); + + NoteWeakEdge(static_cast(trc), + ConvertToBase(thingp->unsafeUnbarrieredForTracing())); +} + template void js::TraceRoot(JSTracer* trc, T* thingp, const char* name) @@ -472,12 +446,12 @@ js::TraceNullableRoot(JSTracer* trc, T* thingp, const char* name) template void -js::TraceRange(JSTracer* trc, size_t len, BarrieredBase* vec, const char* name) +js::TraceRange(JSTracer* trc, size_t len, WriteBarrieredBase* vec, const char* name) { JS::AutoTracingIndex index(trc); for (auto i : MakeRange(len)) { if (InternalGCMethods::isMarkable(vec[i].get())) - DispatchToTracer(trc, ConvertToBase(vec[i].unsafeGet()), name); + DispatchToTracer(trc, ConvertToBase(vec[i].unsafeUnbarrieredForTracing()), name); ++index; } } @@ -497,11 +471,12 @@ js::TraceRootRange(JSTracer* trc, size_t len, T* vec, const char* name) // Instantiate a copy of the Tracing templates for each derived type. #define INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS(type) \ - template void js::TraceEdge(JSTracer*, BarrieredBase*, const char*); \ + template void js::TraceEdge(JSTracer*, WriteBarrieredBase*, const char*); \ template void js::TraceManuallyBarrieredEdge(JSTracer*, type*, const char*); \ + template void js::TraceWeakEdge(JSTracer*, WeakRef*, const char*); \ template void js::TraceRoot(JSTracer*, type*, const char*); \ template void js::TraceNullableRoot(JSTracer*, type*, const char*); \ - template void js::TraceRange(JSTracer*, size_t, BarrieredBase*, const char*); \ + template void js::TraceRange(JSTracer*, size_t, WriteBarrieredBase*, const char*); \ template void js::TraceRootRange(JSTracer*, size_t, type*, const char*); FOR_EACH_GC_POINTER_TYPE(INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS) #undef INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS @@ -521,13 +496,14 @@ template void js::TraceManuallyBarrieredCrossCompartmentEdge(JSTracer template void -js::TraceCrossCompartmentEdge(JSTracer* trc, JSObject* src, BarrieredBase* dst, const char* name) +js::TraceCrossCompartmentEdge(JSTracer* trc, JSObject* src, WriteBarrieredBase* dst, + const char* name) { if (ShouldMarkCrossCompartment(trc, src, dst->get())) - DispatchToTracer(trc, dst->unsafeGet(), name); + DispatchToTracer(trc, dst->unsafeUnbarrieredForTracing(), name); } -template void js::TraceCrossCompartmentEdge(JSTracer*, JSObject*, BarrieredBase*, - const char*); +template void js::TraceCrossCompartmentEdge(JSTracer*, JSObject*, + WriteBarrieredBase*, const char*); template void @@ -755,6 +731,47 @@ DoMarking(GCMarker* gcmarker, T thing) DispatchTyped(DoMarkingFunctor(), thing, gcmarker); } +template +void +NoteWeakEdge(GCMarker* gcmarker, T** thingp) +{ + // Do per-type marking precondition checks. + if (MustSkipMarking(*thingp)) + return; + + CheckTracedThing(gcmarker, *thingp); + + // If the target is already marked, there's no need to store the edge. + if (IsMarkedUnbarriered(thingp)) + return; + + gcmarker->noteWeakEdge(thingp); +} + +template +void +NoteWeakEdge(GCMarker* gcmarker, T* thingp) +{ + MOZ_CRASH("the gc does not support tagged pointers as weak edges"); +} + +template +void +js::GCMarker::noteWeakEdge(T* edge) +{ + static_assert(IsBaseOf::Type>::value, + "edge must point to a GC pointer"); + MOZ_ASSERT((*edge)->isTenured()); + + // Note: we really want the *source* Zone here. The edge may start in a + // non-gc heap location, however, so we use the fact that cross-zone weak + // references are not allowed and use the *target's* zone. + JS::Zone::WeakEdges &weakRefs = (*edge)->asTenured().zone()->gcWeakRefs; + AutoEnterOOMUnsafeRegion oomUnsafe; + if (!weakRefs.append(reinterpret_cast(edge))) + oomUnsafe.crash("Failed to record a weak edge for sweeping."); +} + // The simplest traversal calls out to the fully generic traceChildren function // to visit the child edges. In the absence of other traversal mechanisms, this // function will rapidly grow the stack past its bounds and crash the process. @@ -877,6 +894,9 @@ js::GCMarker::mark(T* thing) void LazyScript::traceChildren(JSTracer* trc) { + if (script_) + TraceWeakEdge(trc, &script_, "script"); + if (function_) TraceEdge(trc, &function_, "function"); @@ -900,6 +920,9 @@ LazyScript::traceChildren(JSTracer* trc) inline void js::GCMarker::eagerlyMarkChildren(LazyScript *thing) { + if (thing->script_) + noteWeakEdge(thing->script_.unsafeUnbarrieredForTracing()); + if (thing->function_) traverseEdge(thing, static_cast(thing->function_)); @@ -1012,7 +1035,33 @@ js::GCMarker::eagerlyMarkChildren(JSRope* rope) // types. ptrdiff_t savedPos = stack.position(); JS_DIAGNOSTICS_ASSERT(rope->getTraceKind() == JS::TraceKind::String); +#ifdef JS_DEBUG + static const size_t DEEP_ROPE_THRESHOLD = 100000; + static const size_t ROPE_CYCLE_HISTORY = 100; + DebugOnly ropeDepth = 0; + JSRope* history[ROPE_CYCLE_HISTORY]; +#endif while (true) { +#ifdef JS_DEBUG + if (++ropeDepth >= DEEP_ROPE_THRESHOLD) { + // Bug 1011786 comment 294 - detect cyclic ropes. There are some + // legitimate deep ropes, at least in tests. So if we hit a deep + // rope, start recording the nodes we visit and check whether we + // repeat. But do it on a finite window size W so that we're not + // scanning the full history for every node. And only check every + // Wth push, to add only constant overhead per node. This will only + // catch cycles of size up to W (but it seems most likely that any + // cycles will be size 1 or maybe 2.) + if ((ropeDepth > DEEP_ROPE_THRESHOLD + ROPE_CYCLE_HISTORY) && + (ropeDepth % ROPE_CYCLE_HISTORY) == 0) + { + for (size_t i = 0; i < ROPE_CYCLE_HISTORY; i++) + MOZ_ASSERT(history[i] != rope, "cycle detected in rope"); + } + history[ropeDepth % ROPE_CYCLE_HISTORY] = rope; + } +#endif + JS_DIAGNOSTICS_ASSERT(rope->getTraceKind() == JS::TraceKind::String); JS_DIAGNOSTICS_ASSERT(rope->JSString::isRope()); AssertZoneIsMarking(rope); @@ -1249,6 +1298,41 @@ GCMarker::drainMarkStack(SliceBudget& budget) return true; } +inline static bool +ObjectDenseElementsMayBeMarkable(NativeObject* nobj) +{ + /* + * For arrays that are large enough it's worth checking the type information + * to see if the object's elements contain any GC pointers. If not, we + * don't need to trace them. + */ + const unsigned MinElementsLength = 32; + if (nobj->getDenseInitializedLength() < MinElementsLength || nobj->isSingleton()) + return true; + + ObjectGroup* group = nobj->group(); + if (group->needsSweep() || group->unknownProperties()) + return true; + + HeapTypeSet* typeSet = group->maybeGetProperty(JSID_VOID); + if (!typeSet) + return true; + + static const uint32_t flagMask = + TYPE_FLAG_STRING | TYPE_FLAG_SYMBOL | TYPE_FLAG_LAZYARGS | TYPE_FLAG_ANYOBJECT; + bool mayBeMarkable = typeSet->hasAnyFlag(flagMask) || typeSet->getObjectCount() != 0; + +#ifdef DEBUG + if (!mayBeMarkable) { + const Value* elements = nobj->getDenseElementsAllowCopyOnWrite(); + for (unsigned i = 0; i < nobj->getDenseInitializedLength(); i++) + MOZ_ASSERT(!elements[i].isMarkable()); + } +#endif + + return mayBeMarkable; +} + inline void GCMarker::processMarkStackTop(SliceBudget& budget) { @@ -1378,8 +1462,12 @@ GCMarker::processMarkStackTop(SliceBudget& budget) } } + if (!ObjectDenseElementsMayBeMarkable(nobj)) + break; + vp = nobj->getDenseElementsAllowCopyOnWrite(); end = vp + nobj->getDenseInitializedLength(); + if (!nslots) goto scan_value_array; pushValueArray(nobj, vp, end); @@ -1717,7 +1805,7 @@ GCMarker::markDelayedChildren(ArenaHeader* aheader) TenuredCell* t = i.getCell(); if (always || t->isMarked()) { t->markIfUnmarked(); - JS_TraceChildren(this, t, MapAllocToTraceKind(aheader->getAllocKind())); + js::TraceChildren(this, t, MapAllocToTraceKind(aheader->getAllocKind())); } } } else { @@ -1860,8 +1948,8 @@ template void StoreBuffer::MonoTypeBuffer::trace(StoreBuffer*, TenuringTracer&); template void StoreBuffer::MonoTypeBuffer::trace(StoreBuffer*, TenuringTracer&); -} // namespace js } // namespace gc +} // namespace js void js::gc::StoreBuffer::SlotsEdge::trace(TenuringTracer& mover) const @@ -1879,8 +1967,8 @@ js::gc::StoreBuffer::SlotsEdge::trace(TenuringTracer& mover) const int32_t initLen = obj->getDenseInitializedLength(); int32_t clampedStart = Min(start_, initLen); int32_t clampedEnd = Min(start_ + count_, initLen); - mover.traceSlots(static_cast(obj->getDenseElements() + clampedStart)->unsafeGet(), - clampedEnd - clampedStart); + mover.traceSlots(static_cast(obj->getDenseElements() + clampedStart) + ->unsafeUnbarrieredForTracing(), clampedEnd - clampedStart); } else { int32_t start = Min(uint32_t(start_), obj->slotSpan()); int32_t end = Min(uint32_t(start_) + count_, obj->slotSpan()); @@ -2013,8 +2101,11 @@ js::TenuringTracer::traceObject(JSObject* obj) // Note: the contents of copy on write elements pointers are filled in // during parsing and cannot contain nursery pointers. - if (!nobj->hasEmptyElements() && !nobj->denseElementsAreCopyOnWrite()) { - Value* elems = static_cast(nobj->getDenseElements())->unsafeGet(); + if (!nobj->hasEmptyElements() && + !nobj->denseElementsAreCopyOnWrite() && + ObjectDenseElementsMayBeMarkable(nobj)) + { + Value* elems = static_cast(nobj->getDenseElements())->unsafeUnbarrieredForTracing(); traceSlots(elems, elems + nobj->getDenseInitializedLength()); } @@ -2030,9 +2121,9 @@ js::TenuringTracer::traceObjectSlots(NativeObject* nobj, uint32_t start, uint32_ HeapSlot* dynEnd; nobj->getSlotRange(start, length, &fixedStart, &fixedEnd, &dynStart, &dynEnd); if (fixedStart) - traceSlots(fixedStart->unsafeGet(), fixedEnd->unsafeGet()); + traceSlots(fixedStart->unsafeUnbarrieredForTracing(), fixedEnd->unsafeUnbarrieredForTracing()); if (dynStart) - traceSlots(dynStart->unsafeGet(), dynEnd->unsafeGet()); + traceSlots(dynStart->unsafeUnbarrieredForTracing(), dynEnd->unsafeUnbarrieredForTracing()); } void @@ -2242,6 +2333,17 @@ IsMarkedInternal(T* thingp) return rv; } +bool +js::gc::IsAboutToBeFinalizedDuringSweep(TenuredCell& tenured) +{ + MOZ_ASSERT(!IsInsideNursery(&tenured)); + MOZ_ASSERT(!tenured.runtimeFromAnyThread()->isHeapMinorCollecting()); + MOZ_ASSERT(tenured.zoneFromAnyThread()->isGCSweeping()); + if (tenured.arenaHeader()->allocatedDuringIncremental) + return false; + return !tenured.isMarked(); +} + template static bool IsAboutToBeFinalizedInternal(T** thingp) @@ -2264,11 +2366,8 @@ IsAboutToBeFinalizedInternal(T** thingp) Zone* zone = thing->asTenured().zoneFromAnyThread(); if (zone->isGCSweeping()) { - if (thing->asTenured().arenaHeader()->allocatedDuringIncremental) - return false; - return !thing->asTenured().isMarked(); - } - else if (zone->isGCCompacting() && IsForwarded(thing)) { + return IsAboutToBeFinalizedDuringSweep(thing->asTenured()); + } else if (zone->isGCCompacting() && IsForwarded(thing)) { *thingp = Forwarded(thing); return false; } @@ -2305,16 +2404,9 @@ IsMarkedUnbarriered(T* thingp) template bool -IsMarked(BarrieredBase* thingp) +IsMarked(WriteBarrieredBase* thingp) { - return IsMarkedInternal(ConvertToBase(thingp->unsafeGet())); -} - -template -bool -IsMarked(ReadBarriered* thingp) -{ - return IsMarkedInternal(ConvertToBase(thingp->unsafeGet())); + return IsMarkedInternal(ConvertToBase(thingp->unsafeUnbarrieredForTracing())); } template @@ -2326,26 +2418,25 @@ IsAboutToBeFinalizedUnbarriered(T* thingp) template bool -IsAboutToBeFinalized(BarrieredBase* thingp) +IsAboutToBeFinalized(WriteBarrieredBase* thingp) { - return IsAboutToBeFinalizedInternal(ConvertToBase(thingp->unsafeGet())); + return IsAboutToBeFinalizedInternal(ConvertToBase(thingp->unsafeUnbarrieredForTracing())); } template bool -IsAboutToBeFinalized(ReadBarriered* thingp) +IsAboutToBeFinalized(ReadBarrieredBase* thingp) { - return IsAboutToBeFinalizedInternal(ConvertToBase(thingp->unsafeGet())); + return IsAboutToBeFinalizedInternal(ConvertToBase(thingp->unsafeUnbarrieredForTracing())); } // Instantiate a copy of the Tracing templates for each derived type. #define INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS(type) \ template bool IsMarkedUnbarriered(type*); \ - template bool IsMarked(BarrieredBase*); \ - template bool IsMarked(ReadBarriered*); \ + template bool IsMarked(WriteBarrieredBase*); \ template bool IsAboutToBeFinalizedUnbarriered(type*); \ - template bool IsAboutToBeFinalized(BarrieredBase*); \ - template bool IsAboutToBeFinalized(ReadBarriered*); + template bool IsAboutToBeFinalized(WriteBarrieredBase*); \ + template bool IsAboutToBeFinalized(ReadBarrieredBase*); FOR_EACH_GC_POINTER_TYPE(INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS) #undef INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS @@ -2509,40 +2600,51 @@ UnmarkGrayTracer::onChild(const JS::GCCellPtr& thing) do { MOZ_ASSERT(!shape->isMarked(js::gc::GRAY)); - TraceChildren(&childTracer, shape, JS::TraceKind::Shape); + shape->traceChildren(&childTracer); shape = childTracer.previousShape; childTracer.previousShape = nullptr; } while (shape); unmarkedAny |= childTracer.unmarkedAny; } -bool -js::UnmarkGrayCellRecursively(gc::Cell* cell, JS::TraceKind kind) +template +static bool +TypedUnmarkGrayCellRecursively(T* t) { - MOZ_ASSERT(cell); + MOZ_ASSERT(t); - JSRuntime* rt = cell->runtimeFromMainThread(); + JSRuntime* rt = t->runtimeFromMainThread(); MOZ_ASSERT(!rt->isHeapBusy()); bool unmarkedArg = false; - if (cell->isTenured()) { - if (!cell->asTenured().isMarked(GRAY)) + if (t->isTenured()) { + if (!t->asTenured().isMarked(GRAY)) return false; - cell->asTenured().unmark(GRAY); + t->asTenured().unmark(GRAY); unmarkedArg = true; } UnmarkGrayTracer trc(rt); - TraceChildren(&trc, cell, kind); + t->traceChildren(&trc); return unmarkedArg || trc.unmarkedAny; } +struct UnmarkGrayCellRecursivelyFunctor { + template bool operator()(T* t) { return TypedUnmarkGrayCellRecursively(t); } +}; + +bool +js::UnmarkGrayCellRecursively(Cell* cell, JS::TraceKind kind) +{ + return DispatchTraceKindTyped(UnmarkGrayCellRecursivelyFunctor(), cell, kind); +} + bool js::UnmarkGrayShapeRecursively(Shape* shape) { - return js::UnmarkGrayCellRecursively(shape, JS::TraceKind::Shape); + return TypedUnmarkGrayCellRecursively(shape); } JS_FRIEND_API(bool) diff --git a/js/src/gc/Marking.h b/js/src/gc/Marking.h index 0377346310..be698e36fe 100644 --- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -41,9 +41,9 @@ static const size_t NON_INCREMENTAL_MARK_STACK_BASE_CAPACITY = 4096; static const size_t INCREMENTAL_MARK_STACK_BASE_CAPACITY = 32768; /* - * When the native stack is low, the GC does not call JS_TraceChildren to mark + * When the native stack is low, the GC does not call js::TraceChildren to mark * the reachable "children" of the thing. Rather the thing is put aside and - * JS_TraceChildren is called later with more space on the C stack. + * js::TraceChildren is called later with more space on the C stack. * * To implement such delayed marking of the children with minimal overhead for * the normal case of sufficient native stack, the code adds a field per arena. @@ -186,6 +186,9 @@ class GCMarker : public JSTracer template void traverseEdge(S source, T* target); template void traverseEdge(S source, T target); + // Notes a weak graph edge for later sweeping. + template void noteWeakEdge(T* edge); + /* * Care must be taken changing the mark color from gray to black. The cycle * collector depends on the invariant that there are no black to gray edges @@ -388,11 +391,7 @@ IsMarkedUnbarriered(T* thingp); template bool -IsMarked(BarrieredBase* thingp); - -template -bool -IsMarked(ReadBarriered* thingp); +IsMarked(WriteBarrieredBase* thingp); template bool @@ -400,11 +399,14 @@ IsAboutToBeFinalizedUnbarriered(T* thingp); template bool -IsAboutToBeFinalized(BarrieredBase* thingp); +IsAboutToBeFinalized(WriteBarrieredBase* thingp); template bool -IsAboutToBeFinalized(ReadBarriered* thingp); +IsAboutToBeFinalized(ReadBarrieredBase* thingp); + +bool +IsAboutToBeFinalizedDuringSweep(TenuredCell& tenured); inline Cell* ToMarkable(const Value& v) diff --git a/js/src/gc/RootMarking.cpp b/js/src/gc/RootMarking.cpp index e131fdbfff..67b0986323 100644 --- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -117,18 +117,6 @@ AutoGCRooter::trace(JSTracer* trc) return; } - case SCRIPTVECTOR: { - AutoScriptVector::VectorImpl& vector = static_cast(this)->vector; - TraceRootRange(trc, vector.length(), vector.begin(), "js::AutoScriptVector.vector"); - return; - } - - case HASHABLEVALUE: { - AutoHashableValueRooter* rooter = static_cast(this); - rooter->trace(trc); - return; - } - case IONMASM: { static_cast(this)->masm()->trace(trc); return; @@ -185,12 +173,6 @@ AutoGCRooter::traceAllWrappers(JSTracer* trc) } } -void -AutoHashableValueRooter::trace(JSTracer* trc) -{ - TraceRoot(trc, reinterpret_cast(&value), "AutoHashableValueRooter"); -} - void StackShape::trace(JSTracer* trc) { @@ -234,11 +216,12 @@ struct PersistentRootedMarker typedef mozilla::LinkedList List; typedef void (*MarkFunc)(JSTracer* trc, T* ref, const char* name); + template TraceFn = TraceNullableRoot> static void markChain(JSTracer* trc, List& list, const char* name) { for (Element* r = list.getFirst(); r; r = r->getNext()) - TraceNullableRoot(trc, r->address(), name); + TraceFn(trc, r->address(), name); } }; @@ -259,6 +242,12 @@ js::gc::MarkPersistentRootedChainsInLists(RootLists& roots, JSTracer* trc) "PersistentRooted"); PersistentRootedMarker::markChain(trc, roots.getPersistentRootedList(), "PersistentRooted"); + + PersistentRootedMarker::markChain< + js::DispatchWrapper::TraceWrapped>(trc, + reinterpret_cast>&>( + roots.heapRoots_[THING_ROOT_TRACEABLE]), + "PersistentRooted"); } void diff --git a/js/src/gc/Tracer.cpp b/js/src/gc/Tracer.cpp index 68be97b6cd..58b770b953 100644 --- a/js/src/gc/Tracer.cpp +++ b/js/src/gc/Tracer.cpp @@ -184,9 +184,9 @@ JS_CallTenuredObjectTracer(JSTracer* trc, JS::TenuredHeap* objp, cons } JS_PUBLIC_API(void) -JS_TraceChildren(JSTracer* trc, void* thing, JS::TraceKind kind) +JS::TraceChildren(JSTracer* trc, GCCellPtr thing) { - js::TraceChildren(trc, thing, kind); + js::TraceChildren(trc, thing.asCell(), thing.kind()); } struct TraceChildrenFunctor { diff --git a/js/src/gc/Tracer.h b/js/src/gc/Tracer.h index 465b03a420..81da218b9d 100644 --- a/js/src/gc/Tracer.h +++ b/js/src/gc/Tracer.h @@ -54,7 +54,7 @@ namespace js { // effect of tracing the edge depends on the JSTracer being used. template void -TraceEdge(JSTracer* trc, BarrieredBase* thingp, const char* name); +TraceEdge(JSTracer* trc, WriteBarrieredBase* thingp, const char* name); // Trace through a "root" edge. These edges are the initial edges in the object // graph traversal. Root edges are asserted to only be traversed in the initial @@ -76,10 +76,17 @@ template void TraceManuallyBarrieredEdge(JSTracer* trc, T* thingp, const char* name); +// Visits a WeakRef, but does not trace its referents. If *thingp is not marked +// at the end of marking, it is replaced by nullptr. This method records +// thingp, so the edge location must not change after this function is called. +template +void +TraceWeakEdge(JSTracer* trc, WeakRef* thingp, const char* name); + // Trace all edges contained in the given array. template void -TraceRange(JSTracer* trc, size_t len, BarrieredBase* vec, const char* name); +TraceRange(JSTracer* trc, size_t len, WriteBarrieredBase* vec, const char* name); // Trace all root edges in the given array. template @@ -90,7 +97,7 @@ TraceRootRange(JSTracer* trc, size_t len, T* vec, const char* name); // destination thing is not being GC'd, then the edge will not be traced. template void -TraceCrossCompartmentEdge(JSTracer* trc, JSObject* src, BarrieredBase* dst, +TraceCrossCompartmentEdge(JSTracer* trc, JSObject* src, WriteBarrieredBase* dst, const char* name); // As above but with manual barriers. diff --git a/js/src/gc/Verifier.cpp b/js/src/gc/Verifier.cpp index 363c666133..933be35b03 100644 --- a/js/src/gc/Verifier.cpp +++ b/js/src/gc/Verifier.cpp @@ -216,7 +216,7 @@ gc::GCRuntime::startVerifyPreBarriers() VerifyNode* child = MakeNode(trc, e.thing, e.kind); if (child) { trc->curnode = child; - JS_TraceChildren(trc, e.thing, e.kind); + js::TraceChildren(trc, e.thing, e.kind); } if (trc->edgeptr == trc->term) goto oom; @@ -338,7 +338,7 @@ gc::GCRuntime::endVerifyPreBarriers() VerifyNode* node = NextNode(trc->root); while ((char*)node < trc->edgeptr) { cetrc.node = node; - JS_TraceChildren(&cetrc, node->thing, node->kind); + js::TraceChildren(&cetrc, node->thing, node->kind); if (node->count <= MAX_VERIFIER_EDGES) { for (uint32_t i = 0; i < node->count; i++) diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index 11c902926b..7f39d00d4e 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -288,10 +288,15 @@ struct Zone : public JS::shadow::Zone, typedef js::Vector CompartmentVector; CompartmentVector compartments; - // This compartment's gray roots. + // This zone's gray roots. typedef js::Vector GrayRootVector; GrayRootVector gcGrayRoots; + // This zone's weak edges found via graph traversal during marking, + // preserved for re-scanning during sweeping. + using WeakEdges = js::Vector; + WeakEdges gcWeakRefs; + // A set of edges from this zone to other zones. // // This is used during GC while calculating zone groups to record edges that diff --git a/js/src/jit-test/lib/asm.js b/js/src/jit-test/lib/asm.js index a505dc3927..4724912556 100644 --- a/js/src/jit-test/lib/asm.js +++ b/js/src/jit-test/lib/asm.js @@ -77,9 +77,9 @@ function assertAsmTypeFail() // Verify no error is thrown with warnings off Function.apply(null, arguments); - // Turn on warnings-as-errors - var oldOpts = options("werror"); - assertEq(oldOpts.indexOf("werror"), -1); + // Turn on throwing on validation errors + var oldOpts = options("throw_on_asmjs_validation_failure"); + assertEq(oldOpts.indexOf("throw_on_asmjs_validation_failure"), -1); // Verify an error is thrown var caught = false; @@ -94,7 +94,7 @@ function assertAsmTypeFail() throw new Error("Didn't catch the type failure error"); // Turn warnings-as-errors back off - options("werror"); + options("throw_on_asmjs_validation_failure"); } function assertAsmLinkFail(f) diff --git a/js/src/jit-test/lib/oomTest.js b/js/src/jit-test/lib/oomTest.js index efc96405ae..75e6d49219 100644 --- a/js/src/jit-test/lib/oomTest.js +++ b/js/src/jit-test/lib/oomTest.js @@ -1,30 +1,39 @@ // Function to test OOM handling by repeatedly calling a function and failing // successive allocations. -const verbose = false; - -if (!("oomAtAllocation" in this && "resetOOMFailure" in this)) +if (!("oomAtAllocation" in this && "resetOOMFailure" in this && "oomThreadTypes" in this)) quit(); if ("gczeal" in this) gczeal(0); -function oomTest(f) { - var i = 1; - var more; - do { - if (verbose) - print("fail at " + i); - try { - oomAtAllocation(i); - f(); - } catch (e) { - // Ignore exceptions. - } - more = resetOOMFailure(); - i++; - } while(more); +const verbose = ("os" in this) && os.getenv("OOM_VERBOSE"); - if (verbose) - print("finished after " + i); +// Test out of memory handing by calling a function f() while causing successive +// memory allocations to fail. Repeat until f() finishes without reaching the +// failing allocation. +function oomTest(f) { + for (let thread = 1; thread < oomThreadTypes(); thread++) { + if (verbose) + print("testing thread " + thread); + + var i = 1; + var more; + do { + if (verbose) + print("fail at " + i); + try { + oomAtAllocation(i, thread); + f(); + more = resetOOMFailure(); + } catch (e) { + // Ignore exceptions. + more = resetOOMFailure(); + } + i++; + } while(more); + + if (verbose) + print("finished after " + (i - 2) + " failures"); + } } diff --git a/js/src/jit-test/tests/coverage/bug1203695.js b/js/src/jit-test/tests/coverage/bug1203695.js new file mode 100644 index 0000000000..5668e2dbab --- /dev/null +++ b/js/src/jit-test/tests/coverage/bug1203695.js @@ -0,0 +1,14 @@ + +var lfcode = new Array(); +lfcode.push = loadFile; +lfcode.push(")"); +lfcode.push(` +assertThrowsInstanceOf(function () {}, TypeError); +var g = newGlobal(); +`); +getLcovInfo(g); +function loadFile(lfVarx) { + try { + evaluate(lfVarx, { noScriptRval : true, compileAndGo : true }); + } catch (lfVare) {} +} diff --git a/js/src/jit-test/tests/coverage/bug1206247.js b/js/src/jit-test/tests/coverage/bug1206247.js new file mode 100644 index 0000000000..a07f4a8874 --- /dev/null +++ b/js/src/jit-test/tests/coverage/bug1206247.js @@ -0,0 +1,4 @@ +evaluate("", { + fileName: null +}); +getLcovInfo(); diff --git a/js/src/jit-test/tests/gc/bug-1165966.js b/js/src/jit-test/tests/gc/bug-1165966.js index 0cdaabd1fe..62c5b1a372 100644 --- a/js/src/jit-test/tests/gc/bug-1165966.js +++ b/js/src/jit-test/tests/gc/bug-1165966.js @@ -1,4 +1,4 @@ -// |jit-test| --no-ggc; allow-unhandlable-oom; --no-ion +// |jit-test| --no-ion load(libdir + 'oomTest.js'); var g = newGlobal(); oomTest(function() { diff --git a/js/src/jit-test/tests/gc/bug-1171909.js b/js/src/jit-test/tests/gc/bug-1171909.js index 590c41b782..5a210fe051 100644 --- a/js/src/jit-test/tests/gc/bug-1171909.js +++ b/js/src/jit-test/tests/gc/bug-1171909.js @@ -1,3 +1,2 @@ -// |jit-test| --no-ggc; allow-unhandlable-oom load(libdir + 'oomTest.js'); oomTest((function(x) { assertEq(x + y + ex, 25); })); diff --git a/js/src/jit-test/tests/gc/bug-1208994.js b/js/src/jit-test/tests/gc/bug-1208994.js new file mode 100644 index 0000000000..4c69e49879 --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1208994.js @@ -0,0 +1,2 @@ +load(libdir + 'oomTest.js'); +oomTest(() => getBacktrace({args: oomTest[load+1], locals: true, thisprops: true})); diff --git a/js/src/jit-test/tests/gc/oomInDebugger.js b/js/src/jit-test/tests/gc/oomInDebugger.js index f17ed281a5..96f8174bcc 100644 --- a/js/src/jit-test/tests/gc/oomInDebugger.js +++ b/js/src/jit-test/tests/gc/oomInDebugger.js @@ -1,5 +1,3 @@ -// |jit-test| --no-ggc; allow-unhandlable-oom - load(libdir + 'oomTest.js'); var g = newGlobal(); oomTest(() => Debugger(g)); diff --git a/js/src/jit-test/tests/gc/oomInExceptionHandlerBailout.js b/js/src/jit-test/tests/gc/oomInExceptionHandlerBailout.js new file mode 100644 index 0000000000..c00950e77e --- /dev/null +++ b/js/src/jit-test/tests/gc/oomInExceptionHandlerBailout.js @@ -0,0 +1,15 @@ +load(libdir + 'oomTest.js'); + +oomTest(() => { + let x = 0; + try { + for (let i = 0; i < 100; i++) { + if (i == 99) + throw "foo"; + x += i; + } + } catch (e) { + x = 0; + } + return x; +}); diff --git a/js/src/jit-test/tests/gc/oomInFormatStackDump.js b/js/src/jit-test/tests/gc/oomInFormatStackDump.js index 6a64a5076f..c801a91f67 100644 --- a/js/src/jit-test/tests/gc/oomInFormatStackDump.js +++ b/js/src/jit-test/tests/gc/oomInFormatStackDump.js @@ -1,4 +1,2 @@ -// |jit-test| --no-ggc; allow-unhandlable-oom; --no-threads - load(libdir + 'oomTest.js'); oomTest(() => getBacktrace({args: true, locals: true, thisprops: true})); diff --git a/js/src/jit-test/tests/gc/oomInNewGlobal.js b/js/src/jit-test/tests/gc/oomInNewGlobal.js index 64c2054d57..e371c72021 100644 --- a/js/src/jit-test/tests/gc/oomInNewGlobal.js +++ b/js/src/jit-test/tests/gc/oomInNewGlobal.js @@ -1,4 +1,2 @@ -// |jit-test| --no-ggc; allow-unhandlable-oom - load(libdir + 'oomTest.js'); oomTest(newGlobal); diff --git a/js/src/jit-test/tests/gc/oomInParseFunction.js b/js/src/jit-test/tests/gc/oomInParseFunction.js index 672f9dfd4b..070e0e7103 100644 --- a/js/src/jit-test/tests/gc/oomInParseFunction.js +++ b/js/src/jit-test/tests/gc/oomInParseFunction.js @@ -1,4 +1,2 @@ -// |jit-test| --no-ggc; allow-unhandlable-oom - load(libdir + 'oomTest.js'); oomTest(() => eval("function f() {}")); diff --git a/js/src/jit-test/tests/gc/oomInWeakMap.js b/js/src/jit-test/tests/gc/oomInWeakMap.js index df9867fc64..e26fb5d5f3 100644 --- a/js/src/jit-test/tests/gc/oomInWeakMap.js +++ b/js/src/jit-test/tests/gc/oomInWeakMap.js @@ -1,5 +1,3 @@ -// |jit-test| --no-ggc; allow-unhandlable-oom; --no-threads - load(libdir + 'oomTest.js'); oomTest(function () { eval(`var wm = new WeakMap(); diff --git a/js/src/jit-test/tests/heap-analysis/byteSize-of-scripts.js b/js/src/jit-test/tests/heap-analysis/byteSize-of-scripts.js new file mode 100644 index 0000000000..11923b35b3 --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-scripts.js @@ -0,0 +1,46 @@ +// Check JS::ubi::Node::size results for scripts. We don't attempt to check +// exact sizes in this test (deemed to difficult and non-deterministic), just +// some sanity checks. + +function f1() { + return 42; +} + +print("byteSizeOfScript(f1) = " + byteSizeOfScript(f1)); +assertEq(byteSizeOfScript(f1) > 1, true); + +function f2(n) { + var obj = { + x: 1, + y: 2, + z: 3, + }; + + if (i % 2 == 0) { + for (var i = 0; i < n; i++) { + this.x += i; + print(uneval(i)); + obj[i] = i * i; + if (i > 10) { + f2(i / f1()); + } + } + } + + if (i % 3 == 0) { + for (var i = 0; i < n; i++) { + this.x *= i; + print(uneval(i)); + obj[i] = i * i; + if (i > 10) { + f2(i / f1()); + } + } + } + + return this.x; +} + +print("byteSizeOfScript(f2) = " + byteSizeOfScript(f2)); +assertEq(byteSizeOfScript(f2) > 1, true); +assertEq(byteSizeOfScript(f2) > byteSizeOfScript(f1), true); diff --git a/js/src/jit-test/tests/heap-analysis/byteSize-of-symbol.js b/js/src/jit-test/tests/heap-analysis/byteSize-of-symbol.js new file mode 100644 index 0000000000..4a98bd424c --- /dev/null +++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-symbol.js @@ -0,0 +1,25 @@ +// Check JS::ubi::Node::size results for symbols. + +// We actually hard-code specific sizes into this test, even though they're +// implementation details, because in practice there are only two architecture +// variants to consider (32-bit and 64-bit), and if these sizes change, that's +// something SpiderMonkey hackers really want to know; they're supposed to be +// stable. + +// Run this test only if we're using jemalloc. Other malloc implementations +// exhibit surprising behaviors. For example, 32-bit Fedora builds have +// non-deterministic allocation sizes. +var config = getBuildConfiguration(); +if (!config['moz-memory']) + quit(0); + +const SIZE_OF_SYMBOL = config['pointer-byte-size'] == 4 ? 16 : 24; + +// Without a description. +assertEq(byteSize(Symbol()), SIZE_OF_SYMBOL); + +// With a description. +assertEq(byteSize(Symbol("This is a relatively long description to be passed to " + + "Symbol() but it doesn't matter because it just gets " + + "interned as a JSAtom* anyways.")), + SIZE_OF_SYMBOL); diff --git a/js/src/jit-test/tests/heap-analysis/findPath.js b/js/src/jit-test/tests/heap-analysis/findPath.js index e63c99b2b6..1660fd5aac 100644 --- a/js/src/jit-test/tests/heap-analysis/findPath.js +++ b/js/src/jit-test/tests/heap-analysis/findPath.js @@ -1,7 +1,7 @@ load(libdir + "match.js") // At the moment, findPath just returns the names as provided by ubi::Node, -// which just uses JS_TraceChildren for now. However, we have various plans +// which just uses js::TraceChildren for now. However, we have various plans // to improve the quality of ubi::Node's metadata, to improve the precision // and clarity of the results here. diff --git a/js/src/jit/Bailouts.cpp b/js/src/jit/Bailouts.cpp index 3281056333..cf20ddebd5 100644 --- a/js/src/jit/Bailouts.cpp +++ b/js/src/jit/Bailouts.cpp @@ -206,8 +206,18 @@ jit::ExceptionHandlerBailout(JSContext* cx, const InlineFrameIterator& frame, CommonFrameLayout* currentFramePtr = iter.current(); BaselineBailoutInfo* bailoutInfo = nullptr; - uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), iter, true, - &bailoutInfo, &excInfo); + uint32_t retval; + + { + // Currently we do not tolerate OOM here so as not to complicate the + // exception handling code further. + AutoEnterOOMUnsafeRegion oomUnsafe; + + retval = BailoutIonToBaseline(cx, bailoutData.activation(), iter, true, + &bailoutInfo, &excInfo); + if (retval == BAILOUT_RETURN_FATAL_ERROR && cx->isThrowingOutOfMemory()) + oomUnsafe.crash("ExceptionHandlerBailout"); + } if (retval == BAILOUT_RETURN_OK) { MOZ_ASSERT(bailoutInfo); @@ -235,8 +245,6 @@ jit::ExceptionHandlerBailout(JSContext* cx, const InlineFrameIterator& frame, // Crash for now so as not to complicate the exception handling code // further. - if (cx->isThrowingOutOfMemory()) - CrashAtUnhandlableOOM("ExceptionHandlerBailout"); MOZ_CRASH(); } } diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 59728470fd..0df506b3bd 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -11142,7 +11142,7 @@ ICGetElem_NativePrototypeCallNative::Clone(JSContext* cx, ICGetElem_NativePrototypeCallNative& other) { return ICStub::New>(cx, space, other.jitCode(), - firstMonitorStub, other.receiverGuard(), other.key().unsafeGet(), other.accessType(), + firstMonitorStub, other.receiverGuard(), &other.key().get(), other.accessType(), other.needsAtomize(), other.getter(), other.pcOffset_, other.holder(), other.holderShape()); } @@ -11162,7 +11162,7 @@ ICGetElem_NativePrototypeCallScripted::Clone(JSContext* cx, ICGetElem_NativePrototypeCallScripted& other) { return ICStub::New>(cx, space, other.jitCode(), - firstMonitorStub, other.receiverGuard(), other.key().unsafeGet(), other.accessType(), + firstMonitorStub, other.receiverGuard(), &other.key().get(), other.accessType(), other.needsAtomize(), other.getter(), other.pcOffset_, other.holder(), other.holderShape()); } diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 8369637e1f..c00b772dcb 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -437,15 +437,16 @@ typedef Vector OnIonCompilationVector; // Debugger::onIonCompilation should be called. static inline void PrepareForDebuggerOnIonCompilationHook(JSContext* cx, jit::MIRGraph& graph, - AutoScriptVector* scripts, OnIonCompilationInfo* info) + MutableHandle scripts, + OnIonCompilationInfo* info) { info->numBlocks = 0; if (!Debugger::observesIonCompilation(cx)) return; // fireOnIonCompilation failures are ignored, do the same here. - info->scriptIndex = scripts->length(); - if (!scripts->reserve(graph.numBlocks() + scripts->length())) { + info->scriptIndex = scripts.length(); + if (!scripts.reserve(graph.numBlocks() + scripts.length())) { cx->clearPendingException(); return; } @@ -453,7 +454,7 @@ PrepareForDebuggerOnIonCompilationHook(JSContext* cx, jit::MIRGraph& graph, // Collect the list of scripts which are inlined in the MIRGraph. info->numBlocks = graph.numBlocks(); for (jit::MBasicBlockIterator block(graph.begin()); block != graph.end(); block++) - scripts->infallibleAppend(block->info().script()); + scripts.infallibleAppend(block->info().script()); // Spew the JSON graph made for the Debugger at the end of the LifoAlloc // used by the compiler. This would not prevent unexpected GC from the @@ -462,7 +463,7 @@ PrepareForDebuggerOnIonCompilationHook(JSContext* cx, jit::MIRGraph& graph, jit::JSONSpewer spewer(info->graph); spewer.spewDebuggerGraph(&graph); if (info->graph.hadOutOfMemory()) { - scripts->resize(info->scriptIndex); + scripts.resize(info->scriptIndex); info->numBlocks = 0; } } @@ -541,7 +542,7 @@ class MOZ_RAII AutoLazyLinkExitFrame static bool LinkCodeGen(JSContext* cx, IonBuilder* builder, CodeGenerator *codegen, - AutoScriptVector* scripts, OnIonCompilationInfo* info) + MutableHandle scripts, OnIonCompilationInfo* info) { RootedScript script(cx, builder->script()); TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); @@ -558,7 +559,7 @@ LinkCodeGen(JSContext* cx, IonBuilder* builder, CodeGenerator *codegen, static bool LinkBackgroundCodeGen(JSContext* cx, IonBuilder* builder, - AutoScriptVector* scripts, OnIonCompilationInfo* info) + MutableHandle scripts, OnIonCompilationInfo* info) { CodeGenerator* codegen = builder->backgroundCodegen(); if (!codegen) @@ -592,7 +593,7 @@ jit::LazyLink(JSContext* cx, HandleScript calleeScript) } // See PrepareForDebuggerOnIonCompilationHook - AutoScriptVector debugScripts(cx); + Rooted debugScripts(cx, ScriptVector(cx)); OnIonCompilationInfo info(builder->alloc().lifoAlloc()); { @@ -2225,7 +2226,7 @@ IonCompile(JSContext* cx, JSScript* script, } // See PrepareForDebuggerOnIonCompilationHook - AutoScriptVector debugScripts(cx); + Rooted debugScripts(cx, ScriptVector(cx)); OnIonCompilationInfo debugInfo(alloc); ScopedJSDeletePtr codegen; diff --git a/js/src/jit/JitAllocPolicy.h b/js/src/jit/JitAllocPolicy.h index 7bb5b76b71..8e59dc233f 100644 --- a/js/src/jit/JitAllocPolicy.h +++ b/js/src/jit/JitAllocPolicy.h @@ -79,21 +79,21 @@ class JitAllocPolicy : alloc_(alloc) {} template - T* pod_malloc(size_t numElems) { + T* maybe_pod_malloc(size_t numElems) { size_t bytes; if (MOZ_UNLIKELY(!CalculateAllocSize(numElems, &bytes))) return nullptr; return static_cast(alloc_.allocate(bytes)); } template - T* pod_calloc(size_t numElems) { + T* maybe_pod_calloc(size_t numElems) { T* p = pod_malloc(numElems); if (MOZ_LIKELY(p)) memset(p, 0, numElems * sizeof(T)); return p; } template - T* pod_realloc(T* p, size_t oldSize, size_t newSize) { + T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) { T* n = pod_malloc(newSize); if (MOZ_UNLIKELY(!n)) return n; @@ -101,6 +101,18 @@ class JitAllocPolicy memcpy(n, p, Min(oldSize * sizeof(T), newSize * sizeof(T))); return n; } + template + T* pod_malloc(size_t numElems) { + return maybe_pod_malloc(numElems); + } + template + T* pod_calloc(size_t numElems) { + return maybe_pod_calloc(numElems); + } + template + T* pod_realloc(T* ptr, size_t oldSize, size_t newSize) { + return maybe_pod_realloc(ptr, oldSize, newSize); + } void free_(void* p) { } void reportAllocOverflow() const { @@ -116,12 +128,16 @@ class OldJitAllocPolicy OldJitAllocPolicy() {} template - T* pod_malloc(size_t numElems) { + T* maybe_pod_malloc(size_t numElems) { size_t bytes; if (MOZ_UNLIKELY(!CalculateAllocSize(numElems, &bytes))) return nullptr; return static_cast(GetJitContext()->temp->allocate(bytes)); } + template + T* pod_malloc(size_t numElems) { + return maybe_pod_malloc(numElems); + } void free_(void* p) { } void reportAllocOverflow() const { diff --git a/js/src/jit/JitCompartment.h b/js/src/jit/JitCompartment.h index 4b9c3ab64b..7953901a95 100644 --- a/js/src/jit/JitCompartment.h +++ b/js/src/jit/JitCompartment.h @@ -406,9 +406,13 @@ class JitCompartment return p->value(); return nullptr; } - bool putStubCode(uint32_t key, Handle stubCode) { + bool putStubCode(JSContext* cx, uint32_t key, Handle stubCode) { MOZ_ASSERT(stubCode); - return stubCodes_->putNew(key, stubCode.get()); + if (!stubCodes_->putNew(key, stubCode.get())) { + ReportOutOfMemory(cx); + return false; + } + return true; } void initBaselineCallReturnAddr(void* addr, bool constructing) { MOZ_ASSERT(baselineCallReturnAddrs_[constructing] == nullptr); diff --git a/js/src/jit/SharedIC.cpp b/js/src/jit/SharedIC.cpp index f0d9459742..ef88a01609 100644 --- a/js/src/jit/SharedIC.cpp +++ b/js/src/jit/SharedIC.cpp @@ -728,7 +728,7 @@ ICStubCompiler::getStubCode() newStubCode->togglePreBarriers(true); // Cache newly compiled stubcode. - if (!comp->putStubCode(stubKey, newStubCode)) + if (!comp->putStubCode(cx, stubKey, newStubCode)) return nullptr; MOZ_ASSERT(entersStubFrame_ == ICStub::CanMakeCalls(kind)); diff --git a/js/src/jit/Snapshots.cpp b/js/src/jit/Snapshots.cpp index 5eeec89725..a7fd3d579c 100644 --- a/js/src/jit/Snapshots.cpp +++ b/js/src/jit/Snapshots.cpp @@ -661,8 +661,10 @@ SnapshotWriter::add(const RValueAllocation& alloc) if (!p) { offset = allocWriter_.length(); alloc.write(allocWriter_); - if (!allocMap_.add(p, alloc, offset)) + if (!allocMap_.add(p, alloc, offset)) { + allocWriter_.setOOM(); return false; + } } else { offset = p->value(); } diff --git a/js/src/jit/shared/CodeGenerator-shared.cpp b/js/src/jit/shared/CodeGenerator-shared.cpp index cda187ae32..e10fd86dbd 100644 --- a/js/src/jit/shared/CodeGenerator-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-shared.cpp @@ -588,7 +588,7 @@ CodeGeneratorShared::encode(LSnapshot* snapshot) for (LRecoverInfo::OperandIter it(recoverInfo); !it; ++it) { DebugOnly allocWritten = snapshots_.allocWritten(); encodeAllocation(snapshot, *it, &allocIndex); - MOZ_ASSERT(allocWritten + 1 == snapshots_.allocWritten()); + MOZ_ASSERT_IF(!snapshots_.oom(), allocWritten + 1 == snapshots_.allocWritten()); } MOZ_ASSERT(allocIndex == snapshot->numSlots()); diff --git a/js/src/jsalloc.h b/js/src/jsalloc.h index 8032df04e0..6d9bf42949 100644 --- a/js/src/jsalloc.h +++ b/js/src/jsalloc.h @@ -31,11 +31,16 @@ struct ContextFriendFields; class SystemAllocPolicy { public: - template T* pod_malloc(size_t numElems) { return js_pod_malloc(numElems); } - template T* pod_calloc(size_t numElems) { return js_pod_calloc(numElems); } - template T* pod_realloc(T* p, size_t oldSize, size_t newSize) { + template T* maybe_pod_malloc(size_t numElems) { return js_pod_malloc(numElems); } + template T* maybe_pod_calloc(size_t numElems) { return js_pod_calloc(numElems); } + template T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) { return js_pod_realloc(p, oldSize, newSize); } + template T* pod_malloc(size_t numElems) { return maybe_pod_malloc(numElems); } + template T* pod_calloc(size_t numElems) { return maybe_pod_calloc(numElems); } + template T* pod_realloc(T* p, size_t oldSize, size_t newSize) { + return maybe_pod_realloc(p, oldSize, newSize); + } void free_(void* p) { js_free(p); } void reportAllocOverflow() const {} bool checkSimulatedOOM() const { @@ -78,9 +83,24 @@ class TempAllocPolicy MOZ_IMPLICIT TempAllocPolicy(JSContext* cx) : cx_((ContextFriendFields*) cx) {} // :( MOZ_IMPLICIT TempAllocPolicy(ContextFriendFields* cx) : cx_(cx) {} + template + T* maybe_pod_malloc(size_t numElems) { + return js_pod_malloc(numElems); + } + + template + T* maybe_pod_calloc(size_t numElems) { + return js_pod_calloc(numElems); + } + + template + T* maybe_pod_realloc(T* prior, size_t oldSize, size_t newSize) { + return js_pod_realloc(prior, oldSize, newSize); + } + template T* pod_malloc(size_t numElems) { - T* p = js_pod_malloc(numElems); + T* p = maybe_pod_malloc(numElems); if (MOZ_UNLIKELY(!p)) p = onOutOfMemoryTyped(AllocFunction::Malloc, numElems); return p; @@ -88,7 +108,7 @@ class TempAllocPolicy template T* pod_calloc(size_t numElems) { - T* p = js_pod_calloc(numElems); + T* p = maybe_pod_calloc(numElems); if (MOZ_UNLIKELY(!p)) p = onOutOfMemoryTyped(AllocFunction::Calloc, numElems); return p; @@ -96,7 +116,7 @@ class TempAllocPolicy template T* pod_realloc(T* prior, size_t oldSize, size_t newSize) { - T* p2 = js_pod_realloc(prior, oldSize, newSize); + T* p2 = maybe_pod_realloc(prior, oldSize, newSize); if (MOZ_UNLIKELY(!p2)) p2 = onOutOfMemoryTyped(AllocFunction::Realloc, newSize); return p2; diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build index d88adb7889..9ad38d24dd 100644 --- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -44,6 +44,7 @@ UNIFIED_SOURCES += [ 'testGCOutOfMemory.cpp', 'testGCStoreBufferRemoval.cpp', 'testGCUniqueId.cpp', + 'testGCWeakRef.cpp', 'testGetPropertyDescriptor.cpp', 'testHashTable.cpp', 'testIndexToString.cpp', diff --git a/js/src/jsapi-tests/testCloneScript.cpp b/js/src/jsapi-tests/testCloneScript.cpp index 7ac06ae142..1bef44906e 100644 --- a/js/src/jsapi-tests/testCloneScript.cpp +++ b/js/src/jsapi-tests/testCloneScript.cpp @@ -51,19 +51,18 @@ BEGIN_TEST(test_cloneScript) } END_TEST(test_cloneScript) -static void -DestroyPrincipals(JSPrincipals* principals) -{ - delete principals; -} - -struct Principals : public JSPrincipals +struct Principals final : public JSPrincipals { public: Principals() { refcount = 0; } + + bool write(JSContext* cx, JSStructuredCloneWriter* writer) override { + MOZ_ASSERT(false, "not imlemented"); + return false; + } }; class AutoDropPrincipals @@ -84,6 +83,13 @@ class AutoDropPrincipals } }; +static void +DestroyPrincipals(JSPrincipals* principals) +{ + auto p = static_cast(principals); + delete p; +} + BEGIN_TEST(test_cloneScriptWithPrincipals) { JS_InitDestroyPrincipalsCallback(rt, DestroyPrincipals); diff --git a/js/src/jsapi-tests/testGCExactRooting.cpp b/js/src/jsapi-tests/testGCExactRooting.cpp index 9e8833a998..db51c301e8 100644 --- a/js/src/jsapi-tests/testGCExactRooting.cpp +++ b/js/src/jsapi-tests/testGCExactRooting.cpp @@ -61,6 +61,15 @@ struct RootedBase { RelocatablePtrObject& obj() { return static_cast*>(this)->get().obj; } RelocatablePtrString& str() { return static_cast*>(this)->get().str; } }; +template <> +struct PersistentRootedBase { + RelocatablePtrObject& obj() { + return static_cast*>(this)->get().obj; + } + RelocatablePtrString& str() { + return static_cast*>(this)->get().str; + } +}; } // namespace js BEGIN_TEST(testGCRootedStaticStructInternalStackStorageAugmented) @@ -75,6 +84,40 @@ BEGIN_TEST(testGCRootedStaticStructInternalStackStorageAugmented) JS::RootedObject obj(cx, container.obj()); JS::RootedValue val(cx, StringValue(container.str())); CHECK(JS_SetProperty(cx, obj, "foo", val)); + obj = nullptr; + val = UndefinedValue(); + + { + JS::RootedString actual(cx); + bool same; + + // Automatic move from stack to heap. + JS::PersistentRooted heap(cx, container); + + // clear prior rooting. + container.obj() = nullptr; + container.str() = nullptr; + + obj = heap.obj(); + CHECK(JS_GetProperty(cx, obj, "foo", &val)); + actual = val.toString(); + CHECK(JS_StringEqualsAscii(cx, actual, "Hello", &same)); + CHECK(same); + obj = nullptr; + actual = nullptr; + + JS_GC(cx->runtime()); + JS_GC(cx->runtime()); + + obj = heap.obj(); + CHECK(JS_GetProperty(cx, obj, "foo", &val)); + actual = val.toString(); + CHECK(JS_StringEqualsAscii(cx, actual, "Hello", &same)); + CHECK(same); + obj = nullptr; + actual = nullptr; + } + return true; } END_TEST(testGCRootedStaticStructInternalStackStorageAugmented) diff --git a/js/src/jsapi-tests/testGCWeakRef.cpp b/js/src/jsapi-tests/testGCWeakRef.cpp new file mode 100644 index 0000000000..ab1b17e0a7 --- /dev/null +++ b/js/src/jsapi-tests/testGCWeakRef.cpp @@ -0,0 +1,63 @@ +/* -*- 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 "gc/Barrier.h" +#include "js/RootingAPI.h" + +struct MyHeap : JS::Traceable +{ + explicit MyHeap(JSObject* obj) : weak(obj) {} + js::WeakRef weak; + + static void trace(MyHeap* self, JSTracer* trc) { + js::TraceWeakEdge(trc, &self->weak, "weak"); + } +}; + +BEGIN_TEST(testGCWeakRef) +{ + // Create an object and add a property to it so that we can read the + // property back later to verify that object internals are not garbage. + JS::RootedObject obj(cx, JS_NewPlainObject(cx)); + CHECK(obj); + CHECK(JS_DefineProperty(cx, obj, "x", 42, 0)); + + // Store the object behind a weak pointer and remove other references. + Rooted heap(cx, MyHeap(obj)); + obj = nullptr; + + rt->gc.minorGC(JS::gcreason::API); + + // The minor collection should have treated the weak ref as a strong ref, + // so the object should still be live, despite not having any other live + // references. + CHECK(heap.get().weak.unbarrieredGet() != nullptr); + obj = heap.get().weak; + RootedValue v(cx); + CHECK(JS_GetProperty(cx, obj, "x", &v)); + CHECK(v.isInt32()); + CHECK(v.toInt32() == 42); + + // A full collection with a second ref should keep the object as well. + CHECK(obj == heap.get().weak); + JS_GC(rt); + CHECK(obj == heap.get().weak); + v = UndefinedValue(); + CHECK(JS_GetProperty(cx, obj, "x", &v)); + CHECK(v.isInt32()); + CHECK(v.toInt32() == 42); + + // A full collection after nulling the root should collect the object, or + // at least null out the weak reference before returning to the mutator. + obj = nullptr; + JS_GC(rt); + CHECK(heap.get().weak == nullptr); + + return true; +} +END_TEST(testGCWeakRef) + diff --git a/js/src/jsapi-tests/testMappedArrayBuffer.cpp b/js/src/jsapi-tests/testMappedArrayBuffer.cpp index fe598ca10a..f45b28b009 100644 --- a/js/src/jsapi-tests/testMappedArrayBuffer.cpp +++ b/js/src/jsapi-tests/testMappedArrayBuffer.cpp @@ -126,10 +126,9 @@ bool TestCloneObject() CHECK(obj1); JSAutoStructuredCloneBuffer cloned_buffer; JS::RootedValue v1(cx, JS::ObjectValue(*obj1)); - const JSStructuredCloneCallbacks* callbacks = js::GetContextStructuredCloneCallbacks(cx); - CHECK(cloned_buffer.write(cx, v1, callbacks, nullptr)); + CHECK(cloned_buffer.write(cx, v1, nullptr, nullptr)); JS::RootedValue v2(cx); - CHECK(cloned_buffer.read(cx, &v2, callbacks, nullptr)); + CHECK(cloned_buffer.read(cx, &v2, nullptr, nullptr)); JS::RootedObject obj2(cx, v2.toObjectOrNull()); CHECK(VerifyObject(obj2, 8, 12, false)); @@ -162,10 +161,9 @@ bool TestTransferObject() JS::RootedValue transferable(cx, JS::ObjectValue(*obj)); JSAutoStructuredCloneBuffer cloned_buffer; - const JSStructuredCloneCallbacks* callbacks = js::GetContextStructuredCloneCallbacks(cx); - CHECK(cloned_buffer.write(cx, v1, transferable, callbacks, nullptr)); + CHECK(cloned_buffer.write(cx, v1, transferable, nullptr, nullptr)); JS::RootedValue v2(cx); - CHECK(cloned_buffer.read(cx, &v2, callbacks, nullptr)); + CHECK(cloned_buffer.read(cx, &v2, nullptr, nullptr)); JS::RootedObject obj2(cx, v2.toObjectOrNull()); CHECK(VerifyObject(obj2, 8, 12, true)); CHECK(isNeutered(obj1)); diff --git a/js/src/jsapi-tests/testUbiNode.cpp b/js/src/jsapi-tests/testUbiNode.cpp index 8d3c8aba97..5c66f23bb3 100644 --- a/js/src/jsapi-tests/testUbiNode.cpp +++ b/js/src/jsapi-tests/testUbiNode.cpp @@ -4,6 +4,7 @@ #include "builtin/TestingFunctions.h" #include "js/UbiNode.h" +#include "js/UbiNodePostOrder.h" #include "jsapi-tests/tests.h" #include "vm/SavedFrame.h" @@ -12,6 +13,46 @@ using JS::RootedScript; using JS::RootedString; using namespace js; +// A helper JS::ubi::Node concrete implementation that can be used to make mock +// graphs for testing traversals with. +struct FakeNode +{ + char name; + JS::ubi::EdgeVector edges; + + explicit FakeNode(char name) : name(name), edges() { } + + bool addEdgeTo(FakeNode& referent) { + JS::ubi::Node node(&referent); + return edges.emplaceBack(nullptr, node); + } +}; + +namespace JS { +namespace ubi { + +template<> +struct Concrete : public Base +{ + static const char16_t concreteTypeName[]; + const char16_t* typeName() const { return concreteTypeName; } + + UniquePtr edges(JSRuntime* rt, bool wantNames) const { + return UniquePtr(js_new(get().edges)); + } + + static void construct(void* storage, FakeNode* ptr) { new (storage) Concrete(ptr); } + + protected: + explicit Concrete(FakeNode* ptr) : Base(ptr) { } + FakeNode& get() const { return *static_cast(ptr); } +}; + +const char16_t Concrete::concreteTypeName[] = MOZ_UTF16("FakeNode"); + +} // namespace ubi +} // namespace JS + // ubi::Node::zone works BEGIN_TEST(test_ubiNodeZone) { @@ -224,3 +265,84 @@ BEGIN_TEST(test_ubiCoarseType) return true; } END_TEST(test_ubiCoarseType) + +BEGIN_TEST(test_ubiPostOrder) +{ + // Construct the following graph: + // + // .-----. + // | | + // .-------| r |---------------. + // | | | | + // | '-----' | + // | | + // .--V--. .--V--. + // | | | | + // .------| a |------. .----| e |----. + // | | | | | | | | + // | '--^--' | | '-----' | + // | | | | | + // .--V--. | .--V--. .--V--. .--V--. + // | | | | | | | | | + // | b | '------| c |-----> f |---------> g | + // | | | | | | | | + // '-----' '-----' '-----' '-----' + // | | + // | .-----. | + // | | | | + // '------> d <------' + // | | + // '-----' + // + + FakeNode r('r'); + FakeNode a('a'); + FakeNode b('b'); + FakeNode c('c'); + FakeNode d('d'); + FakeNode e('e'); + FakeNode f('f'); + FakeNode g('g'); + + r.addEdgeTo(a); + r.addEdgeTo(e); + a.addEdgeTo(b); + a.addEdgeTo(c); + b.addEdgeTo(d); + c.addEdgeTo(a); + c.addEdgeTo(d); + c.addEdgeTo(f); + e.addEdgeTo(f); + e.addEdgeTo(g); + f.addEdgeTo(g); + + js::Vector visited; + { + // Do a PostOrder traversal, starting from r. Accumulate the names of + // the nodes we visit in `visited`. + JS::AutoCheckCannotGC nogc(rt); + JS::ubi::PostOrder traversal(rt, nogc); + CHECK(traversal.init()); + CHECK(traversal.addStart(&r)); + CHECK(traversal.traverse([&](const JS::ubi::Node& node) { + return visited.append(node.as()->name); + })); + } + + fprintf(stderr, "visited.length() = %lu\n", (unsigned long) visited.length()); + for (size_t i = 0; i < visited.length(); i++) + fprintf(stderr, "visited[%lu] = '%c'\n", (unsigned long) i, visited[i]); + + CHECK(visited.length() == 8); + CHECK(visited[0] == 'g'); + CHECK(visited[1] == 'f'); + CHECK(visited[2] == 'e'); + CHECK(visited[3] == 'd'); + CHECK(visited[4] == 'c'); + CHECK(visited[5] == 'b'); + CHECK(visited[6] == 'a'); + CHECK(visited[7] == 'r'); + + return true; +} +END_TEST(test_ubiPostOrder) diff --git a/js/src/jsapi-tests/tests.h b/js/src/jsapi-tests/tests.h index cbc0531f32..e14b345a41 100644 --- a/js/src/jsapi-tests/tests.h +++ b/js/src/jsapi-tests/tests.h @@ -406,6 +406,11 @@ class TestJSPrincipals : public JSPrincipals { refcount = rc; } + + bool write(JSContext* cx, JSStructuredCloneWriter* writer) override { + MOZ_ASSERT(false, "not implemented"); + return false; + } }; #ifdef JS_GC_ZEAL diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index a0f8c7e8d7..27c32ed303 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -588,6 +588,12 @@ JS_Init(void) if (!TlsPerThreadData.initialized() && !TlsPerThreadData.init()) return false; +#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) + if (!js::oom::InitThreadType()) + return false; + js::oom::SetThreadType(js::oom::THREAD_TYPE_MAIN); +#endif + jit::ExecutableAllocator::initStatic(); if (!jit::InitializeIon()) @@ -3343,6 +3349,14 @@ JS_InitDestroyPrincipalsCallback(JSRuntime* rt, JSDestroyPrincipalsOp destroyPri rt->destroyPrincipals = destroyPrincipals; } +extern JS_PUBLIC_API(void) +JS_InitReadPrincipalsCallback(JSRuntime* rt, JSReadPrincipalsOp read) +{ + MOZ_ASSERT(read); + MOZ_ASSERT(!rt->readPrincipals); + rt->readPrincipals = read; +} + JS_PUBLIC_API(JSFunction*) JS_NewFunction(JSContext* cx, JSNative native, unsigned nargs, unsigned flags, const char* name) @@ -3900,6 +3914,7 @@ JS::TransitiveCompileOptions::copyPODTransitiveOptions(const TransitiveCompileOp extraWarningsOption = rhs.extraWarningsOption; werrorOption = rhs.werrorOption; asmJSOption = rhs.asmJSOption; + throwOnAsmJSValidationFailureOption = rhs.throwOnAsmJSValidationFailureOption; forceAsync = rhs.forceAsync; installedFile = rhs.installedFile; sourceIsLazy = rhs.sourceIsLazy; @@ -4022,6 +4037,7 @@ JS::CompileOptions::CompileOptions(JSContext* cx, JSVersion version) extraWarningsOption = cx->compartment()->options().extraWarnings(cx); werrorOption = cx->runtime()->options().werror(); asmJSOption = cx->runtime()->options().asmJS(); + throwOnAsmJSValidationFailureOption = cx->runtime()->options().throwOnAsmJSValidationFailure(); } enum SyntacticScopeOption { HasSyntacticScope, HasNonSyntacticScope }; diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 48fc232316..d151eb1bc0 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -219,7 +219,6 @@ class MOZ_RAII AutoVectorRooter : public AutoVectorRooterBase typedef AutoVectorRooter AutoValueVector; typedef AutoVectorRooter AutoIdVector; typedef AutoVectorRooter AutoObjectVector; -typedef AutoVectorRooter AutoScriptVector; using ValueVector = js::TraceableVector; using IdVector = js::TraceableVector; @@ -1144,6 +1143,7 @@ class JS_PUBLIC_API(RuntimeOptions) { : baseline_(true), ion_(true), asmJS_(true), + throwOnAsmJSValidationFailure_(false), nativeRegExp_(true), unboxedArrays_(false), asyncStack_(true), @@ -1183,6 +1183,16 @@ class JS_PUBLIC_API(RuntimeOptions) { return *this; } + bool throwOnAsmJSValidationFailure() const { return throwOnAsmJSValidationFailure_; } + RuntimeOptions& setThrowOnAsmJSValidationFailure(bool flag) { + throwOnAsmJSValidationFailure_ = flag; + return *this; + } + RuntimeOptions& toggleThrowOnAsmJSValidationFailure() { + throwOnAsmJSValidationFailure_ = !throwOnAsmJSValidationFailure_; + return *this; + } + bool nativeRegExp() const { return nativeRegExp_; } RuntimeOptions& setNativeRegExp(bool flag) { nativeRegExp_ = flag; @@ -1235,6 +1245,7 @@ class JS_PUBLIC_API(RuntimeOptions) { bool baseline_ : 1; bool ion_ : 1; bool asmJS_ : 1; + bool throwOnAsmJSValidationFailure_ : 1; bool nativeRegExp_ : 1; bool unboxedArrays_ : 1; bool asyncStack_ : 1; @@ -3368,6 +3379,7 @@ class JS_FRIEND_API(TransitiveCompileOptions) extraWarningsOption(false), werrorOption(false), asmJSOption(false), + throwOnAsmJSValidationFailureOption(false), forceAsync(false), installedFile(false), sourceIsLazy(false), @@ -3402,6 +3414,7 @@ class JS_FRIEND_API(TransitiveCompileOptions) bool extraWarningsOption; bool werrorOption; bool asmJSOption; + bool throwOnAsmJSValidationFailureOption; bool forceAsync; bool installedFile; // 'true' iff pre-compiling js file in packaged app bool sourceIsLazy; diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index eab5fd9804..0cf8e06aa6 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -498,7 +498,12 @@ js::ToAtom(ExclusiveContext* cx, typename MaybeRooted::HandleTyp if (str->isAtom()) return &str->asAtom(); - return AtomizeString(cx, str); + JSAtom* atom = AtomizeString(cx, str); + if (!atom && !allowGC) { + MOZ_ASSERT_IF(cx->isJSContext(), cx->asJSContext()->isThrowingOutOfMemory()); + cx->recoverFromOutOfMemory(); + } + return atom; } template JSAtom* diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index d31f4d66e6..964b505a20 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -196,6 +196,7 @@ class ExclusiveContext : public ContextFriendFields, bool canUseSignalHandlers() const { return runtime_->canUseSignalHandlers(); } bool jitSupportsFloatingPoint() const { return runtime_->jitSupportsFloatingPoint; } bool jitSupportsSimd() const { return runtime_->jitSupportsSimd; } + bool lcovEnabled() const { return runtime_->lcovOutput.isEnabled(); } // Thread local data that may be accessed freely. DtoaState* dtoaState() { diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 34e328e2b4..9143431ddf 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -79,7 +79,8 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options = maybeAlive(true), jitCompartment_(nullptr), mappedArgumentsTemplate_(nullptr), - unmappedArgumentsTemplate_(nullptr) + unmappedArgumentsTemplate_(nullptr), + lcovOutput() { runtime_->numCompartments++; MOZ_ASSERT_IF(options.mergeable(), options.invisibleToDebugger()); @@ -87,6 +88,11 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options = JSCompartment::~JSCompartment() { + // Write the code coverage information in a file. + JSRuntime* rt = runtimeFromMainThread(); + if (rt->lcovOutput.isEnabled()) + rt->lcovOutput.writeLCovResult(lcovOutput); + js_delete(jitCompartment_); js_delete(watchpointMap); js_delete(scriptCountsMap); @@ -258,17 +264,21 @@ JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped, con MOZ_ASSERT(wrapped.wrapped); MOZ_ASSERT_IF(wrapped.kind == CrossCompartmentKey::StringWrapper, wrapper.isString()); MOZ_ASSERT_IF(wrapped.kind != CrossCompartmentKey::StringWrapper, wrapper.isObject()); - bool success = crossCompartmentWrappers.put(wrapped, ReadBarriered(wrapper)); /* There's no point allocating wrappers in the nursery since we will tenure them anyway. */ MOZ_ASSERT(!IsInsideNursery(static_cast(wrapper.toGCThing()))); - if (success && (IsInsideNursery(wrapped.wrapped) || IsInsideNursery(wrapped.debugger))) { + if (!crossCompartmentWrappers.put(wrapped, ReadBarriered(wrapper))) { + ReportOutOfMemory(cx); + return false; + } + + if (IsInsideNursery(wrapped.wrapped) || IsInsideNursery(wrapped.debugger)) { WrapperMapRef ref(&crossCompartmentWrappers, wrapped); cx->runtime()->gc.storeBuffer.putGeneric(ref); } - return success; + return true; } static JSString* @@ -514,7 +524,7 @@ JSCompartment::traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime t { if (objectMetadataState.is()) { TraceRoot(trc, - objectMetadataState.as().unsafeGet(), + objectMetadataState.as().unsafeUnbarrieredForTracing(), "on-stack object pending metadata"); } @@ -528,7 +538,7 @@ JSCompartment::traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime t // If a compartment is on-stack, we mark its global so that // JSContext::global() remains valid. if (enterCompartmentDepth && global_.unbarrieredGet()) - TraceRoot(trc, global_.unsafeGet(), "on-stack compartment global"); + TraceRoot(trc, global_.unsafeUnbarrieredForTracing(), "on-stack compartment global"); } // Nothing below here needs to be treated as a root if we aren't marking @@ -552,7 +562,23 @@ JSCompartment::traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime t if (objectMetadataTable) objectMetadataTable->trace(trc); - if (scriptCountsMap && !trc->runtime()->isHeapMinorCollecting()) { + // If code coverage is only enabled with the Debugger or the LCovOutput, + // then the following comment holds. + // + // The scriptCountsMap maps JSScript weak-pointers to ScriptCounts + // structures. It uses a HashMap instead of a WeakMap, so that we can keep + // the data alive for the JSScript::finalize call. Thus, we do not trace the + // keys of the HashMap to avoid adding a strong reference to the JSScript + // pointers. Additionally, we assert that the JSScripts have not been moved + // in JSCompartment::fixupAfterMovingGC. + // + // If the code coverage is either enabled with the --dump-bytecode command + // line option, or with the PCCount JSFriend API functions, then we mark the + // keys of the map to hold the JSScript alive. + if (scriptCountsMap && + trc->runtime()->profilingScripts && + !trc->runtime()->isHeapMinorCollecting()) + { MOZ_ASSERT_IF(!trc->runtime()->isBeingDestroyed(), collectCoverage()); for (ScriptCountsMap::Range r = scriptCountsMap->all(); !r.empty(); r.popFront()) { JSScript* script = const_cast(r.front().key()); @@ -732,6 +758,18 @@ JSCompartment::fixupAfterMovingGC() fixupGlobal(); fixupInitialShapeTable(); objectGroups.fixupTablesAfterMovingGC(); + +#ifdef DEBUG + // Assert that none of the JSScript pointers, which are used as key of the + // scriptCountsMap HashMap are moved. We do not mark these keys because we + // need weak references. We do not use a WeakMap because these entries would + // be collected before the JSScript::finalize calls which is used to + // summarized the content of the code coverage. + if (scriptCountsMap) { + for (ScriptCountsMap::Range r = scriptCountsMap->all(); !r.empty(); r.popFront()) + MOZ_ASSERT(!IsForwarded(r.front().key())); + } +#endif } void @@ -956,9 +994,8 @@ JSCompartment::updateDebuggerObservesCoverage() return; } - // If the runtime flag is enabled, then keep the data until - // StopPCCountProfiling is called. - if (runtimeFromMainThread()->profilingScripts) + // If code coverage is enabled by any other means, keep it. + if (collectCoverage()) return; clearScriptCounts(); diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index e2370aeb8b..a77d8a3b84 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -199,7 +199,7 @@ class MOZ_RAII AutoSetNewObjectMetadata : private JS::CustomAutoRooter virtual void trace(JSTracer* trc) override { if (prevState_.is()) { TraceRoot(trc, - prevState_.as().unsafeGet(), + prevState_.as().unsafeUnbarrieredForTracing(), "Object pending metadata"); } } @@ -662,7 +662,8 @@ struct JSCompartment // Debugger API, or for the entire runtime. bool collectCoverage() const { return debuggerObservesCoverage() || - runtimeFromAnyThread()->profilingScripts; + runtimeFromAnyThread()->profilingScripts || + runtimeFromAnyThread()->lcovOutput.isEnabled(); } void clearScriptCounts(); @@ -737,6 +738,11 @@ struct JSCompartment }; js::ArgumentsObject* getOrCreateArgumentsTemplateObject(JSContext* cx, bool mapped); + + public: + // Aggregated output used to collect JSScript hit counts when code coverage + // is enabled. + js::coverage::LCovCompartment lcovOutput; }; inline bool diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index ba665694de..921451d876 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -980,7 +980,7 @@ DumpHeapVisitCell(JSRuntime* rt, void* data, void* thing, char cellDesc[1024 * 32]; JS_GetTraceThingInfo(cellDesc, sizeof(cellDesc), dtrc, thing, traceKind, true); fprintf(dtrc->output, "%p %c %s\n", thing, MarkDescriptor(thing), cellDesc); - JS_TraceChildren(dtrc, thing, traceKind); + js::TraceChildren(dtrc, thing, traceKind); } void @@ -1019,12 +1019,6 @@ js::DumpHeap(JSRuntime* rt, FILE* fp, js::DumpHeapNurseryBehaviour nurseryBehavi fflush(dtrc.output); } -JS_FRIEND_API(const JSStructuredCloneCallbacks*) -js::GetContextStructuredCloneCallbacks(JSContext* cx) -{ - return cx->runtime()->structuredCloneCallbacks; -} - JS_FRIEND_API(bool) js::ContextHasOutstandingRequests(const JSContext* cx) { @@ -1038,12 +1032,6 @@ js::SetActivityCallback(JSRuntime* rt, ActivityCallback cb, void* arg) rt->activityCallbackArg = arg; } -JS_FRIEND_API(bool) -js::IsContextRunningJS(JSContext* cx) -{ - return cx->currentlyRunning(); -} - JS_FRIEND_API(void) JS::NotifyDidPaint(JSRuntime* rt) { diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index b1de1add95..90c24ec32a 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -1055,12 +1055,6 @@ typedef void JS_FRIEND_API(void) SetActivityCallback(JSRuntime* rt, ActivityCallback cb, void* arg); -extern JS_FRIEND_API(const JSStructuredCloneCallbacks*) -GetContextStructuredCloneCallbacks(JSContext* cx); - -extern JS_FRIEND_API(bool) -IsContextRunningJS(JSContext* cx); - typedef bool (* DOMInstanceClassHasProtoAtDepth)(const Class* instanceClass, uint32_t protoID, uint32_t depth); diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 6f40f082bd..28fb5224f0 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -11,6 +11,7 @@ #include "jsfuninlines.h" #include "mozilla/ArrayUtils.h" +#include "mozilla/CheckedInt.h" #include "mozilla/PodOperations.h" #include "mozilla/Range.h" @@ -1509,7 +1510,7 @@ JSFunction::createScriptForLazilyInterpretedFunction(JSContext* cx, HandleFuncti // script together during bytecode compilation. Reset it now on // error. fun->initLazyScript(lazy); - if (lazy->maybeScriptUnbarriered()) + if (lazy->hasScript()) lazy->resetScript(); return false; } @@ -1804,7 +1805,6 @@ static bool FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind generatorKind) { CallArgs args = CallArgsFromVp(argc, vp); - RootedString arg(cx); // used multiple times below /* Block this call if security callbacks forbid it. */ Rooted global(cx, &args.callee().global()); @@ -1813,11 +1813,6 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener return false; } - AutoKeepAtoms keepAtoms(cx->perThreadData); - Rooted formals(cx, PropertyNameVector(cx)); - - bool hasRest = false; - bool isStarGenerator = generatorKind == StarGenerator; MOZ_ASSERT(generatorKind != LegacyGenerator); @@ -1843,63 +1838,59 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener .setNoScriptRval(false) .setIntroductionInfo(introducerFilename, introductionType, lineno, maybeScript, pcOffset); - unsigned n = args.length() ? args.length() - 1 : 0; - if (n > 0) { - /* - * Collect the function-argument arguments into one string, separated - * by commas, then make a tokenstream from that string, and scan it to - * get the arguments. We need to throw the full scanner at the - * problem, because the argument string can legitimately contain - * comments and linefeeds. XXX It might be better to concatenate - * everything up into a function definition and pass it to the - * compiler, but doing it this way is less of a delta from the old - * code. See ECMA 15.3.2.1. - */ - size_t args_length = 0; - for (unsigned i = 0; i < n; i++) { - /* Collect the lengths for all the function-argument arguments. */ - arg = ToString(cx, args[i]); - if (!arg) - return false; - args[i].setString(arg); + Vector paramStr(cx); + RootedString bodyText(cx); - /* - * Check for overflow. The < test works because the maximum - * JSString length fits in 2 fewer bits than size_t has. - */ - size_t old_args_length = args_length; - args_length = old_args_length + arg->length(); - if (args_length < old_args_length) { - ReportAllocationOverflow(cx); + if (args.length() == 0) { + bodyText = cx->names().empty; + } else { + // Collect the function-argument arguments into one string, separated + // by commas, then make a tokenstream from that string, and scan it to + // get the arguments. We need to throw the full scanner at the + // problem because the argument string may contain comments, newlines, + // destructuring arguments, and similar manner of insanities. ("I have + // a feeling we're not in simple-comma-separated-parameters land any + // more, Toto....") + // + // XXX It'd be better if the parser provided utility methods to parse + // an argument list, and to parse a function body given a parameter + // list. But our parser provides no such pleasant interface now. + unsigned n = args.length() - 1; + + // Convert the parameters-related arguments to strings, and determine + // the length of the string containing the overall parameter list. + mozilla::CheckedInt paramStrLen = 0; + RootedString str(cx); + for (unsigned i = 0; i < n; i++) { + str = ToString(cx, args[i]); + if (!str) return false; - } + + args[i].setString(str); + paramStrLen += str->length(); } - /* Add 1 for each joining comma and check for overflow (two ways). */ - size_t old_args_length = args_length; - args_length = old_args_length + n - 1; - if (args_length < old_args_length || - args_length >= ~(size_t)0 / sizeof(char16_t)) { + // Tack in space for any combining commas. + if (n > 0) + paramStrLen += n - 1; + + // Check for integer and string-size overflow. + if (!paramStrLen.isValid() || paramStrLen.value() > JSString::MAX_LENGTH) { ReportAllocationOverflow(cx); return false; } - /* - * Allocate a string to hold the concatenated arguments, including room - * for a terminating 0. Mark cx->tempLifeAlloc for later release, to - * free collected_args and its tokenstream in one swoop. - */ - LifoAllocScope las(&cx->tempLifoAlloc()); - char16_t* cp = cx->tempLifoAlloc().newArray(args_length + 1); - if (!cp) { + uint32_t paramsLen = paramStrLen.value(); + + // Fill a vector with the comma-joined arguments. Careful! This + // string is *not* null-terminated! + MOZ_ASSERT(paramStr.length() == 0); + if (!paramStr.growBy(paramsLen)) { ReportOutOfMemory(cx); return false; } - ConstTwoByteChars collected_args(cp, args_length + 1); - /* - * Concatenate the arguments into the new string, separated by commas. - */ + char16_t* cp = paramStr.begin(); for (unsigned i = 0; i < n; i++) { JSLinearString* argLinear = args[i].toString()->ensureLinear(cx); if (!argLinear) @@ -1908,28 +1899,71 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener CopyChars(cp, *argLinear); cp += argLinear->length(); - /* Add separating comma or terminating 0. */ - *cp++ = (i + 1 < n) ? ',' : 0; + if (i + 1 < n) + *cp++ = ','; } - /* - * Initialize a tokenstream that reads from the given string. No - * StrictModeGetter is needed because this TokenStream won't report any - * strict mode errors. Any strict mode errors which might be reported - * here (duplicate argument names, etc.) will be detected when we - * compile the function body. - */ - TokenStream ts(cx, options, collected_args.start().get(), args_length, + MOZ_ASSERT(cp == paramStr.end()); + + bodyText = ToString(cx, args[n]); + if (!bodyText) + return false; + } + + /* + * 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) { + proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, global); + if (!proto) + return false; + } + RootedFunction fun(cx, NewFunctionWithProto(cx, nullptr, 0, + JSFunction::INTERPRETED_LAMBDA, global, + anonymousAtom, proto, + AllocKind::FUNCTION, TenuredObject)); + if (!fun) + return false; + + if (!JSFunction::setTypeForScriptedFunction(cx, fun)) + return false; + + AutoStableStringChars stableChars(cx); + if (!stableChars.initTwoByte(cx, bodyText)) + return false; + + bool hasRest = false; + + Rooted formals(cx, PropertyNameVector(cx)); + if (args.length() > 1) { + // Initialize a tokenstream to parse the new function's arguments. No + // StrictModeGetter is needed because this TokenStream won't report any + // strict mode errors. Strict mode errors that might be reported here + // (duplicate argument names, etc.) will be detected when we compile + // the function body. + // + // XXX Bug! We have to parse the body first to determine strictness. + // We have to know strictness to parse arguments correctly, in case + // arguments contains a strict mode violation. And we should be + // using full-fledged arguments parsing here, in order to handle + // destructuring and other exotic syntaxes. + AutoKeepAtoms keepAtoms(cx->perThreadData); + TokenStream ts(cx, options, paramStr.begin(), paramStr.length(), /* strictModeGetter = */ nullptr); bool yieldIsValidName = ts.versionNumber() < JSVERSION_1_7 && !isStarGenerator; - /* The argument string may be empty or contain no tokens. */ + // The argument string may be empty or contain no tokens. TokenKind tt; if (!ts.getToken(&tt)) return false; if (tt != TOK_EOF) { - for (;;) { - /* Check that it's a name. */ + while (true) { + // Check that it's a name. if (hasRest) { ts.reportError(JSMSG_PARAMETER_AFTER_REST); return false; @@ -1957,10 +1991,8 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener if (!formals.append(ts.currentName())) return false; - /* - * Get the next token. Stop on end of stream. Otherwise - * insist on a comma, get another name, and iterate. - */ + // Get the next token. Stop on end of stream. Otherwise + // insist on a comma, get another name, and iterate. if (!ts.getToken(&tt)) return false; if (tt == TOK_EOF) @@ -1973,51 +2005,9 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener } } -#ifdef DEBUG - for (unsigned i = 0; i < formals.length(); ++i) { - JSString* str = formals[i]; - MOZ_ASSERT(str->asAtom().asPropertyName() == formals[i]); - } -#endif - - RootedString str(cx); - if (!args.length()) - str = cx->runtime()->emptyString; - else - str = ToString(cx, args[args.length() - 1]); - if (!str) - return false; - - /* - * 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) { - proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, global); - if (!proto) - return false; - } - RootedFunction fun(cx, NewFunctionWithProto(cx, nullptr, 0, - JSFunction::INTERPRETED_LAMBDA, global, - anonymousAtom, proto, - AllocKind::FUNCTION, TenuredObject)); - if (!fun) - return false; - - if (!JSFunction::setTypeForScriptedFunction(cx, fun)) - return false; - if (hasRest) fun->setHasRest(); - AutoStableStringChars stableChars(cx); - if (!stableChars.initTwoByte(cx, str)) - return false; - mozilla::Range chars = stableChars.twoByteRange(); SourceBufferHolder::Ownership ownership = stableChars.maybeGiveOwnershipToCaller() ? SourceBufferHolder::GiveOwnership diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index cbb36587db..6e96169791 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1175,7 +1175,6 @@ GCRuntime::GCRuntime(JSRuntime* rt) : noGCOrAllocationCheck(0), #endif lock(nullptr), - lockOwner(nullptr), allocTask(rt, emptyChunks_), helperState(rt) { @@ -1381,6 +1380,7 @@ js::gc::FinishPersistentRootedChains(RootLists& roots) FinishPersistentRootedChain(roots.getPersistentRootedList()); FinishPersistentRootedChain(roots.getPersistentRootedList()); FinishPersistentRootedChain(roots.getPersistentRootedList()); + FinishPersistentRootedChain(roots.heapRoots_[THING_ROOT_TRACEABLE]); } void @@ -1874,7 +1874,7 @@ GCRuntime::enableCompactingGC() } bool -GCRuntime::isCompactingGCEnabled() +GCRuntime::isCompactingGCEnabled() const { MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt)); return compactingEnabled && compactingDisabledCount == 0; @@ -3410,11 +3410,11 @@ GCHelperState::waitForBackgroundThread() MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt)); #ifdef DEBUG - rt->gc.lockOwner = nullptr; + rt->gc.lockOwner.value = nullptr; #endif PR_WaitCondVar(done, PR_INTERVAL_NO_TIMEOUT); #ifdef DEBUG - rt->gc.lockOwner = PR_GetCurrentThread(); + rt->gc.lockOwner.value = PR_GetCurrentThread(); #endif } @@ -3813,7 +3813,7 @@ GCRuntime::checkForCompartmentMismatches() trc.srcKind = MapAllocToTraceKind(thingKind); trc.compartment = DispatchTraceKindTyped(MaybeCompartmentFunctor(), trc.src, trc.srcKind); - JS_TraceChildren(&trc, trc.src, trc.srcKind); + js::TraceChildren(&trc, trc.src, trc.srcKind); } } } @@ -5000,6 +5000,16 @@ GCRuntime::beginSweepingZoneGroup() } } + /* Clear all weakrefs that point to unmarked things. */ + for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) { + for (auto edge : zone->gcWeakRefs) { + /* Edges may be present multiple times, so may already be nulled. */ + if (*edge && IsAboutToBeFinalizedDuringSweep(**edge)) + *edge = nullptr; + } + zone->gcWeakRefs.clear(); + } + FreeOp fop(rt); SweepAtomsTask sweepAtomsTask(rt); SweepInnerViewsTask sweepInnerViewsTask(rt); @@ -5628,11 +5638,29 @@ GCRuntime::finishCollection(JS::gcreason::Reason reason) } } +static const char* +HeapStateToLabel(JS::HeapState heapState) +{ + switch (heapState) { + case JS::HeapState::MinorCollecting: + return "js::Nursery::collect"; + case JS::HeapState::MajorCollecting: + return "js::GCRuntime::collect"; + case JS::HeapState::Tracing: + return "JS_IterateCompartments"; + case JS::HeapState::Idle: + MOZ_CRASH("Should never have an Idle heap state when pushing GC pseudo frames!"); + } + MOZ_ASSERT_UNREACHABLE("Should have exhausted every JS::HeapState variant!"); + return nullptr; +} + /* Start a new heap session. */ AutoTraceSession::AutoTraceSession(JSRuntime* rt, JS::HeapState heapState) : lock(rt), runtime(rt), - prevState(rt->heapState_) + prevState(rt->heapState_), + pseudoFrame(rt, HeapStateToLabel(heapState), ProfileEntry::Category::GC) { MOZ_ASSERT(rt->heapState_ == JS::HeapState::Idle); MOZ_ASSERT(heapState != JS::HeapState::Idle); @@ -5858,15 +5886,19 @@ GCRuntime::pushZealSelectedObjects() #endif } +static bool +IsShutdownGC(JS::gcreason::Reason reason) +{ + return reason == JS::gcreason::SHUTDOWN_CC || reason == JS::gcreason::DESTROY_RUNTIME; +} + static bool ShouldCleanUpEverything(JS::gcreason::Reason reason, JSGCInvocationKind gckind) { // During shutdown, we must clean everything up, for the sake of leak // detection. When a runtime has no contexts, or we're doing a GC before a // shutdown CC, those are strong indications that we're shutting down. - return reason == JS::gcreason::DESTROY_RUNTIME || - reason == JS::gcreason::SHUTDOWN_CC || - gckind == GC_SHRINK; + return IsShutdownGC(reason) || gckind == GC_SHRINK; } void @@ -6087,6 +6119,35 @@ class AutoDisableStoreBuffer } }; +class AutoScheduleZonesForGC +{ + JSRuntime* rt_; + + public: + explicit AutoScheduleZonesForGC(JSRuntime* rt) : rt_(rt) { + for (ZonesIter zone(rt_, WithAtoms); !zone.done(); zone.next()) { + if (rt->gc.gcMode() == JSGC_MODE_GLOBAL) + zone->scheduleGC(); + + /* This is a heuristic to avoid resets. */ + if (rt->gc.isIncrementalGCInProgress() && zone->needsIncrementalBarrier()) + zone->scheduleGC(); + + /* This is a heuristic to reduce the total number of collections. */ + if (zone->usage.gcBytes() >= + zone->threshold.allocTrigger(rt->gc.schedulingState.inHighFrequencyGCMode())) + { + zone->scheduleGC(); + } + } + } + + ~AutoScheduleZonesForGC() { + for (ZonesIter zone(rt_, WithAtoms); !zone.done(); zone.next()) + zone->unscheduleGC(); + } +}; + } /* anonymous namespace */ /* @@ -6099,7 +6160,7 @@ class AutoDisableStoreBuffer * to run another cycle. */ MOZ_NEVER_INLINE bool -GCRuntime::gcCycle(bool incremental, SliceBudget& budget, JS::gcreason::Reason reason) +GCRuntime::gcCycle(bool nonincrementalByAPI, SliceBudget& budget, JS::gcreason::Reason reason) { evictNursery(reason); @@ -6142,7 +6203,7 @@ GCRuntime::gcCycle(bool incremental, SliceBudget& budget, JS::gcreason::Reason r State prevState = incrementalState; - if (!incremental) { + if (nonincrementalByAPI) { // Reset any in progress incremental GC if this was triggered via the // API. This isn't required for correctness, but sometimes during tests // the caller expects this GC to collect certain objects, and we need @@ -6175,11 +6236,9 @@ GCRuntime::gcCycle(bool incremental, SliceBudget& budget, JS::gcreason::Reason r clearSelectedForMarking(); #endif - /* Clear gcMallocBytes for all compartments */ - for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) { + /* Clear gcMallocBytes for all zones. */ + for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) zone->resetGCMallocBytes(); - zone->unscheduleGC(); - } resetMallocBytes(); @@ -6210,17 +6269,6 @@ GCRuntime::scanZonesBeforeGC() { gcstats::ZoneGCStats zoneStats; for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) { - if (mode == JSGC_MODE_GLOBAL) - zone->scheduleGC(); - - /* This is a heuristic to avoid resets. */ - if (isIncrementalGCInProgress() && zone->needsIncrementalBarrier()) - zone->scheduleGC(); - - /* This is a heuristic to reduce the total number of collections. */ - if (zone->usage.gcBytes() >= zone->threshold.allocTrigger(schedulingState.inHighFrequencyGCMode())) - zone->scheduleGC(); - zoneStats.zoneCount++; if (zone->isGCScheduled()) { zoneStats.collectedZoneCount++; @@ -6235,7 +6283,7 @@ GCRuntime::scanZonesBeforeGC() } void -GCRuntime::collect(bool incremental, SliceBudget budget, JS::gcreason::Reason reason) +GCRuntime::checkCanCallAPI() { JS_AbortIfWrongThread(rt); @@ -6246,21 +6294,36 @@ GCRuntime::collect(bool incremental, SliceBudget budget, JS::gcreason::Reason re MOZ_ASSERT(!rt->currentThreadHasExclusiveAccess()); MOZ_ASSERT(isAllocAllowed()); +} +bool +GCRuntime::checkIfGCAllowedInCurrentState(JS::gcreason::Reason reason) +{ if (rt->mainThread.suppressGC) - return; - - TraceLoggerThread* logger = TraceLoggerForMainThread(rt); - AutoTraceLog logGC(logger, TraceLogger_GC); + return false; #ifdef JS_GC_ZEAL if (deterministicOnly && !IsDeterministicGCReason(reason)) - return; + return false; #endif - AutoStopVerifyingBarriers av(rt, reason == JS::gcreason::SHUTDOWN_CC || - reason == JS::gcreason::DESTROY_RUNTIME); + return true; +} +void +GCRuntime::collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::Reason reason) +{ + // Checks run for each request, even if we do not actually GC. + checkCanCallAPI(); + + // Check if we are allowed to GC at this time before proceeding. + if (!checkIfGCAllowedInCurrentState(reason)) + return; + + AutoTraceLog logGC(TraceLoggerForMainThread(rt), TraceLogger_GC); + AutoStopVerifyingBarriers av(rt, IsShutdownGC(reason)); + AutoEnqueuePendingParseTasksAfterGC aept(*this); + AutoScheduleZonesForGC asz(rt); gcstats::AutoGCSlice agc(stats, scanZonesBeforeGC(), invocationKind, budget, reason); bool repeat = false; @@ -6276,7 +6339,7 @@ GCRuntime::collect(bool incremental, SliceBudget budget, JS::gcreason::Reason re } poked = false; - bool wasReset = gcCycle(incremental, budget, reason); + bool wasReset = gcCycle(nonincrementalByAPI, budget, reason); if (!isIncrementalGCInProgress()) { gcstats::AutoPhase ap(stats, gcstats::PHASE_GC_END); @@ -6294,10 +6357,10 @@ GCRuntime::collect(bool incremental, SliceBudget budget, JS::gcreason::Reason re * beginMarkPhase. */ bool repeatForDeadZone = false; - if (incremental && !isIncrementalGCInProgress()) { + if (!nonincrementalByAPI && !isIncrementalGCInProgress()) { for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { if (c->scheduledForDestruction) { - incremental = false; + nonincrementalByAPI = true; repeatForDeadZone = true; reason = JS::gcreason::COMPARTMENT_REVIVED; c->zone()->scheduleGC(); @@ -6313,9 +6376,12 @@ GCRuntime::collect(bool incremental, SliceBudget budget, JS::gcreason::Reason re */ repeat = (poked && cleanUpEverything) || wasReset || repeatForDeadZone; } while (repeat); +} - if (!isIncrementalGCInProgress()) - EnqueuePendingParseTasksAfterGC(rt); +js::AutoEnqueuePendingParseTasksAfterGC::~AutoEnqueuePendingParseTasksAfterGC() +{ + if (!gc_.isIncrementalGCInProgress()) + EnqueuePendingParseTasksAfterGC(gc_.rt); } SliceBudget @@ -6337,7 +6403,7 @@ void GCRuntime::gc(JSGCInvocationKind gckind, JS::gcreason::Reason reason) { invocationKind = gckind; - collect(false, SliceBudget::unlimited(), reason); + collect(true, SliceBudget::unlimited(), reason); } void @@ -6345,14 +6411,14 @@ GCRuntime::startGC(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64 { MOZ_ASSERT(!isIncrementalGCInProgress()); invocationKind = gckind; - collect(true, defaultBudget(reason, millis), reason); + collect(false, defaultBudget(reason, millis), reason); } void GCRuntime::gcSlice(JS::gcreason::Reason reason, int64_t millis) { MOZ_ASSERT(isIncrementalGCInProgress()); - collect(true, defaultBudget(reason, millis), reason); + collect(false, defaultBudget(reason, millis), reason); } void @@ -6372,16 +6438,13 @@ GCRuntime::finishGC(JS::gcreason::Reason reason) isCompacting = false; } - collect(true, SliceBudget::unlimited(), reason); + collect(false, SliceBudget::unlimited(), reason); } void GCRuntime::abortGC() { - JS_AbortIfWrongThread(rt); - - MOZ_RELEASE_ASSERT(!rt->isHeapBusy()); - MOZ_ASSERT(!rt->currentThreadHasExclusiveAccess()); + checkCanCallAPI(); MOZ_ASSERT(!rt->mainThread.suppressGC); AutoStopVerifyingBarriers av(rt, false); @@ -6440,7 +6503,7 @@ GCRuntime::startDebugGC(JSGCInvocationKind gckind, SliceBudget& budget) if (!ZonesSelected(rt)) JS::PrepareForFullGC(rt); invocationKind = gckind; - collect(true, budget, JS::gcreason::DEBUG_GC); + collect(false, budget, JS::gcreason::DEBUG_GC); } void @@ -6449,7 +6512,7 @@ GCRuntime::debugGCSlice(SliceBudget& budget) MOZ_ASSERT(isIncrementalGCInProgress()); if (!ZonesSelected(rt)) JS::PrepareForIncrementalGC(rt); - collect(true, budget, JS::gcreason::DEBUG_GC); + collect(false, budget, JS::gcreason::DEBUG_GC); } /* Schedule a full GC unless a zone will already be collected. */ @@ -6768,7 +6831,7 @@ GCRuntime::runDebugGC() if (!isIncrementalGCInProgress()) invocationKind = GC_SHRINK; - collect(true, budget, JS::gcreason::DEBUG_GC); + collect(false, budget, JS::gcreason::DEBUG_GC); /* * For multi-slice zeal, reset the slice size when we get to the sweep diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 40c3cb7249..1aa0371c31 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -51,6 +51,47 @@ enum State { COMPACT }; +// Expand the given macro D for each valid GC reference type. +#define FOR_EACH_GC_POINTER_TYPE(D) \ + D(AccessorShape*) \ + D(BaseShape*) \ + D(UnownedBaseShape*) \ + D(jit::JitCode*) \ + D(NativeObject*) \ + D(ArrayObject*) \ + D(ArgumentsObject*) \ + D(ArrayBufferObject*) \ + D(ArrayBufferObjectMaybeShared*) \ + D(ArrayBufferViewObject*) \ + D(DebugScopeObject*) \ + D(GlobalObject*) \ + D(JSObject*) \ + D(JSFunction*) \ + D(ModuleObject*) \ + D(ModuleEnvironmentObject*) \ + D(NestedScopeObject*) \ + D(PlainObject*) \ + D(SavedFrame*) \ + D(ScopeObject*) \ + D(ScriptSourceObject*) \ + D(SharedArrayBufferObject*) \ + D(SharedTypedArrayObject*) \ + D(ImportEntryObject*) \ + D(ExportEntryObject*) \ + D(JSScript*) \ + D(LazyScript*) \ + D(Shape*) \ + D(JSAtom*) \ + D(JSString*) \ + D(JSFlatString*) \ + D(JSLinearString*) \ + D(PropertyName*) \ + D(JS::Symbol*) \ + D(js::ObjectGroup*) \ + D(Value) \ + D(jsid) \ + D(TaggedProto) + /* Map from C++ type to alloc kind. JSObject does not have a 1:1 mapping, so must use Arena::thingSize. */ template struct MapTypeToFinalizeKind {}; template <> struct MapTypeToFinalizeKind { static const AllocKind kind = AllocKind::SCRIPT; }; diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index cead38531c..33d3b1b001 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -3707,7 +3707,8 @@ JSObject::traceChildren(JSTracer* trc) JS::AutoTracingDetails ctx(trc, func); JS::AutoTracingIndex index(trc); for (uint32_t i = 0; i < nobj->slotSpan(); ++i) { - TraceManuallyBarrieredEdge(trc, nobj->getSlotRef(i).unsafeGet(), "object slot"); + TraceManuallyBarrieredEdge(trc, nobj->getSlotRef(i).unsafeUnbarrieredForTracing(), + "object slot"); ++index; } } diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index d0a3483d3a..e1b07beb8c 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -37,6 +37,7 @@ #include "frontend/BytecodeCompiler.h" #include "frontend/SourceNotes.h" #include "js/CharacterEncoding.h" +#include "vm/CodeCoverage.h" #include "vm/Opcodes.h" #include "vm/ScopeObject.h" #include "vm/Shape.h" @@ -193,7 +194,7 @@ js::DumpPCCounts(JSContext* cx, HandleScript script, Sprinter* sp) } void -js::DumpCompartmentPCCounts(JSContext *cx) +js::DumpCompartmentPCCounts(JSContext* cx) { for (ZoneCellIter i(cx->zone(), gc::AllocKind::SCRIPT); !i.done(); i.next()) { RootedScript script(cx, i.get()); @@ -214,12 +215,12 @@ js::DumpCompartmentPCCounts(JSContext *cx) for (auto thingKind : ObjectAllocKinds()) { for (ZoneCellIter i(cx->zone(), thingKind); !i.done(); i.next()) { - JSObject *obj = i.get(); + JSObject* obj = i.get(); if (obj->compartment() != cx->compartment()) continue; if (obj->is()) { - AsmJSModule &module = obj->as().module(); + AsmJSModule& module = obj->as().module(); Sprinter sprinter(cx); if (!sprinter.init()) @@ -268,9 +269,9 @@ class BytecodeParser bool captureOffsetStack(LifoAlloc& alloc, const uint32_t* stack, uint32_t depth) { stackDepth = depth; offsetStack = alloc.newArray(stackDepth); + if (!offsetStack) + return false; if (stackDepth) { - if (!offsetStack) - return false; for (uint32_t n = 0; n < stackDepth; n++) offsetStack[n] = stack[n]; } @@ -709,7 +710,7 @@ js::Disassemble(JSContext* cx, HandleScript script, bool lines, Sprinter* sp) } JS_FRIEND_API(bool) -js::DumpPC(JSContext *cx) +js::DumpPC(JSContext* cx) { gc::AutoSuppressGC suppressGC(cx); Sprinter sprinter(cx); @@ -727,7 +728,7 @@ js::DumpPC(JSContext *cx) } JS_FRIEND_API(bool) -js::DumpScript(JSContext *cx, JSScript *scriptArg) +js::DumpScript(JSContext* cx, JSScript* scriptArg) { gc::AutoSuppressGC suppressGC(cx); Sprinter sprinter(cx); @@ -1965,175 +1966,22 @@ js::GetPCCountScriptContents(JSContext* cx, size_t index) return buf.finishString(); } -static bool -LcovWriteScriptName(GenericPrinter& out, JSScript* script) -{ - JSFunction* fun = script->functionNonDelazifying(); - if (fun && fun->displayAtom()) - return EscapedStringPrinter(out, fun->displayAtom(), 0); - out.printf("top-level"); - return true; -} - -struct LcovSourceFile -{ - const char* filename; - - LSprinter outFN; - LSprinter outFNDA; - size_t numFunctionsFound; - size_t numFunctionsHit; - - LSprinter outBRDA; - size_t numBranchesFound; - size_t numBranchesHit; - - LSprinter outDA; - size_t numLinesInstrumented; - size_t numLinesHit; - - LcovSourceFile(LifoAlloc* alloc, JSScript *script) - : filename(script->filename()), - outFN(alloc), - outFNDA(alloc), - numFunctionsFound(0), - numFunctionsHit(0), - outBRDA(alloc), - numBranchesFound(0), - numBranchesHit(0), - outDA(alloc), - numLinesInstrumented(0), - numLinesHit(0) - { } -}; - -static bool -LcovWriteScript(JSContext* cx, LcovSourceFile& lsf, JSScript* script) -{ - lsf.numFunctionsFound++; - lsf.outFN.printf("FN:%d,", script->lineno()); - if (!LcovWriteScriptName(lsf.outFN, script)) - return false; - lsf.outFN.put("\n", 1); - - uint64_t hits = 0; - ScriptCounts* sc = nullptr; - if (script->hasScriptCounts()) { - sc = &script->getScriptCounts(); - lsf.numFunctionsHit++; - const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(script->main())); - lsf.outFNDA.printf("FNDA:%" PRIu64 ",", counts->numExec()); - if (!LcovWriteScriptName(lsf.outFNDA, script)) - return false; - lsf.outFNDA.put("\n", 1); - - // Set the hit count of the pre-main code to 1, if the function ever got - // visited. - hits = 1; - } - - jsbytecode* snpc = script->code(); - jssrcnote* sn = script->notes(); - if (!SN_IS_TERMINATOR(sn)) - snpc += SN_DELTA(sn); - - size_t lineno = script->lineno(); - jsbytecode* end = script->codeEnd(); - size_t blockId = 0; - for (jsbytecode* pc = script->code(); pc != end; pc = GetNextPc(pc)) { - JSOp op = JSOp(*pc); - bool jump = IsJumpOpcode(op); - bool fallsthrough = BytecodeFallsThrough(op); - - // If the current script & pc has a hit-count report, then update the - // current number of hits. - if (sc) { - const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(pc)); - if (counts) - hits = counts->numExec(); - } - - // If we have additional source notes, walk all the source notes of the - // current pc. - if (snpc <= pc) { - size_t oldLine = lineno; - while (!SN_IS_TERMINATOR(sn) && snpc <= pc) { - SrcNoteType type = (SrcNoteType) SN_TYPE(sn); - if (type == SRC_SETLINE) - lineno = size_t(GetSrcNoteOffset(sn, 0)); - else if (type == SRC_NEWLINE) - lineno++; - - sn = SN_NEXT(sn); - snpc += SN_DELTA(sn); - } - - if (oldLine != lineno && fallsthrough) { - lsf.outDA.printf("DA:%d,%" PRIu64 "\n", lineno, hits); - - // Count the number of lines instrumented & hit. - lsf.numLinesInstrumented++; - if (hits) - lsf.numLinesHit++; - } - } - - // If the current instruction has thrown, then decrement the hit counts - // with the number of throws. - if (sc) { - const PCCounts* counts = sc->maybeGetThrowCounts(script->pcToOffset(pc)); - if (counts) - hits -= counts->numExec(); - } - - // If the current pc corresponds to a conditional jump instruction, then reports - // branch hits. - if (jump && fallsthrough) { - jsbytecode* target = pc + GET_JUMP_OFFSET(pc); - jsbytecode* fallthroughTarget = GetNextPc(pc); - uint64_t fallthroughHits = 0; - if (sc) { - const PCCounts* counts = sc->maybeGetPCCounts(script->pcToOffset(fallthroughTarget)); - if (counts) - fallthroughHits = counts->numExec(); - } - - size_t targetId = script->pcToOffset(target); - uint64_t taken = hits - fallthroughHits; - lsf.outBRDA.printf("BRDA:%d,%d,%d,", lineno, blockId, targetId); - if (hits) - lsf.outBRDA.printf("%d\n", taken); - else - lsf.outBRDA.put("-\n", 2); - - // Count the number of branches, and the number of branches hit. - lsf.numBranchesFound++; - if (hits) - lsf.numBranchesHit++; - - // Update the blockId when there is a discontinuity. - blockId = script->pcToOffset(fallthroughTarget); - } - } - - return true; -} - static bool GenerateLcovInfo(JSContext* cx, JSCompartment* comp, GenericPrinter& out) { JSRuntime* rt = cx->runtime(); // Collect the list of scripts which are part of the current compartment. - AutoScriptVector topScripts(cx); + Rooted topScripts(cx, ScriptVector(cx)); for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) { for (ZoneCellIter i(zone, AllocKind::SCRIPT); !i.done(); i.next()) { JSScript* script = i.get(); - if (script->compartment() != comp) - continue; - - if (script->functionNonDelazifying()) + if (script->compartment() != comp || + !script->isTopLevel() || + !script->filename()) + { continue; + } if (!topScripts.append(script)) return false; @@ -2146,48 +1994,18 @@ GenerateLcovInfo(JSContext* cx, JSCompartment* comp, GenericPrinter& out) // Sort the information to avoid generating multiple file entries, and to // generate functions in the right order. auto lessFun = [](const JSScript* lhs, const JSScript* rhs) -> bool { - int d = strcmp(lhs->filename(), rhs->filename()); - /* - This should not be necessary as we are supposed to have only the - top-level script. - - d = (d != 0) ? d : lhs->lineno() - rhs->lineno(); - d = (d != 0) ? d : lhs->column() - rhs->column(); - */ - return d < 0; + return strcmp(lhs->filename(), rhs->filename()) < 0; }; std::sort(topScripts.begin(), topScripts.end(), lessFun); - // lcov trace files are starting with an optional test case name, that we - // recycle to be a compartment name. - out.put("TN:"); - if (rt->compartmentNameCallback) { - char name[1024]; - (*rt->compartmentNameCallback)(rt, comp, name, sizeof(name)); - for (char *s = name; s < name + sizeof(name) && *s; s++) { - if (('a' <= *s && *s <= 'z') || - ('A' <= *s && *s <= 'Z') || - ('0' <= *s && *s <= '9')) - { - out.put(s, 1); - continue; - } - out.printf("_%p", (void*) size_t(*s)); - } - out.put("\n", 1); - } else { - out.printf("Compartment_%p%p\n", (void*) size_t('_'), comp); - } - - // For each source file - LifoAlloc printerAlloc(4096); + // Collect code coverage info for one compartment. + coverage::LCovCompartment compCover; for (JSScript* topLevel: topScripts) { - LifoAllocScope printerScope(&printerAlloc); - LcovSourceFile lsf(&printerAlloc, topLevel); + RootedScript topScript(cx, topLevel); // We found the top-level script, visit all the functions reachable - // from the top-level function. - AutoScriptVector queue(cx); + // from the top-level function, and delazify them. + Rooted queue(cx, ScriptVector(cx)); if (!queue.append(topLevel)) return false; @@ -2195,10 +2013,6 @@ GenerateLcovInfo(JSContext* cx, JSCompartment* comp, GenericPrinter& out) do { script = queue.popCopy(); - // Code the current script before pushing. - if (!LcovWriteScript(cx, lsf, script)) - return false; - // Iterate from the last to the first object in order to have // the functions them visited in the opposite order when popping // elements from the stack of remaining scripts, such that the @@ -2225,33 +2039,11 @@ GenerateLcovInfo(JSContext* cx, JSCompartment* comp, GenericPrinter& out) } } while (!queue.empty()); - if (lsf.outFN.hadOutOfMemory() || - lsf.outFNDA.hadOutOfMemory() || - lsf.outBRDA.hadOutOfMemory() || - lsf.outDA.hadOutOfMemory()) - { - out.reportOutOfMemory(); - return false; - } - - out.printf("SF:%s\n", lsf.filename); - - lsf.outFN.exportInto(out); - lsf.outFNDA.exportInto(out); - out.printf("FNF:%d\n", lsf.numFunctionsFound); - out.printf("FNH:%d\n", lsf.numFunctionsHit); - - lsf.outBRDA.exportInto(out); - out.printf("BRF:%d\n", lsf.numBranchesFound); - out.printf("BRH:%d\n", lsf.numBranchesHit); - - lsf.outDA.exportInto(out); - out.printf("LF:%d\n", lsf.numLinesInstrumented); - out.printf("LH:%d\n", lsf.numLinesHit); - - out.put("end_of_record\n"); + compCover.collectSourceFile(comp, &topScript->scriptSourceUnwrap()); + compCover.collectCodeCoverageInfo(comp, topScript->sourceObject(), topScript); } + compCover.exportInto(out); if (out.hadOutOfMemory()) return false; return true; diff --git a/js/src/jspubtd.h b/js/src/jspubtd.h index 7b9e2e2bfd..15b27fee12 100644 --- a/js/src/jspubtd.h +++ b/js/src/jspubtd.h @@ -221,8 +221,6 @@ class JS_PUBLIC_API(AutoGCRooter) VALVECTOR = -10, /* js::AutoValueVector */ IDVECTOR = -11, /* js::AutoIdVector */ OBJVECTOR = -14, /* js::AutoObjectVector */ - SCRIPTVECTOR =-16, /* js::AutoScriptVector */ - HASHABLEVALUE=-18, /* js::HashableValue */ IONMASM = -19, /* js::jit::MacroAssembler */ WRAPVECTOR = -20, /* js::AutoWrapperVector */ WRAPPER = -21, /* js::AutoWrapperRooter */ @@ -232,7 +230,6 @@ class JS_PUBLIC_API(AutoGCRooter) static ptrdiff_t GetTag(const Value& value) { return VALVECTOR; } static ptrdiff_t GetTag(const jsid& id) { return IDVECTOR; } static ptrdiff_t GetTag(JSObject* obj) { return OBJVECTOR; } - static ptrdiff_t GetTag(JSScript* script) { return SCRIPTVECTOR; } private: AutoGCRooter ** const stackTop; diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index d2054bb178..9d3bc4a6d1 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1337,7 +1337,7 @@ JSScript::initScriptCounts(JSContext* cx) return false; for (size_t i = 0; i < jumpTargets.length(); i++) - MOZ_ALWAYS_TRUE(base.emplaceBack(pcToOffset(jumpTargets[i]))); + base.infallibleEmplaceBack(pcToOffset(jumpTargets[i])); // Create compartment's scriptCountsMap if necessary. ScriptCountsMap* map = compartment()->scriptCountsMap; @@ -1510,6 +1510,12 @@ void ScriptSourceObject::finalize(FreeOp* fop, JSObject* obj) { ScriptSourceObject* sso = &obj->as(); + + // If code coverage is enabled, record the filename associated with this + // source object. + if (fop->runtime()->lcovOutput.isEnabled()) + sso->compartment()->lcovOutput.collectSourceFile(sso->compartment(), sso); + sso->source()->decref(); sso->setReservedSlot(SOURCE_SLOT, PrivateValue(nullptr)); } @@ -2939,6 +2945,11 @@ JSScript::finalize(FreeOp* fop) // JSScript::Create(), but not yet finished initializing it with // fullyInitFromEmitter() or fullyInitTrivial(). + // Collect code coverage information for this script and all its inner + // scripts, and store the aggregated information on the compartment. + if (isTopLevel() && fop->runtime()->lcovOutput.isEnabled()) + compartment()->lcovOutput.collectCodeCoverageInfo(compartment(), sourceObject(), this); + fop->runtime()->spsProfiler.onScriptFinalized(this); if (types_) @@ -2956,14 +2967,12 @@ JSScript::finalize(FreeOp* fop) fop->runtime()->lazyScriptCache.remove(this); - if (lazyScript && lazyScript->maybeScriptUnbarriered() == this) { - // In most cases, our LazyScript's script pointer will reference this - // script. However, because sweeping can be incremental, it's - // possible LazyScript::maybeScript() already null'ed this pointer. - // Furthermore, if we unlazified the LazyScript, it will have a - // completely different JSScript. - lazyScript->resetScript(); - } + // In most cases, our LazyScript's script pointer will reference this + // script, and thus be nulled out by normal weakref processing. However, if + // we unlazified the LazyScript during incremental sweeping, it will have a + // completely different JSScript. + MOZ_ASSERT_IF(lazyScript && !IsAboutToBeFinalizedUnbarriered(&lazyScript), + !lazyScript->hasScript() || lazyScript->maybeScriptUnbarriered() != this); } static const uint32_t GSN_CACHE_THRESHOLD = 100; @@ -3730,7 +3739,7 @@ JSScript::traceChildren(JSTracer* trc) if (hasRegexps()) { ObjectArray* objarray = regexps(); - TraceRange(trc, objarray->length, objarray->vector, "objects"); + TraceRange(trc, objarray->length, objarray->vector, "regexps"); } if (hasConsts()) { @@ -4164,7 +4173,7 @@ LazyScript::Create(ExclusiveContext* cx, HandleFunction fun, MOZ_ASSERT(!res->sourceObject()); res->setParent(enclosingScope, &sourceObjectScript->scriptSourceUnwrap()); - MOZ_ASSERT(!res->maybeScriptUnbarriered()); + MOZ_ASSERT(!res->hasScript()); if (script) res->initScript(script); @@ -4341,3 +4350,23 @@ JSScript::AutoDelazify::dropScript() script_->setDoNotRelazify(oldDoNotRelazify_); script_ = nullptr; } + +JS::ubi::Node::Size +JS::ubi::Concrete::size(mozilla::MallocSizeOf mallocSizeOf) const +{ + Size size = Arena::thingSize(get().asTenured().getAllocKind()); + + size += get().sizeOfData(mallocSizeOf); + size += get().sizeOfTypeScript(mallocSizeOf); + + size_t baselineSize = 0; + size_t baselineStubsSize = 0; + jit::AddSizeOfBaselineData(&get(), mallocSizeOf, &baselineSize, &baselineStubsSize); + size += baselineSize; + size += baselineStubsSize; + + size += jit::SizeOfIonData(&get(), mallocSizeOf); + + MOZ_ASSERT(size > 0); + return size; +} diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 54a067b79d..d90229535e 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -496,6 +496,11 @@ class ScriptCounts jit::IonScriptCounts* ionCounts_; }; +// Note: The key of this hash map is a weak reference to a JSScript. We do not +// use the WeakMap implementation provided in jsweakmap.h because it would be +// collected at the beginning of the sweeping of the compartment, thus before +// the calls to the JSScript::finalize function which are used to aggregate code +// coverage results on the compartment. typedef HashMap, @@ -1623,6 +1628,18 @@ class JSScript : public js::gc::TenuredCell /* Return whether this script was compiled for 'eval' */ bool isForEval() { return isCachedEval() || isActiveEval(); } + /* + * Return whether this script is a top-level script. + * + * If we evaluate some code which contains a syntax error, then we might + * produce a JSScript which has no associated bytecode. Testing with + * |code()| filters out this kind of scripts. + * + * If this script has a function associated to it, then it is not the + * top-level of a file. + */ + bool isTopLevel() { return code() && !functionNonDelazifying(); } + /* Ensure the script has a TypeScript. */ inline bool ensureHasTypes(JSContext* cx); @@ -2065,7 +2082,7 @@ class LazyScript : public gc::TenuredCell // If non-nullptr, the script has been compiled and this is a forwarding // pointer to the result. This is a weak pointer: after relazification, we // can collect the script if there are no other pointers to it. - ReadBarrieredScript script_; + WeakRef script_; // Original function with which the lazy script is associated. HeapPtrFunction function_; @@ -2168,13 +2185,14 @@ class LazyScript : public gc::TenuredCell void resetScript(); JSScript* maybeScript() { - if (script_.unbarrieredGet() && gc::IsAboutToBeFinalized(&script_)) - script_.set(nullptr); return script_; } - JSScript* maybeScriptUnbarriered() const { + const JSScript* maybeScriptUnbarriered() const { return script_.unbarrieredGet(); } + bool hasScript() const { + return bool(script_); + } JSObject* enclosingScope() const { return enclosingScope_; diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index fbaa1b6c0d..623d4e1a5f 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -4539,6 +4539,17 @@ js::DuplicateString(js::ExclusiveContext* cx, const char16_t* s) return ret; } +UniquePtr +js::DuplicateString(const char16_t* s) +{ + size_t n = js_strlen(s) + 1; + UniquePtr ret(js_pod_malloc(n)); + if (!ret) + return nullptr; + PodCopy(ret.get(), s, n); + return ret; +} + template const CharT* js_strchr_limit(const CharT* s, char16_t c, const CharT* limit) diff --git a/js/src/jsstr.h b/js/src/jsstr.h index 72a630ab90..8a993e4ca3 100644 --- a/js/src/jsstr.h +++ b/js/src/jsstr.h @@ -130,6 +130,11 @@ DuplicateString(ExclusiveContext* cx, const char* s); extern mozilla::UniquePtr DuplicateString(ExclusiveContext* cx, const char16_t* s); +// This variant does not report OOMs, you must arrange for OOMs to be reported +// yourself. +extern mozilla::UniquePtr +DuplicateString(const char16_t* s); + /* * Convert a non-string value to a string, returning null after reporting an * error, otherwise returning a new string reference. diff --git a/js/src/jsutil.cpp b/js/src/jsutil.cpp index 5a592c66d5..303063fc85 100644 --- a/js/src/jsutil.cpp +++ b/js/src/jsutil.cpp @@ -11,6 +11,7 @@ #include "mozilla/Assertions.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/PodOperations.h" +#include "mozilla/ThreadLocal.h" #include @@ -32,7 +33,30 @@ using mozilla::PodArrayZero; JS_PUBLIC_DATA(uint32_t) OOM_maxAllocations = UINT32_MAX; JS_PUBLIC_DATA(uint32_t) OOM_counter = 0; JS_PUBLIC_DATA(bool) OOM_failAlways = true; -#endif +namespace js { +namespace oom { + +JS_PUBLIC_DATA(uint32_t) targetThread = 0; +JS_PUBLIC_DATA(mozilla::ThreadLocal) threadType; + +bool +InitThreadType(void) { + return threadType.initialized() || threadType.init(); +} + +void +SetThreadType(ThreadType type) { + threadType.set(type); +} + +uint32_t +GetThreadType(void) { + return threadType.get(); +} + +} // namespace oom +} // namespace js +#endif // defined(DEBUG) || defined(JS_OOM_BREAKPOINT) JS_PUBLIC_API(void) JS_Assert(const char* s, const char* file, int ln) diff --git a/js/src/jsweakmap.h b/js/src/jsweakmap.h index fa72ddbaf6..56ce873f34 100644 --- a/js/src/jsweakmap.h +++ b/js/src/jsweakmap.h @@ -119,7 +119,7 @@ class WeakMapBase { }; template -static T extractUnbarriered(BarrieredBase v) +static T extractUnbarriered(WriteBarrieredBase v) { return v.get(); } diff --git a/js/src/moz.build b/js/src/moz.build index 80d043d2fc..879c980fa3 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -130,8 +130,9 @@ EXPORTS.js += [ '../public/TrackedOptimizationInfo.h', '../public/TypeDecls.h', '../public/UbiNode.h', + '../public/UbiNodeBreadthFirst.h', '../public/UbiNodeCensus.h', - '../public/UbiNodeTraverse.h', + '../public/UbiNodePostOrder.h', '../public/Utility.h', '../public/Value.h', '../public/Vector.h', @@ -288,6 +289,7 @@ UNIFIED_SOURCES += [ 'vm/ArrayBufferObject.cpp', 'vm/CallNonGenericMethod.cpp', 'vm/CharacterEncoding.cpp', + 'vm/CodeCoverage.cpp', 'vm/Compression.cpp', 'vm/DateTime.cpp', 'vm/Debugger.cpp', diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 8a9279c055..77500c0f0c 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -212,7 +212,7 @@ NewGlobalObject(JSContext* cx, JS::CompartmentOptions& options, * The 'newGlobal' function takes an option indicating which principal the * new global should have; 'evaluate' does for the new code. */ -class ShellPrincipals: public JSPrincipals { +class ShellPrincipals final : public JSPrincipals { uint32_t bits; static uint32_t getBits(JSPrincipals* p) { @@ -226,6 +226,11 @@ class ShellPrincipals: public JSPrincipals { this->refcount = refcount; } + bool write(JSContext* cx, JSStructuredCloneWriter* writer) override { + MOZ_ASSERT(false, "not implemented"); + return false; + } + static void destroy(JSPrincipals* principals) { MOZ_ASSERT(principals != &fullyTrusted); MOZ_ASSERT(principals->refcount == 0); @@ -713,6 +718,8 @@ Options(JSContext* cx, unsigned argc, Value* vp) JS::RuntimeOptionsRef(cx).toggleExtraWarnings(); else if (strcmp(opt.ptr(), "werror") == 0) JS::RuntimeOptionsRef(cx).toggleWerror(); + else if (strcmp(opt.ptr(), "throw_on_asmjs_validation_failure") == 0) + JS::RuntimeOptionsRef(cx).toggleThrowOnAsmJSValidationFailure(); else if (strcmp(opt.ptr(), "strict_mode") == 0) JS::RuntimeOptionsRef(cx).toggleStrictMode(); else { @@ -735,6 +742,10 @@ Options(JSContext* cx, unsigned argc, Value* vp) names = JS_sprintf_append(names, "%s%s", found ? "," : "", "werror"); found = true; } + if (names && oldRuntimeOptions.throwOnAsmJSValidationFailure()) { + names = JS_sprintf_append(names, "%s%s", found ? "," : "", "throw_on_asmjs_validation_failure"); + found = true; + } if (names && oldRuntimeOptions.strictMode()) { names = JS_sprintf_append(names, "%s%s", found ? "," : "", "strict_mode"); found = true; diff --git a/js/src/tests/ecma_6/Function/function-constructor-toString-arguments-before-parsing-params.js b/js/src/tests/ecma_6/Function/function-constructor-toString-arguments-before-parsing-params.js new file mode 100644 index 0000000000..4e88a00966 --- /dev/null +++ b/js/src/tests/ecma_6/Function/function-constructor-toString-arguments-before-parsing-params.js @@ -0,0 +1,23 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ + +//----------------------------------------------------------------------------- +var BUGNUMBER = 920479; +var summary = + "Convert all arguments passed to Function() to string before doing any " + + "parsing of the to-be-created Function's parameters or body text"; + +print(BUGNUMBER + ": " + summary); + +/************** + * BEGIN TEST * + **************/ + +assertThrowsValue(() => Function("@", { toString() { throw 42; } }), 42); + +/******************************************************************************/ + +if (typeof reportCompare === "function") + reportCompare(true, true); + +print("Tests complete"); diff --git a/js/src/vm/CodeCoverage.cpp b/js/src/vm/CodeCoverage.cpp new file mode 100644 index 0000000000..3aa7c99ff6 --- /dev/null +++ b/js/src/vm/CodeCoverage.cpp @@ -0,0 +1,492 @@ +/* -*- 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/CodeCoverage.h" + +#include "mozilla/Atomics.h" +#include "mozilla/IntegerPrintfMacros.h" + +#include +#if defined(XP_WIN) +# include +#else +# include +#endif + +#include "jscompartment.h" +#include "jsopcode.h" +#include "jsprf.h" +#include "jsscript.h" + +#include "vm/Runtime.h" +#include "vm/Time.h" + +// This file contains a few functions which are used to produce files understood +// by lcov tools. A detailed description of the format is available in the man +// page for "geninfo" [1]. To make it short, the following paraphrases what is +// commented in the man page by using curly braces prefixed by for-each to +// express repeated patterns. +// +// TN: +// for-each { +// SN: +// for-each