diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 38ca0b9814..fd44bc54c6 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -1081,7 +1081,9 @@ nsOuterWindowProxy::AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy, return false; } for (int32_t i = 0; i < int32_t(length); ++i) { - props.append(INT_TO_JSID(i)); + if (!props.append(INT_TO_JSID(i))) { + return false; + } } return true; diff --git a/dom/base/nsXMLHttpRequest.h b/dom/base/nsXMLHttpRequest.h index ab4e80161c..d0932705bb 100644 --- a/dom/base/nsXMLHttpRequest.h +++ b/dom/base/nsXMLHttpRequest.h @@ -141,7 +141,7 @@ public: IMPL_EVENT_HANDLER(load) IMPL_EVENT_HANDLER(timeout) IMPL_EVENT_HANDLER(loadend) - + virtual void DisconnectFromOwner() override; }; diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index 0750f52651..e880594e26 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -3149,9 +3149,15 @@ ForEachHandler(JSContext* aCx, unsigned aArgc, JS::Value* aVp) JS::AutoValueVector newArgs(aCx); // Arguments are passed in as value, key, object. Keep value and key, replace // object with the maplike/setlike object. - newArgs.append(args.get(0)); - newArgs.append(args.get(1)); - newArgs.append(maplikeOrSetlikeObj); + if (!newArgs.append(args.get(0))) { + return false; + } + if (!newArgs.append(args.get(1))) { + return false; + } + if (!newArgs.append(maplikeOrSetlikeObj)) { + return false; + } JS::Rooted rval(aCx, JS::UndefinedValue()); // Now actually call the user specified callback return JS::Call(aCx, args.thisv(), callbackFn, newArgs, &rval); diff --git a/dom/media/MediaDecoderReaderWrapper.cpp b/dom/media/MediaDecoderReaderWrapper.cpp index 0d5ca7187b..87f5f2c38b 100644 --- a/dom/media/MediaDecoderReaderWrapper.cpp +++ b/dom/media/MediaDecoderReaderWrapper.cpp @@ -146,6 +146,8 @@ MediaDecoderReaderWrapper::MediaDecoderReaderWrapper(bool aIsRealTime, , mReader(aReader) , mAudioCallbackID("AudioCallbackID") , mVideoCallbackID("VideoCallbackID") + , mWaitAudioCallbackID("WaitAudioCallbackID") + , mWaitVideoCallbackID("WaitVideoCallbackID") {} MediaDecoderReaderWrapper::~MediaDecoderReaderWrapper() @@ -198,6 +200,24 @@ MediaDecoderReaderWrapper::CancelVideoCallback(CallbackID aID) mRequestVideoDataCB = nullptr; } +void +MediaDecoderReaderWrapper::CancelWaitAudioCallback(CallbackID aID) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(aID == mWaitAudioCallbackID); + ++mWaitAudioCallbackID; + mWaitAudioDataCB = nullptr; +} + +void +MediaDecoderReaderWrapper::CancelWaitVideoCallback(CallbackID aID) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(aID == mWaitVideoCallbackID); + ++mWaitVideoCallbackID; + mWaitVideoDataCB = nullptr; +} + void MediaDecoderReaderWrapper::RequestAudioData() { @@ -285,6 +305,20 @@ MediaDecoderReaderWrapper::IsRequestingVideoData() const return mVideoDataRequest.Exists(); } +bool +MediaDecoderReaderWrapper::IsWaitingAudioData() const +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + return mAudioWaitRequest.Exists(); +} + +bool +MediaDecoderReaderWrapper::IsWaitingVideoData() const +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + return mVideoWaitRequest.Exists(); +} + RefPtr MediaDecoderReaderWrapper::Seek(SeekTarget aTarget, media::TimeUnit aEndTime) { @@ -295,12 +329,41 @@ MediaDecoderReaderWrapper::Seek(SeekTarget aTarget, media::TimeUnit aEndTime) aEndTime.ToMicroseconds()); } -RefPtr +void MediaDecoderReaderWrapper::WaitForData(MediaData::Type aType) { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); - return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__, - &MediaDecoderReader::WaitForData, aType); + MOZ_ASSERT(WaitCallbackRef(aType)); + + auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__, + &MediaDecoderReader::WaitForData, aType); + + RefPtr self = this; + WaitRequestRef(aType).Begin(p->Then(mOwnerThread, __func__, + [self] (MediaData::Type aType) { + MOZ_ASSERT(self->WaitCallbackRef(aType)); + self->WaitRequestRef(aType).Complete(); + self->WaitCallbackRef(aType)->OnResolved(aType); + }, + [self, aType] (WaitForDataRejectValue aRejection) { + MOZ_ASSERT(self->WaitCallbackRef(aType)); + self->WaitRequestRef(aType).Complete(); + self->WaitCallbackRef(aType)->OnRejected(aRejection); + })); +} + +UniquePtr& +MediaDecoderReaderWrapper::WaitCallbackRef(MediaData::Type aType) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + return aType == MediaData::AUDIO_DATA ? mWaitAudioDataCB : mWaitVideoDataCB; +} + +MozPromiseRequestHolder& +MediaDecoderReaderWrapper::WaitRequestRef(MediaData::Type aType) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + return aType == MediaData::AUDIO_DATA ? mAudioWaitRequest : mVideoWaitRequest; } RefPtr @@ -334,8 +397,12 @@ MediaDecoderReaderWrapper::ResetDecode(TargetQueues aQueues) { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); - mAudioDataRequest.DisconnectIfExists(); + if (aQueues == MediaDecoderReader::AUDIO_VIDEO) { + mAudioDataRequest.DisconnectIfExists(); + mAudioWaitRequest.DisconnectIfExists(); + } mVideoDataRequest.DisconnectIfExists(); + mVideoWaitRequest.DisconnectIfExists(); nsCOMPtr r = NewRunnableMethod(mReader, diff --git a/dom/media/MediaDecoderReaderWrapper.h b/dom/media/MediaDecoderReaderWrapper.h index 6703262312..91ca51af7e 100644 --- a/dom/media/MediaDecoderReaderWrapper.h +++ b/dom/media/MediaDecoderReaderWrapper.h @@ -164,6 +164,58 @@ class MediaDecoderReaderWrapper { RejectFunctionType mRejectFunction; }; + struct WaitForDataCallbackBase + { + virtual ~WaitForDataCallbackBase() {} + virtual void OnResolved(MediaData::Type aType) = 0; + virtual void OnRejected(WaitForDataRejectValue aRejection) = 0; + }; + + template + struct WaitForDataMethodCallback : public WaitForDataCallbackBase + { + WaitForDataMethodCallback(ThisType* aThis, ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod) + : mThis(aThis), mResolveMethod(aResolveMethod), mRejectMethod(aRejectMethod) + { + } + + void OnResolved(MediaData::Type aType) override + { + (mThis->*mResolveMethod)(aType); + } + + void OnRejected(WaitForDataRejectValue aRejection) override + { + (mThis->*mRejectMethod)(aRejection); + } + + RefPtr mThis; + ResolveMethodType mResolveMethod; + RejectMethodType mRejectMethod; + }; + + template + struct WaitForDataFunctionCallback : public WaitForDataCallbackBase + { + WaitForDataFunctionCallback(ResolveFunctionType&& aResolveFuntion, RejectFunctionType&& aRejectFunction) + : mResolveFuntion(Move(aResolveFuntion)), mRejectFunction(Move(aRejectFunction)) + { + } + + void OnResolved(MediaData::Type aType) override + { + mResolveFuntion(aType); + } + + void OnRejected(WaitForDataRejectValue aRejection) override + { + mRejectFunction(aRejection); + } + + ResolveFunctionType mResolveFuntion; + RejectFunctionType mRejectFunction; + }; + public: MediaDecoderReaderWrapper(bool aIsRealTime, AbstractThread* aOwnerThread, @@ -239,18 +291,90 @@ public: return mVideoCallbackID; } + template + CallbackID + SetWaitAudioCallback(ThisType* aThisVal, + ResolveMethodType aResolveMethod, + RejectMethodType aRejectMethod) + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mWaitAudioDataCB, + "Please cancel the original callback before setting a new one."); + + mWaitAudioDataCB.reset( + new WaitForDataMethodCallback( + aThisVal, aResolveMethod, aRejectMethod)); + + return mWaitAudioCallbackID; + } + + template + CallbackID + SetWaitAudioCallback(ResolveFunction&& aResolveFunction, + RejectFunction&& aRejectFunction) + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mWaitAudioDataCB, + "Please cancel the original callback before setting a new one."); + + mWaitAudioDataCB.reset( + new WaitForDataFunctionCallback( + Move(aResolveFunction), Move(aRejectFunction))); + + return mWaitAudioCallbackID; + } + + template + CallbackID + SetWaitVideoCallback(ThisType* aThisVal, + ResolveMethodType aResolveMethod, + RejectMethodType aRejectMethod) + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mWaitVideoDataCB, + "Please cancel the original callback before setting a new one."); + + mWaitVideoDataCB.reset( + new WaitForDataMethodCallback( + aThisVal, aResolveMethod, aRejectMethod)); + + return mWaitVideoCallbackID; + } + + template + CallbackID + SetWaitVideoCallback(ResolveFunction&& aResolveFunction, + RejectFunction&& aRejectFunction) + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mWaitVideoDataCB, + "Please cancel the original callback before setting a new one."); + + mWaitVideoDataCB.reset( + new WaitForDataFunctionCallback( + Move(aResolveFunction), Move(aRejectFunction))); + + return mWaitVideoCallbackID; + } + void CancelAudioCallback(CallbackID aID); void CancelVideoCallback(CallbackID aID); + void CancelWaitAudioCallback(CallbackID aID); + void CancelWaitVideoCallback(CallbackID aID); // NOTE: please set callbacks before requesting audio/video data! void RequestAudioData(); void RequestVideoData(bool aSkipToNextKeyframe, media::TimeUnit aTimeThreshold); + // NOTE: please set callbacks before invoking WaitForData()! + void WaitForData(MediaData::Type aType); + bool IsRequestingAudioData() const; bool IsRequestingVideoData() const; + bool IsWaitingAudioData() const; + bool IsWaitingVideoData() const; RefPtr Seek(SeekTarget aTarget, media::TimeUnit aEndTime); - RefPtr WaitForData(MediaData::Type aType); RefPtr UpdateBufferedWithPromise(); RefPtr Shutdown(); @@ -306,6 +430,9 @@ private: void OnNotDecoded(CallbackBase* aCallback, MediaDecoderReader::NotDecodedReason aReason); + UniquePtr& WaitCallbackRef(MediaData::Type aType); + MozPromiseRequestHolder& WaitRequestRef(MediaData::Type aType); + const bool mForceZeroStartTime; const RefPtr mOwnerThread; const RefPtr mReader; @@ -315,14 +442,20 @@ private: UniquePtr mRequestAudioDataCB; UniquePtr mRequestVideoDataCB; + UniquePtr mWaitAudioDataCB; + UniquePtr mWaitVideoDataCB; MozPromiseRequestHolder mAudioDataRequest; MozPromiseRequestHolder mVideoDataRequest; + MozPromiseRequestHolder mAudioWaitRequest; + MozPromiseRequestHolder mVideoWaitRequest; /* * These callback ids are used to prevent mis-canceling callback. */ CallbackID mAudioCallbackID; CallbackID mVideoCallbackID; + CallbackID mWaitAudioCallbackID; + CallbackID mWaitVideoCallbackID; }; } // namespace mozilla diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp index 0ef5f3e482..692007bcd8 100644 --- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -696,21 +696,7 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType, if (aReason == MediaDecoderReader::WAITING_FOR_DATA) { MOZ_ASSERT(mReader->IsWaitForDataSupported(), "Readers that send WAITING_FOR_DATA need to implement WaitForData"); - RefPtr self = this; - WaitRequestRef(aType).Begin( - mReader->WaitForData(aType) - ->Then(OwnerThread(), __func__, - [self] (MediaData::Type aType) -> void { - self->WaitRequestRef(aType).Complete(); - if (aType == MediaData::AUDIO_DATA) { - self->EnsureAudioDecodeTaskQueued(); - } else { - self->EnsureVideoDecodeTaskQueued(); - } - }, - [self] (WaitForDataRejectValue aRejection) -> void { - self->WaitRequestRef(aRejection.mType).Complete(); - })); + mReader->WaitForData(aType); // We are out of data to decode and will enter buffering mode soon. // We want to play the frames we have already decoded, so we stop pre-rolling @@ -939,8 +925,25 @@ MediaDecoderStateMachine::SetMediaDecoderReaderWrapperCallback() &MediaDecoderStateMachine::OnVideoDecoded, &MediaDecoderStateMachine::OnVideoNotDecoded); + RefPtr self = this; + mWaitAudioCallbackID = + mReader->SetWaitAudioCallback( + [self] (MediaData::Type aType) -> void { + self->EnsureAudioDecodeTaskQueued(); + }, + [self] (WaitForDataRejectValue aRejection) -> void {}); + + mWaitVideoCallbackID = + mReader->SetWaitVideoCallback( + [self] (MediaData::Type aType) -> void { + self->EnsureVideoDecodeTaskQueued(); + }, + [self] (WaitForDataRejectValue aRejection) -> void {}); + DECODER_LOG("MDSM set audio callbacks: mAudioCallbackID = %d\n", (int)mAudioCallbackID); DECODER_LOG("MDSM set video callbacks: mVideoCallbackID = %d\n", (int)mVideoCallbackID); + DECODER_LOG("MDSM set wait audio callbacks: mWaitAudioCallbackID = %d\n", (int)mWaitAudioCallbackID); + DECODER_LOG("MDSM set wait video callbacks: mWaitVideoCallbackID = %d\n", (int)mWaitVideoCallbackID); } void @@ -951,6 +954,12 @@ MediaDecoderStateMachine::CancelMediaDecoderReaderWrapperCallback() DECODER_LOG("MDSM cancel video callbacks: mVideoCallbackID = %d\n", (int)mVideoCallbackID); mReader->CancelVideoCallback(mVideoCallbackID); + + DECODER_LOG("MDSM cancel wait audio callbacks: mWaitAudioCallbackID = %d\n", (int)mWaitAudioCallbackID); + mReader->CancelWaitAudioCallback(mWaitAudioCallbackID); + + DECODER_LOG("MDSM cancel wait video callbacks: mWaitVideoCallbackID = %d\n", (int)mWaitVideoCallbackID); + mReader->CancelWaitVideoCallback(mWaitVideoCallbackID); } void MediaDecoderStateMachine::StopPlayback() @@ -1016,8 +1025,8 @@ MediaDecoderStateMachine::MaybeStartBuffering() (JustExitedQuickBuffering() || HasLowUndecodedData()); } else { MOZ_ASSERT(mReader->IsWaitForDataSupported()); - shouldBuffer = (OutOfDecodedAudio() && mAudioWaitRequest.Exists()) || - (OutOfDecodedVideo() && mVideoWaitRequest.Exists()); + shouldBuffer = (OutOfDecodedAudio() && mReader->IsWaitingAudioData()) || + (OutOfDecodedVideo() && mReader->IsWaitingVideoData()); } if (shouldBuffer) { StartBuffering(); @@ -1277,8 +1286,8 @@ void MediaDecoderStateMachine::StartDecoding() } // Reset other state to pristine values before starting decode. - mIsAudioPrerolling = !DonePrerollingAudio() && !mAudioWaitRequest.Exists(); - mIsVideoPrerolling = !DonePrerollingVideo() && !mVideoWaitRequest.Exists(); + mIsAudioPrerolling = !DonePrerollingAudio() && !mReader->IsWaitingAudioData(); + mIsVideoPrerolling = !DonePrerollingVideo() && !mReader->IsWaitingVideoData(); // Ensure that we've got tasks enqueued to decode data if we need to. DispatchDecodeTasksIfNeeded(); @@ -1707,7 +1716,7 @@ MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued() } if (!IsAudioDecoding() || mReader->IsRequestingAudioData() || - mAudioWaitRequest.Exists()) { + mReader->IsWaitingAudioData()) { return NS_OK; } @@ -1765,7 +1774,7 @@ MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued() } if (!IsVideoDecoding() || mReader->IsRequestingVideoData() || - mVideoWaitRequest.Exists()) { + mReader->IsWaitingVideoData()) { return NS_OK; } @@ -2276,8 +2285,8 @@ nsresult MediaDecoderStateMachine::RunStateMachine() MOZ_ASSERT(mReader->IsWaitForDataSupported(), "Don't yet have a strategy for non-heuristic + non-WaitForData"); DispatchDecodeTasksIfNeeded(); - MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedAudio(), mReader->IsRequestingAudioData() || mAudioWaitRequest.Exists()); - MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedVideo(), mReader->IsRequestingVideoData() || mVideoWaitRequest.Exists()); + MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedAudio(), mReader->IsRequestingAudioData() || mReader->IsWaitingAudioData()); + MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedVideo(), mReader->IsRequestingVideoData() || mReader->IsWaitingVideoData()); DECODER_LOG("In buffering mode, waiting to be notified: outOfAudio: %d, " "mAudioStatus: %s, outOfVideo: %d, mVideoStatus: %s", OutOfDecodedAudio(), AudioRequestStatus(), @@ -2362,7 +2371,6 @@ MediaDecoderStateMachine::Reset(MediaDecoderReader::TargetQueues aQueues /*= AUD mDecodedVideoEndTime = 0; mVideoCompleted = false; VideoQueue().Reset(); - mVideoWaitRequest.DisconnectIfExists(); if (aQueues == MediaDecoderReader::AUDIO_VIDEO) { // Stop the audio thread. Otherwise, MediaSink might be accessing AudioQueue @@ -2372,7 +2380,6 @@ MediaDecoderStateMachine::Reset(MediaDecoderReader::TargetQueues aQueues /*= AUD mDecodedAudioEndTime = 0; mAudioCompleted = false; AudioQueue().Reset(); - mAudioWaitRequest.DisconnectIfExists(); } mMetadataRequest.DisconnectIfExists(); @@ -2848,9 +2855,9 @@ MediaDecoderStateMachine::AudioRequestStatus() const { MOZ_ASSERT(OnTaskQueue()); if (mReader->IsRequestingAudioData()) { - MOZ_DIAGNOSTIC_ASSERT(!mAudioWaitRequest.Exists()); + MOZ_DIAGNOSTIC_ASSERT(!mReader->IsWaitingAudioData()); return "pending"; - } else if (mAudioWaitRequest.Exists()) { + } else if (mReader->IsWaitingAudioData()) { return "waiting"; } return "idle"; @@ -2861,9 +2868,9 @@ MediaDecoderStateMachine::VideoRequestStatus() const { MOZ_ASSERT(OnTaskQueue()); if (mReader->IsRequestingVideoData()) { - MOZ_DIAGNOSTIC_ASSERT(!mVideoWaitRequest.Exists()); + MOZ_DIAGNOSTIC_ASSERT(!mReader->IsWaitingVideoData()); return "pending"; - } else if (mVideoWaitRequest.Exists()) { + } else if (mReader->IsWaitingVideoData()) { return "waiting"; } return "idle"; diff --git a/dom/media/MediaDecoderStateMachine.h b/dom/media/MediaDecoderStateMachine.h index abd0489489..13b274e1bd 100644 --- a/dom/media/MediaDecoderStateMachine.h +++ b/dom/media/MediaDecoderStateMachine.h @@ -830,19 +830,13 @@ private: // should exist at any given moment. CallbackID mAudioCallbackID; - MozPromiseRequestHolder mAudioWaitRequest; + CallbackID mWaitAudioCallbackID; const char* AudioRequestStatus() const; CallbackID mVideoCallbackID; - MozPromiseRequestHolder mVideoWaitRequest; + CallbackID mWaitVideoCallbackID; const char* VideoRequestStatus() const; - MozPromiseRequestHolder& WaitRequestRef(MediaData::Type aType) - { - MOZ_ASSERT(OnTaskQueue()); - return aType == MediaData::AUDIO_DATA ? mAudioWaitRequest : mVideoWaitRequest; - } - // True if we shouldn't play our audio (but still write it to any capturing // streams). When this is true, the audio thread will never start again after // it has stopped. diff --git a/dom/media/SeekTask.cpp b/dom/media/SeekTask.cpp index 15e75e0408..7bd339ba2f 100644 --- a/dom/media/SeekTask.cpp +++ b/dom/media/SeekTask.cpp @@ -157,8 +157,6 @@ SeekTask::Discard() // Disconnect MediaDecoderReaderWrapper. mSeekRequest.DisconnectIfExists(); - mAudioWaitRequest.DisconnectIfExists(); - mVideoWaitRequest.DisconnectIfExists(); CancelMediaDecoderReaderWrapperCallback(); mIsDiscarded = true; @@ -219,7 +217,7 @@ SeekTask::EnsureAudioDecodeTaskQueued() if (!IsAudioDecoding() || mReader->IsRequestingAudioData() || - mAudioWaitRequest.Exists() || + mReader->IsWaitingAudioData() || mSeekRequest.Exists()) { return NS_OK; } @@ -238,7 +236,7 @@ SeekTask::EnsureVideoDecodeTaskQueued() if (!IsVideoDecoding() || mReader->IsRequestingVideoData() || - mVideoWaitRequest.Exists() || + mReader->IsWaitingVideoData() || mSeekRequest.Exists()) { return NS_OK; } @@ -252,9 +250,9 @@ SeekTask::AudioRequestStatus() { AssertOwnerThread(); if (mReader->IsRequestingAudioData()) { - MOZ_DIAGNOSTIC_ASSERT(!mAudioWaitRequest.Exists()); + MOZ_DIAGNOSTIC_ASSERT(!mReader->IsWaitingAudioData()); return "pending"; - } else if (mAudioWaitRequest.Exists()) { + } else if (mReader->IsWaitingAudioData()) { return "waiting"; } return "idle"; @@ -265,9 +263,9 @@ SeekTask::VideoRequestStatus() { AssertOwnerThread(); if (mReader->IsRequestingVideoData()) { - MOZ_DIAGNOSTIC_ASSERT(!mVideoWaitRequest.Exists()); + MOZ_DIAGNOSTIC_ASSERT(!mReader->IsWaitingVideoData()); return "pending"; - } else if (mVideoWaitRequest.Exists()) { + } else if (mReader->IsWaitingVideoData()) { return "waiting"; } return "idle"; @@ -564,16 +562,7 @@ SeekTask::OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason) if (aReason == MediaDecoderReader::WAITING_FOR_DATA) { MOZ_ASSERT(mReader->IsWaitForDataSupported(), "Readers that send WAITING_FOR_DATA need to implement WaitForData"); - RefPtr self = this; - mAudioWaitRequest.Begin(mReader->WaitForData(MediaData::AUDIO_DATA) - ->Then(OwnerThread(), __func__, - [self] (MediaData::Type aType) -> void { - self->mAudioWaitRequest.Complete(); - self->EnsureAudioDecodeTaskQueued(); - }, - [self] (WaitForDataRejectValue aRejection) -> void { - self->mAudioWaitRequest.Complete(); - })); + mReader->WaitForData(MediaData::AUDIO_DATA); // We are out of data to decode and will enter buffering mode soon. // We want to play the frames we have already decoded, so we stop pre-rolling @@ -666,16 +655,7 @@ SeekTask::OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason) if (aReason == MediaDecoderReader::WAITING_FOR_DATA) { MOZ_ASSERT(mReader->IsWaitForDataSupported(), "Readers that send WAITING_FOR_DATA need to implement WaitForData"); - RefPtr self = this; - mVideoWaitRequest.Begin(mReader->WaitForData(MediaData::VIDEO_DATA) - ->Then(OwnerThread(), __func__, - [self] (MediaData::Type aType) -> void { - self->mVideoWaitRequest.Complete(); - self->EnsureVideoDecodeTaskQueued(); - }, - [self] (WaitForDataRejectValue aRejection) -> void { - self->mVideoWaitRequest.Complete(); - })); + mReader->WaitForData(MediaData::VIDEO_DATA); // We are out of data to decode and will enter buffering mode soon. // We want to play the frames we have already decoded, so we stop pre-rolling @@ -717,8 +697,25 @@ SeekTask::SetMediaDecoderReaderWrapperCallback() mReader->SetVideoCallback(this, &SeekTask::OnVideoDecoded, &SeekTask::OnVideoNotDecoded); + RefPtr self = this; + mWaitAudioCallbackID = + mReader->SetWaitAudioCallback( + [self] (MediaData::Type aType) -> void { + self->EnsureAudioDecodeTaskQueued(); + }, + [self] (WaitForDataRejectValue aRejection) -> void {}); + + mWaitVideoCallbackID = + mReader->SetWaitVideoCallback( + [self] (MediaData::Type aType) -> void { + self->EnsureVideoDecodeTaskQueued(); + }, + [self] (WaitForDataRejectValue aRejection) -> void {}); + DECODER_LOG("SeekTask set audio callbacks: mAudioCallbackID = %d\n", (int)mAudioCallbackID); DECODER_LOG("SeekTask set video callbacks: mVideoCallbackID = %d\n", (int)mAudioCallbackID); + DECODER_LOG("SeekTask set wait audio callbacks: mWaitAudioCallbackID = %d\n", (int)mWaitAudioCallbackID); + DECODER_LOG("SeekTask set wait video callbacks: mWaitVideoCallbackID = %d\n", (int)mWaitVideoCallbackID); } void @@ -729,5 +726,11 @@ SeekTask::CancelMediaDecoderReaderWrapperCallback() DECODER_LOG("SeekTask cancel video callbacks: mVideoCallbackID = %d\n", (int)mVideoCallbackID); mReader->CancelVideoCallback(mVideoCallbackID); + + DECODER_LOG("SeekTask cancel wait audio callbacks: mWaitAudioCallbackID = %d\n", (int)mWaitAudioCallbackID); + mReader->CancelWaitAudioCallback(mWaitAudioCallbackID); + + DECODER_LOG("SeekTask cancel wait video callbacks: mWaitVideoCallbackID = %d\n", (int)mWaitVideoCallbackID); + mReader->CancelWaitVideoCallback(mWaitVideoCallbackID); } } // namespace mozilla diff --git a/dom/media/SeekTask.h b/dom/media/SeekTask.h index f9196ff3dd..53fd738a43 100644 --- a/dom/media/SeekTask.h +++ b/dom/media/SeekTask.h @@ -164,8 +164,8 @@ protected: MozPromiseRequestHolder mSeekRequest; CallbackID mAudioCallbackID; CallbackID mVideoCallbackID; - MozPromiseRequestHolder mAudioWaitRequest; - MozPromiseRequestHolder mVideoWaitRequest; + CallbackID mWaitAudioCallbackID; + CallbackID mWaitVideoCallbackID; /* * Information which are going to be returned to MDSM. diff --git a/image/RasterImage.cpp b/image/RasterImage.cpp index b727b24dc3..4fc2db7b34 100644 --- a/image/RasterImage.cpp +++ b/image/RasterImage.cpp @@ -9,7 +9,6 @@ #include "RasterImage.h" -#include "base/histogram.h" #include "gfxPlatform.h" #include "nsComponentManagerUtils.h" #include "nsError.h" @@ -106,7 +105,6 @@ RasterImage::RasterImage(ImageURL* aURI /* = nullptr */) : mAnimationFinished(false), mWantFullDecode(false) { - //Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0); } //****************************************************************************** @@ -1337,16 +1335,6 @@ RasterImage::Decode(const IntSize& aSize, uint32_t aFlags) return NS_ERROR_FAILURE; } - if (mDecodeCount > sMaxDecodeCount) { - // Don't subtract out 0 from the histogram, because that causes its count - // to go negative, which is not kosher. - if (sMaxDecodeCount > 0) { - /*Telemetry::GetHistogramById(Telemetry::IMAGE_MAX_DECODE_COUNT) - ->Subtract(sMaxDecodeCount);*/ - } - sMaxDecodeCount = mDecodeCount; - } - // We're ready to decode; start the decoder. LaunchDecoder(decoder, this, aFlags, mHasSourceData); return NS_OK; diff --git a/ipc/chromium/src/base/histogram.cc b/ipc/chromium/src/base/histogram.cc index dd4a6aec73..1f8b7b5d9b 100644 --- a/ipc/chromium/src/base/histogram.cc +++ b/ipc/chromium/src/base/histogram.cc @@ -941,7 +941,7 @@ void CountHistogram::Accumulate(Sample value, Count count, size_t index) { size_t zero_index = BucketIndex(0); - LinearHistogram::Accumulate(1, 1, zero_index); + LinearHistogram::Accumulate(value, 1, zero_index); } void diff --git a/ipc/glue/BackgroundImpl.cpp b/ipc/glue/BackgroundImpl.cpp index 31650de398..2968e4f90d 100644 --- a/ipc/glue/BackgroundImpl.cpp +++ b/ipc/glue/BackgroundImpl.cpp @@ -435,7 +435,7 @@ private: threadLocalInfo->mActor.forget(&actor); MOZ_ALWAYS_SUCCEEDS( - NS_DispatchToMainThread(NewNonOwningRunnableMethod(actor, &ChildImpl::Release))); + NS_DispatchToMainThread(NewNonOwningRunnableMethod(actor, &ChildImpl::Release))); } } delete threadLocalInfo; @@ -1258,7 +1258,7 @@ ParentImpl::ShutdownTimerCallback(nsITimer* aTimer, void* aClosure) nsCOMPtr forceCloseRunnable = new ForceCloseBackgroundActorsRunnable(closure->mLiveActors); MOZ_ALWAYS_SUCCEEDS(closure->mThread->Dispatch(forceCloseRunnable, - NS_DISPATCH_NORMAL)); + NS_DISPATCH_NORMAL)); } void diff --git a/ipc/glue/BrowserProcessSubThread.h b/ipc/glue/BrowserProcessSubThread.h index 8788a4e8aa..6bea9e3edc 100644 --- a/ipc/glue/BrowserProcessSubThread.h +++ b/ipc/glue/BrowserProcessSubThread.h @@ -72,7 +72,7 @@ private: inline void AssertIOThread() { NS_ASSERTION(MessageLoop::TYPE_IO == MessageLoop::current()->type(), - "should be on the IO thread!"); + "should be on the IO thread!"); } } // namespace ipc diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp index 83c4f9d2b5..5f2326d047 100644 --- a/ipc/glue/GeckoChildProcessHost.cpp +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -1,5 +1,3 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- - */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ @@ -492,22 +490,24 @@ GeckoChildProcessHost::DissociateActor() int32_t GeckoChildProcessHost::mChildCounter = 0; void -GeckoChildProcessHost::SetChildLogName(const char* varName, const char* origLogName) +GeckoChildProcessHost::SetChildLogName(const char* varName, const char* origLogName, + nsACString &buffer) { // We currently have no portable way to launch child with environment // different than parent. So temporarily change NSPR_LOG_FILE so child // inherits value we want it to have. (NSPR only looks at NSPR_LOG_FILE at // startup, so it's 'safe' to play with the parent's environment this way.) - nsAutoCString setChildLogName(varName); - setChildLogName.Append(origLogName); + buffer.Assign(varName); + buffer.Append(origLogName); // Append child-specific postfix to name - setChildLogName.AppendLiteral(".child-"); - setChildLogName.AppendInt(mChildCounter); + buffer.AppendLiteral(".child-"); + buffer.AppendInt(mChildCounter); - // Passing temporary to PR_SetEnv is ok here because env gets copied - // by exec, etc., to permanent storage in child when process launched. - PR_SetEnv(setChildLogName.get()); + // Passing temporary to PR_SetEnv is ok here if we keep the temporary + // for the time we launch the sub-process. It's copied to the new + // environment. + PR_SetEnv(buffer.BeginReading()); } bool @@ -520,37 +520,38 @@ GeckoChildProcessHost::PerformAsyncLaunch(std::vector aExtraOpts, b return PerformAsyncLaunchInternal(aExtraOpts, arch); } - ++mChildCounter; - - // remember original value so we can restore it. // - Note: this code is not called re-entrantly, nor are restoreOrig*LogName // or mChildCounter touched by any other thread, so this is safe. - static nsAutoCString restoreOrigNSPRLogName; - static nsAutoCString restoreOrigMozLogName; + ++mChildCounter; + + // Must keep these on the same stack where from we call PerformAsyncLaunchInternal + // so that PR_DuplicateEnvironment() still sees a valid memory. + nsAutoCString nsprLogName; + nsAutoCString mozLogName; if (origNSPRLogName) { - if (restoreOrigNSPRLogName.IsEmpty()) { - restoreOrigNSPRLogName.AssignLiteral("NSPR_LOG_FILE="); - restoreOrigNSPRLogName.Append(origNSPRLogName); + if (mRestoreOrigNSPRLogName.IsEmpty()) { + mRestoreOrigNSPRLogName.AssignLiteral("NSPR_LOG_FILE="); + mRestoreOrigNSPRLogName.Append(origNSPRLogName); } - SetChildLogName("NSPR_LOG_FILE=", origNSPRLogName); + SetChildLogName("NSPR_LOG_FILE=", origNSPRLogName, nsprLogName); } if (origMozLogName) { - if (restoreOrigMozLogName.IsEmpty()) { - restoreOrigMozLogName.AssignLiteral("MOZ_LOG_FILE="); - restoreOrigMozLogName.Append(origMozLogName); + if (mRestoreOrigMozLogName.IsEmpty()) { + mRestoreOrigMozLogName.AssignLiteral("MOZ_LOG_FILE="); + mRestoreOrigMozLogName.Append(origMozLogName); } - SetChildLogName("MOZ_LOG_FILE=", origMozLogName); + SetChildLogName("MOZ_LOG_FILE=", origMozLogName, mozLogName); } bool retval = PerformAsyncLaunchInternal(aExtraOpts, arch); // Revert to original value if (origNSPRLogName) { - PR_SetEnv(restoreOrigNSPRLogName.get()); + PR_SetEnv(mRestoreOrigNSPRLogName.get()); } if (origMozLogName) { - PR_SetEnv(restoreOrigMozLogName.get()); + PR_SetEnv(mRestoreOrigMozLogName.get()); } return retval; diff --git a/ipc/glue/GeckoChildProcessHost.h b/ipc/glue/GeckoChildProcessHost.h index 7a52350427..894616a56d 100644 --- a/ipc/glue/GeckoChildProcessHost.h +++ b/ipc/glue/GeckoChildProcessHost.h @@ -196,11 +196,14 @@ private: base::ProcessArchitecture arch); bool RunPerformAsyncLaunch(StringVector aExtraOpts=StringVector(), - base::ProcessArchitecture aArch=base::GetCurrentProcessArchitecture()); + base::ProcessArchitecture aArch=base::GetCurrentProcessArchitecture()); static void GetPathToBinary(FilePath& exePath); - void SetChildLogName(const char* varName, const char* origLogName); + // The buffer is passed to preserve its lifetime until we are done + // with launching the sub-process. + void SetChildLogName(const char* varName, const char* origLogName, + nsACString &buffer); // In between launching the subprocess and handing off its IPC // channel, there's a small window of time in which *we* might still @@ -215,6 +218,12 @@ private: // it to stay alive and have not yet been destroyed. Atomic mAssociatedActors; + // Remember original env values so we can restore it (there is no other + // simple way how to change environment of a child process than to modify + // the current environment). + nsCString mRestoreOrigNSPRLogName; + nsCString mRestoreOrigMozLogName; + static uint32_t sNextUniqueID; }; diff --git a/ipc/glue/MessagePump.cpp b/ipc/glue/MessagePump.cpp index 39362da3d3..6789357ae0 100644 --- a/ipc/glue/MessagePump.cpp +++ b/ipc/glue/MessagePump.cpp @@ -81,7 +81,7 @@ MessagePump::Run(MessagePump::Delegate* aDelegate) { MOZ_ASSERT(keep_running_); MOZ_RELEASE_ASSERT(NS_IsMainThread(), - "Use mozilla::ipc::MessagePumpForNonMainThreads instead!"); + "Use mozilla::ipc::MessagePumpForNonMainThreads instead!"); MOZ_RELEASE_ASSERT(!mThread); nsIThread* thisThread = NS_GetCurrentThread(); @@ -172,7 +172,7 @@ MessagePump::ScheduleDelayedWork(const base::TimeTicks& aDelayedTime) // To avoid racing on mDelayedWorkTimer, we need to be on the same thread as // ::Run(). MOZ_RELEASE_ASSERT(NS_GetCurrentThread() == mThread || - (!mThread && NS_IsMainThread())); + (!mThread && NS_IsMainThread())); if (!mDelayedWorkTimer) { mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID); @@ -222,7 +222,7 @@ MessagePump::DoDelayedWork(base::MessagePump::Delegate* aDelegate) } NS_IMPL_ISUPPORTS_INHERITED(DoWorkRunnable, CancelableRunnable, - nsITimerCallback) + nsITimerCallback) NS_IMETHODIMP DoWorkRunnable::Run() diff --git a/js/public/Class.h b/js/public/Class.h index 3f2555186a..926021769e 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -953,7 +953,8 @@ Valueify(const JSClass* c) enum ESClassValue { ESClass_Object, ESClass_Array, ESClass_Number, ESClass_String, ESClass_Boolean, ESClass_RegExp, ESClass_ArrayBuffer, ESClass_SharedArrayBuffer, - ESClass_Date, ESClass_Set, ESClass_Map, ESClass_Promise, + ESClass_Date, ESClass_Set, ESClass_Map, ESClass_Promise, ESClass_MapIterator, + ESClass_SetIterator, /** None of the above. */ ESClass_Other diff --git a/js/public/GCAPI.h b/js/public/GCAPI.h index ce106d5a43..906890a955 100644 --- a/js/public/GCAPI.h +++ b/js/public/GCAPI.h @@ -578,7 +578,8 @@ class JS_PUBLIC_API(AutoCheckCannotGC) : public AutoAssertOnGC /** * Unsets the gray bit for anything reachable from |thing|. |kind| should not be - * JS::TraceKind::Shape. |thing| should be non-null. + * JS::TraceKind::Shape. |thing| should be non-null. The return value indicates + * if anything was unmarked. */ extern JS_FRIEND_API(bool) UnmarkGrayGCThingRecursively(GCCellPtr thing); diff --git a/js/public/Proxy.h b/js/public/Proxy.h index 42c3b9e407..d7c3ff5145 100644 --- a/js/public/Proxy.h +++ b/js/public/Proxy.h @@ -101,10 +101,8 @@ class JS_FRIEND_API(Wrapper); * * BaseProxyHandler * | - * DirectProxyHandler // has a target - * | - * Wrapper // can be unwrapped, revealing target - * | // (see js::CheckedUnwrap) + * Wrapper // has a target, can be unwrapped to reveal + * | // target (see js::CheckedUnwrap) * | * CrossCompartmentWrapper // target is in another compartment; * // implements membrane between compartments @@ -349,81 +347,6 @@ class JS_FRIEND_API(BaseProxyHandler) virtual bool isScripted() const { return false; } }; -/* - * DirectProxyHandler includes a notion of a target object. All methods are - * reimplemented such that they forward their behavior to the target. This - * allows consumers of this class to forward to another object as transparently - * and efficiently as possible. - * - * Important: If you add a method implementation here, you probably also need - * to add an override in CrossCompartmentWrapper. If you don't, you risk - * compartment mismatches. See bug 945826 comment 0. - */ -class JS_FRIEND_API(DirectProxyHandler) : public BaseProxyHandler -{ - public: - explicit MOZ_CONSTEXPR DirectProxyHandler(const void* aFamily, bool aHasPrototype = false, - bool aHasSecurityPolicy = false) - : BaseProxyHandler(aFamily, aHasPrototype, aHasSecurityPolicy) - { } - - /* Standard internal methods. */ - virtual bool getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, - MutableHandle desc) const override; - virtual bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id, - Handle desc, - ObjectOpResult& result) const override; - virtual bool ownPropertyKeys(JSContext* cx, HandleObject proxy, - AutoIdVector& props) const override; - virtual bool delete_(JSContext* cx, HandleObject proxy, HandleId id, - ObjectOpResult& result) const override; - virtual bool enumerate(JSContext* cx, HandleObject proxy, - MutableHandleObject objp) const override; - virtual bool getPrototype(JSContext* cx, HandleObject proxy, - MutableHandleObject protop) const override; - virtual bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto, - ObjectOpResult& result) const override; - virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary, - MutableHandleObject protop) const override; - virtual bool setImmutablePrototype(JSContext* cx, HandleObject proxy, - bool* succeeded) const override; - virtual bool preventExtensions(JSContext* cx, HandleObject proxy, - ObjectOpResult& result) const override; - virtual bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const override; - virtual bool has(JSContext* cx, HandleObject proxy, HandleId id, - bool* bp) const override; - virtual bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, - HandleId id, MutableHandleValue vp) const override; - virtual bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, - HandleValue receiver, ObjectOpResult& result) const override; - virtual bool call(JSContext* cx, HandleObject proxy, const CallArgs& args) const override; - virtual bool construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const override; - - /* SpiderMonkey extensions. */ - virtual bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, - MutableHandle desc) const override; - virtual bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, - bool* bp) const override; - virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy, - AutoIdVector& props) const override; - virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl, - const CallArgs& args) const override; - virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, - bool* bp) const override; - virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, - ESClassValue* classValue) const override; - virtual bool isArray(JSContext* cx, HandleObject proxy, - JS::IsArrayAnswer* answer) const override; - virtual const char* className(JSContext* cx, HandleObject proxy) const override; - virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, - unsigned indent) const override; - virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, - RegExpGuard* g) const override; - virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override; - virtual bool isCallable(JSObject* obj) const override; - virtual JSObject* weakmapKeyDelegate(JSObject* proxy) const override; -}; - extern JS_FRIEND_DATA(const js::Class* const) ProxyClassPtr; inline bool IsProxy(const JSObject* obj) diff --git a/js/public/TraceKind.h b/js/public/TraceKind.h index 3a2cce9c5c..3f2f18e6ae 100644 --- a/js/public/TraceKind.h +++ b/js/public/TraceKind.h @@ -98,15 +98,10 @@ JS_FOR_EACH_TRACEKIND(JS_EXPAND_DEF); enum class RootKind : int8_t { // These map 1:1 with trace kinds. - BaseShape = 0, - JitCode, - LazyScript, - Object, - ObjectGroup, - Script, - Shape, - String, - Symbol, +#define EXPAND_ROOT_KIND(name, _0, _1) \ + name, +JS_FOR_EACH_TRACEKIND(EXPAND_ROOT_KIND) +#undef EXPAND_ROOT_KIND // These tagged pointers are special-cased for performance. Id, diff --git a/js/public/TracingAPI.h b/js/public/TracingAPI.h index ad378b0a0c..4812bea7ef 100644 --- a/js/public/TracingAPI.h +++ b/js/public/TracingAPI.h @@ -71,19 +71,37 @@ class JS_PUBLIC_API(JSTracer) bool isTenuringTracer() const { return tag_ == TracerKindTag::Tenuring; } bool isCallbackTracer() const { return tag_ == TracerKindTag::Callback; } inline JS::CallbackTracer* asCallbackTracer(); +#ifdef DEBUG + bool checkEdges() { return checkEdges_; } +#endif protected: JSTracer(JSRuntime* rt, TracerKindTag tag, WeakMapTraceKind weakTraceKind = TraceWeakMapValues) - : runtime_(rt), weakMapAction_(weakTraceKind), tag_(tag) + : runtime_(rt) + , weakMapAction_(weakTraceKind) +#ifdef DEBUG + , checkEdges_(true) +#endif + , tag_(tag) {} +#ifdef DEBUG + // Set whether to check edges are valid in debug builds. + void setCheckEdges(bool check) { + checkEdges_ = check; + } +#endif + private: - JSRuntime* runtime_; - WeakMapTraceKind weakMapAction_; + JSRuntime* runtime_; + WeakMapTraceKind weakMapAction_; +#ifdef DEBUG + bool checkEdges_; +#endif protected: - TracerKindTag tag_; + TracerKindTag tag_; }; namespace JS { diff --git a/js/src/asmjs/AsmJS.cpp b/js/src/asmjs/AsmJS.cpp index 2b771493d1..e0e4aafeab 100644 --- a/js/src/asmjs/AsmJS.cpp +++ b/js/src/asmjs/AsmJS.cpp @@ -2025,8 +2025,7 @@ class MOZ_STACK_CLASS ModuleValidator uint32_t funcIndex = numFunctions(); if (funcIndex >= MaxFuncs) return failCurrentOffset("too many functions"); - if (!mg_.initFuncSig(funcIndex, sigIndex)) - return false; + mg_.initFuncSig(funcIndex, sigIndex); Global* global = validationLifo_.new_(Global::Function); if (!global) return false; diff --git a/js/src/asmjs/AsmJS.h b/js/src/asmjs/AsmJS.h index 8d2328ee7d..9c29b2db56 100644 --- a/js/src/asmjs/AsmJS.h +++ b/js/src/asmjs/AsmJS.h @@ -40,7 +40,7 @@ typedef frontend::ParseContext AsmJSParseContext; // indeterminate amount and the entire function should be reparsed from the // beginning. -extern bool +extern MOZ_MUST_USE bool CompileAsmJS(ExclusiveContext* cx, AsmJSParser& parser, frontend::ParseNode* stmtList, bool* validated); diff --git a/js/src/asmjs/Wasm.h b/js/src/asmjs/Wasm.h index a0abe580ed..539a9ab436 100644 --- a/js/src/asmjs/Wasm.h +++ b/js/src/asmjs/Wasm.h @@ -48,7 +48,7 @@ static const uint64_t MappedSize = 2 * Uint32Range + PageSize; // Compiles the given binary wasm module given the ArrayBufferObject // and links the module's imports with the given import object. -bool +MOZ_MUST_USE bool Eval(JSContext* cx, Handle code, HandleObject importObj, MutableHandleObject exportObj); diff --git a/js/src/asmjs/WasmBinary.h b/js/src/asmjs/WasmBinary.h index 3d5bf90163..3a061abab3 100644 --- a/js/src/asmjs/WasmBinary.h +++ b/js/src/asmjs/WasmBinary.h @@ -815,10 +815,10 @@ class Decoder typedef Vector ValTypeVector; -bool +MOZ_MUST_USE bool EncodeLocalEntries(Encoder& d, const ValTypeVector& locals); -bool +MOZ_MUST_USE bool DecodeLocalEntries(Decoder& d, ValTypeVector* locals); } // namespace wasm diff --git a/js/src/asmjs/WasmBinaryIterator.h b/js/src/asmjs/WasmBinaryIterator.h index 0cebb23135..4648d1837b 100644 --- a/js/src/asmjs/WasmBinaryIterator.h +++ b/js/src/asmjs/WasmBinaryIterator.h @@ -390,7 +390,7 @@ class MOZ_STACK_CLASS ExprIter : private Policy } // Read the value stack entry at depth |index|. - bool peek(uint32_t index, TypeAndValue* tv) { + MOZ_MUST_USE bool peek(uint32_t index, TypeAndValue* tv) { if (Validate && valueStack_.length() - controlStack_.back().valueStackStart() <= index) return fail("peeking at value from outside block"); *tv = valueStack_[valueStack_.length() - index]; @@ -536,7 +536,7 @@ ExprIter::typeMismatch(ExprType actual, ExprType expected) } template -inline MOZ_MUST_USE bool +inline bool ExprIter::checkType(ExprType actual, ExprType expected) { if (!Validate) { @@ -1230,7 +1230,7 @@ ExprIter::readSetGlobal(const GlobalDescVector& globals, uint32_t* id, V } template -inline MOZ_MUST_USE bool +inline bool ExprIter::readI32Const(int32_t* i32) { MOZ_ASSERT(Classify(expr_) == ExprKind::I32); @@ -1241,7 +1241,7 @@ ExprIter::readI32Const(int32_t* i32) } template -inline MOZ_MUST_USE bool +inline bool ExprIter::readI64Const(int64_t* i64) { MOZ_ASSERT(Classify(expr_) == ExprKind::I64); @@ -1252,7 +1252,7 @@ ExprIter::readI64Const(int64_t* i64) } template -inline MOZ_MUST_USE bool +inline bool ExprIter::readF32Const(float* f32) { MOZ_ASSERT(Classify(expr_) == ExprKind::F32); @@ -1271,7 +1271,7 @@ ExprIter::readF32Const(float* f32) } template -inline MOZ_MUST_USE bool +inline bool ExprIter::readF64Const(double* f64) { MOZ_ASSERT(Classify(expr_) == ExprKind::F64); @@ -1290,7 +1290,7 @@ ExprIter::readF64Const(double* f64) } template -inline MOZ_MUST_USE bool +inline bool ExprIter::readI32x4Const(I32x4* i32x4) { MOZ_ASSERT(Classify(expr_) == ExprKind::I32x4); @@ -1301,7 +1301,7 @@ ExprIter::readI32x4Const(I32x4* i32x4) } template -inline MOZ_MUST_USE bool +inline bool ExprIter::readF32x4Const(F32x4* f32x4) { MOZ_ASSERT(Classify(expr_) == ExprKind::F32x4); @@ -1312,7 +1312,7 @@ ExprIter::readF32x4Const(F32x4* f32x4) } template -inline MOZ_MUST_USE bool +inline bool ExprIter::readB32x4Const(I32x4* i32x4) { MOZ_ASSERT(Classify(expr_) == ExprKind::B32x4); diff --git a/js/src/asmjs/WasmBinaryToText.h b/js/src/asmjs/WasmBinaryToText.h index 0cd6440b24..89d0ccc0c2 100644 --- a/js/src/asmjs/WasmBinaryToText.h +++ b/js/src/asmjs/WasmBinaryToText.h @@ -33,7 +33,7 @@ namespace wasm { // Translate the given binary representation of a wasm module into the module's textual // representation. -bool +MOZ_MUST_USE bool BinaryToText(JSContext* cx, const uint8_t* bytes, size_t length, StringBuffer& buffer); } // namespace wasm diff --git a/js/src/asmjs/WasmGenerator.cpp b/js/src/asmjs/WasmGenerator.cpp index b2cf87fec9..f6502183f4 100644 --- a/js/src/asmjs/WasmGenerator.cpp +++ b/js/src/asmjs/WasmGenerator.cpp @@ -627,7 +627,7 @@ ModuleGenerator::sig(uint32_t index) const return shared_->sigs[index]; } -bool +void ModuleGenerator::initFuncSig(uint32_t funcIndex, uint32_t sigIndex) { MOZ_ASSERT(isAsmJS()); @@ -636,7 +636,6 @@ ModuleGenerator::initFuncSig(uint32_t funcIndex, uint32_t sigIndex) module_->numFuncs++; shared_->funcSigs[funcIndex] = &shared_->sigs[sigIndex]; - return true; } void diff --git a/js/src/asmjs/WasmGenerator.h b/js/src/asmjs/WasmGenerator.h index dc83723b55..d45c00f316 100644 --- a/js/src/asmjs/WasmGenerator.h +++ b/js/src/asmjs/WasmGenerator.h @@ -145,21 +145,22 @@ class MOZ_STACK_CLASS ModuleGenerator DebugOnly startedFuncDefs_; DebugOnly finishedFuncDefs_; - bool finishOutstandingTask(); + MOZ_MUST_USE bool finishOutstandingTask(); bool funcIsDefined(uint32_t funcIndex) const; uint32_t funcEntry(uint32_t funcIndex) const; - bool convertOutOfRangeBranchesToThunks(); - bool finishTask(IonCompileTask* task); - bool finishCodegen(StaticLinkData* link); - bool finishStaticLinkData(uint8_t* code, uint32_t codeBytes, StaticLinkData* link); - bool addImport(const Sig& sig, uint32_t globalDataOffset); - bool allocateGlobalBytes(uint32_t bytes, uint32_t align, uint32_t* globalDataOffset); + MOZ_MUST_USE bool convertOutOfRangeBranchesToThunks(); + MOZ_MUST_USE bool finishTask(IonCompileTask* task); + MOZ_MUST_USE bool finishCodegen(StaticLinkData* link); + MOZ_MUST_USE bool finishStaticLinkData(uint8_t* code, uint32_t codeBytes, StaticLinkData* link); + MOZ_MUST_USE bool addImport(const Sig& sig, uint32_t globalDataOffset); + MOZ_MUST_USE bool allocateGlobalBytes(uint32_t bytes, uint32_t align, + uint32_t* globalDataOffset); public: explicit ModuleGenerator(ExclusiveContext* cx); ~ModuleGenerator(); - bool init(UniqueModuleGeneratorData shared, UniqueChars filename); + MOZ_MUST_USE bool init(UniqueModuleGeneratorData shared, UniqueChars filename); bool isAsmJS() const { return module_->kind == ModuleKind::AsmJS; } CompileArgs args() const { return module_->compileArgs; } @@ -178,7 +179,7 @@ class MOZ_STACK_CLASS ModuleGenerator const DeclaredSig& funcSig(uint32_t funcIndex) const; // Globals: - bool allocateGlobal(ValType type, bool isConst, uint32_t* index); + MOZ_MUST_USE bool allocateGlobal(ValType type, bool isConst, uint32_t* index); const GlobalDesc& global(unsigned index) const { return shared_->globals[index]; } // Imports: @@ -186,35 +187,37 @@ class MOZ_STACK_CLASS ModuleGenerator const ImportModuleGeneratorData& import(uint32_t index) const; // Exports: - bool declareExport(UniqueChars fieldName, uint32_t funcIndex, uint32_t* exportIndex = nullptr); + MOZ_MUST_USE bool declareExport(UniqueChars fieldName, uint32_t funcIndex, + uint32_t* exportIndex = nullptr); uint32_t numExports() const; - bool addMemoryExport(UniqueChars fieldName); + MOZ_MUST_USE bool addMemoryExport(UniqueChars fieldName); // Function definitions: - bool startFuncDefs(); - bool startFuncDef(uint32_t lineOrBytecode, FunctionGenerator* fg); - bool finishFuncDef(uint32_t funcIndex, unsigned generateTime, FunctionGenerator* fg); - bool finishFuncDefs(); + MOZ_MUST_USE bool startFuncDefs(); + MOZ_MUST_USE bool startFuncDef(uint32_t lineOrBytecode, FunctionGenerator* fg); + MOZ_MUST_USE bool finishFuncDef(uint32_t funcIndex, unsigned generateTime, + FunctionGenerator* fg); + MOZ_MUST_USE bool finishFuncDefs(); // Function-pointer tables: static const uint32_t BadIndirectCall = UINT32_MAX; // asm.js lazy initialization: void initSig(uint32_t sigIndex, Sig&& sig); - bool initFuncSig(uint32_t funcIndex, uint32_t sigIndex); - bool initImport(uint32_t importIndex, uint32_t sigIndex); - bool initSigTableLength(uint32_t sigIndex, uint32_t numElems); + void initFuncSig(uint32_t funcIndex, uint32_t sigIndex); + MOZ_MUST_USE bool initImport(uint32_t importIndex, uint32_t sigIndex); + MOZ_MUST_USE bool initSigTableLength(uint32_t sigIndex, uint32_t numElems); void initSigTableElems(uint32_t sigIndex, Uint32Vector&& elemFuncIndices); void bumpMinHeapLength(uint32_t newMinHeapLength); // Return a ModuleData object which may be used to construct a Module, the // StaticLinkData required to call Module::staticallyLink, and the list of // functions that took a long time to compile. - bool finish(CacheableCharsVector&& prettyFuncNames, - UniqueModuleData* module, - UniqueStaticLinkData* staticLinkData, - UniqueExportMap* exportMap, - SlowFunctionVector* slowFuncs); + MOZ_MUST_USE bool finish(CacheableCharsVector&& prettyFuncNames, + UniqueModuleData* module, + UniqueStaticLinkData* staticLinkData, + UniqueExportMap* exportMap, + SlowFunctionVector* slowFuncs); }; // A FunctionGenerator encapsulates the generation of a single function body. @@ -245,7 +248,7 @@ class MOZ_STACK_CLASS FunctionGenerator Bytes& bytes() { return bytes_; } - bool addCallSiteLineNum(uint32_t lineno) { + MOZ_MUST_USE bool addCallSiteLineNum(uint32_t lineno) { return callSiteLineNums_.append(lineno); } }; diff --git a/js/src/asmjs/WasmIonCompile.cpp b/js/src/asmjs/WasmIonCompile.cpp index 7cadc86d96..d81c0dbe16 100644 --- a/js/src/asmjs/WasmIonCompile.cpp +++ b/js/src/asmjs/WasmIonCompile.cpp @@ -461,20 +461,22 @@ class FunctionCompiler return ins; } - MDefinition* div(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd) + MDefinition* div(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd, + bool trapOnError) { if (inDeadCode()) return nullptr; - MDiv* ins = MDiv::NewAsmJS(alloc(), lhs, rhs, type, unsignd); + MDiv* ins = MDiv::NewAsmJS(alloc(), lhs, rhs, type, unsignd, trapOnError); curBlock_->add(ins); return ins; } - MDefinition* mod(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd) + MDefinition* mod(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd, + bool trapOnError) { if (inDeadCode()) return nullptr; - MMod* ins = MMod::NewAsmJS(alloc(), lhs, rhs, type, unsignd); + MMod* ins = MMod::NewAsmJS(alloc(), lhs, rhs, type, unsignd, trapOnError); curBlock_->add(ins); return ins; } @@ -1975,7 +1977,8 @@ EmitDiv(FunctionCompiler& f, ValType operandType, MIRType mirType, bool isUnsign if (!f.iter().readBinary(operandType, &lhs, &rhs)) return false; - f.iter().setResult(f.div(lhs, rhs, mirType, isUnsigned)); + bool trapOnError = f.mg().kind == ModuleKind::Wasm; + f.iter().setResult(f.div(lhs, rhs, mirType, isUnsigned, trapOnError)); return true; } @@ -1987,7 +1990,8 @@ EmitRem(FunctionCompiler& f, ValType operandType, MIRType mirType, bool isUnsign if (!f.iter().readBinary(operandType, &lhs, &rhs)) return false; - f.iter().setResult(f.mod(lhs, rhs, mirType, isUnsigned)); + bool trapOnError = f.mg().kind == ModuleKind::Wasm; + f.iter().setResult(f.mod(lhs, rhs, mirType, isUnsigned, trapOnError)); return true; } diff --git a/js/src/asmjs/WasmIonCompile.h b/js/src/asmjs/WasmIonCompile.h index f5eff686c3..4dcd999fa9 100644 --- a/js/src/asmjs/WasmIonCompile.h +++ b/js/src/asmjs/WasmIonCompile.h @@ -150,7 +150,7 @@ class IonCompileTask } }; -bool +MOZ_MUST_USE bool IonCompileFunction(IonCompileTask* task); } // namespace wasm diff --git a/js/src/asmjs/WasmModule.h b/js/src/asmjs/WasmModule.h index 7036767fa4..fb41ff901b 100644 --- a/js/src/asmjs/WasmModule.h +++ b/js/src/asmjs/WasmModule.h @@ -45,7 +45,7 @@ namespace wasm { size_t serializedSize() const; \ uint8_t* serialize(uint8_t* cursor) const; \ const uint8_t* deserialize(ExclusiveContext* cx, const uint8_t* cursor); \ - bool clone(JSContext* cx, Type* out) const; \ + MOZ_MUST_USE bool clone(JSContext* cx, Type* out) const; \ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; // The StaticLinkData contains all the metadata necessary to perform @@ -500,7 +500,7 @@ class Module : public mozilla::LinkedListElement WasmActivation*& activation(); void specializeToHeap(ArrayBufferObjectMaybeShared* heap); void despecializeFromHeap(ArrayBufferObjectMaybeShared* heap); - bool sendCodeRangesToProfiler(JSContext* cx); + MOZ_MUST_USE bool sendCodeRangesToProfiler(JSContext* cx); MOZ_MUST_USE bool setProfilingEnabled(JSContext* cx, bool enabled); ImportExit& importToExit(const Import& import); @@ -508,7 +508,7 @@ class Module : public mozilla::LinkedListElement protected: const ModuleData& base() const { return *module_; } - bool clone(JSContext* cx, const StaticLinkData& link, Module* clone) const; + MOZ_MUST_USE bool clone(JSContext* cx, const StaticLinkData& link, Module* clone) const; public: static const unsigned SizeOfImportExit = sizeof(ImportExit); @@ -570,19 +570,19 @@ class Module : public mozilla::LinkedListElement // statically-linked state. The given StaticLinkData must have come from the // compilation of this module. - bool staticallyLink(ExclusiveContext* cx, const StaticLinkData& link); + MOZ_MUST_USE bool staticallyLink(ExclusiveContext* cx, const StaticLinkData& link); // This function transitions the module from a statically-linked state to a // dynamically-linked state. If this module usesHeap(), a non-null heap // buffer must be given. The given import vector must match the module's // ImportVector. The function returns a new export object for this module. - bool dynamicallyLink(JSContext* cx, - Handle moduleObj, - Handle heap, - Handle imports, - const ExportMap& exportMap, - MutableHandleObject exportObj); + MOZ_MUST_USE bool dynamicallyLink(JSContext* cx, + Handle moduleObj, + Handle heap, + Handle imports, + const ExportMap& exportMap, + MutableHandleObject exportObj); // The wasm heap, established by dynamicallyLink. @@ -593,7 +593,7 @@ class Module : public mozilla::LinkedListElement // arguments (coerced to the corresponding types of the Export signature) // and calling the export's entry trampoline. - bool callExport(JSContext* cx, uint32_t exportIndex, CallArgs args); + MOZ_MUST_USE bool callExport(JSContext* cx, uint32_t exportIndex, CallArgs args); // Initially, calls to imports in wasm code call out through the generic // callImport method. If the imported callee gets JIT compiled and the types @@ -601,8 +601,8 @@ class Module : public mozilla::LinkedListElement // directly into the JIT code. If the JIT code is released, the Module must // be notified so it can go back to the generic callImport. - bool callImport(JSContext* cx, uint32_t importIndex, unsigned argc, const uint64_t* argv, - MutableHandleValue rval); + MOZ_MUST_USE bool callImport(JSContext* cx, uint32_t importIndex, unsigned argc, + const uint64_t* argv, MutableHandleValue rval); void deoptimizeImportExit(uint32_t importIndex); // At runtime, when $pc is in wasm function code (containsFunctionPC($pc)), @@ -666,7 +666,7 @@ class WasmModuleObject : public NativeObject public: static const unsigned RESERVED_SLOTS = 1; static WasmModuleObject* create(ExclusiveContext* cx); - bool init(wasm::Module* module); + MOZ_MUST_USE bool init(wasm::Module* module); wasm::Module& module() const; void addSizeOfMisc(mozilla::MallocSizeOf mallocSizeOf, size_t* code, size_t* data); static const Class class_; diff --git a/js/src/asmjs/WasmSerialize.h b/js/src/asmjs/WasmSerialize.h index b13ab5f06f..4c3c55428f 100644 --- a/js/src/asmjs/WasmSerialize.h +++ b/js/src/asmjs/WasmSerialize.h @@ -165,7 +165,7 @@ DeserializeVector(ExclusiveContext* cx, const uint8_t* cursor, } template -static inline bool +static inline MOZ_MUST_USE bool CloneVector(JSContext* cx, const mozilla::Vector& in, mozilla::Vector* out) { @@ -220,7 +220,7 @@ DeserializePodVector(ExclusiveContext* cx, const uint8_t* cursor, } template -static inline bool +static inline MOZ_MUST_USE bool ClonePodVector(JSContext* cx, const mozilla::Vector& in, mozilla::Vector* out) { @@ -230,7 +230,7 @@ ClonePodVector(JSContext* cx, const mozilla::Vector& in return true; } -static inline bool +static inline MOZ_MUST_USE bool GetCPUID(uint32_t* cpuId) { enum Arch { @@ -273,7 +273,7 @@ class MachineId JS::BuildIdCharVector buildId_; public: - bool extractCurrentState(ExclusiveContext* cx) { + MOZ_MUST_USE bool extractCurrentState(ExclusiveContext* cx) { if (!cx->buildIdOp()) return false; if (!cx->buildIdOp()(&buildId_)) diff --git a/js/src/asmjs/WasmSignalHandlers.h b/js/src/asmjs/WasmSignalHandlers.h index 4627670fdd..254cd97d56 100644 --- a/js/src/asmjs/WasmSignalHandlers.h +++ b/js/src/asmjs/WasmSignalHandlers.h @@ -38,7 +38,7 @@ namespace wasm { // runtime. Return whether runtime can: // - rely on fault handler support for avoiding asm.js heap bounds checks // - rely on InterruptRunningJitCode to halt running Ion/asm.js from any thread -bool +MOZ_MUST_USE bool EnsureSignalHandlersInstalled(JSRuntime* rt); #if defined(XP_DARWIN) && defined(ASMJS_MAY_USE_SIGNAL_HANDLERS) diff --git a/js/src/asmjs/WasmStubs.cpp b/js/src/asmjs/WasmStubs.cpp index 0019b1cfed..50cda854da 100644 --- a/js/src/asmjs/WasmStubs.cpp +++ b/js/src/asmjs/WasmStubs.cpp @@ -856,6 +856,43 @@ GenerateErrorStub(MacroAssembler& masm, SymbolicAddress address) return offsets; } +// Generate a stub that calls into HandleTrap with the right trap reason. +static Offsets +GenerateTrapStub(MacroAssembler& masm, Trap reason) +{ + masm.haltingAlign(CodeAlignment); + + Offsets offsets; + offsets.begin = masm.currentOffset(); + + // sp can be anything at this point, so ensure it is aligned when calling + // into C++. We unconditionally jump to throw so don't worry about + // restoring sp. + masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1))); + if (ShadowStackSpace) + masm.subFromStackPtr(Imm32(ShadowStackSpace)); + + MIRTypeVector args; + JS_ALWAYS_TRUE(args.append(MIRType::Int32)); + + ABIArgMIRTypeIter i(args); + if (i->kind() == ABIArg::GPR) { + masm.move32(Imm32(int32_t(reason)), i->gpr()); + } else { + masm.store32(Imm32(int32_t(reason)), + Address(masm.getStackPointer(), i->offsetFromArgBase())); + } + + i++; + MOZ_ASSERT(i.done()); + + masm.call(SymbolicAddress::HandleTrap); + masm.jump(JumpTarget::Throw); + + offsets.end = masm.currentOffset(); + return offsets; +} + // If an exception is thrown, simply pop all frames (since asm.js does not // contain try/catch). To do this: // 1. Restore 'sp' to it's value right after the PushRegsInMask in GenerateEntry. @@ -895,20 +932,18 @@ wasm::GenerateJumpTarget(MacroAssembler& masm, JumpTarget target) switch (target) { case JumpTarget::StackOverflow: return GenerateStackOverflow(masm); - case JumpTarget::ConversionError: - return GenerateErrorStub(masm, SymbolicAddress::OnImpreciseConversion); case JumpTarget::OutOfBounds: return GenerateErrorStub(masm, SymbolicAddress::OnOutOfBounds); case JumpTarget::BadIndirectCall: return GenerateErrorStub(masm, SymbolicAddress::BadIndirectCall); - case JumpTarget::UnreachableTrap: - return GenerateErrorStub(masm, SymbolicAddress::UnreachableTrap); - case JumpTarget::InvalidConversionToIntegerTrap: - return GenerateErrorStub(masm, SymbolicAddress::InvalidConversionToIntegerTrap); - case JumpTarget::IntegerOverflowTrap: - return GenerateErrorStub(masm, SymbolicAddress::IntegerOverflowTrap); case JumpTarget::Throw: return GenerateThrow(masm); + case JumpTarget::Unreachable: + case JumpTarget::IntegerOverflow: + case JumpTarget::InvalidConversionToInteger: + case JumpTarget::IntegerDivideByZero: + case JumpTarget::ImpreciseSimdConversion: + return GenerateTrapStub(masm, Trap(target)); case JumpTarget::Limit: break; } diff --git a/js/src/asmjs/WasmTextToBinary.h b/js/src/asmjs/WasmTextToBinary.h index edea506723..8ad0572922 100644 --- a/js/src/asmjs/WasmTextToBinary.h +++ b/js/src/asmjs/WasmTextToBinary.h @@ -29,7 +29,7 @@ namespace wasm { // null-terminated char16_t array) into serialized bytes. If there is an error // other than out-of-memory an error message string will be stored in 'error'. -extern bool +extern MOZ_MUST_USE bool TextToBinary(const char16_t* text, Bytes* bytes, UniqueChars* error); } // namespace wasm diff --git a/js/src/asmjs/WasmTypes.cpp b/js/src/asmjs/WasmTypes.cpp index c293212141..6846476001 100644 --- a/js/src/asmjs/WasmTypes.cpp +++ b/js/src/asmjs/WasmTypes.cpp @@ -65,13 +65,6 @@ OnOutOfBounds() JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); } -static void -OnImpreciseConversion() -{ - JSContext* cx = JSRuntime::innermostWasmActivation()->cx(); - JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_SIMD_FAILED_CONVERSION); -} - static void BadIndirectCall() { @@ -80,24 +73,35 @@ BadIndirectCall() } static void -UnreachableTrap() +HandleTrap(int32_t trapIndex) { JSContext* cx = JSRuntime::innermostWasmActivation()->cx(); - JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_UNREACHABLE); -} -static void -IntegerOverflowTrap() -{ - JSContext* cx = JSRuntime::innermostWasmActivation()->cx(); - JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_INTEGER_OVERFLOW); -} + MOZ_ASSERT(trapIndex < int32_t(Trap::Limit) && trapIndex >= 0); + Trap trap = Trap(trapIndex); -static void -InvalidConversionToIntegerTrap() -{ - JSContext* cx = JSRuntime::innermostWasmActivation()->cx(); - JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_INVALID_CONVERSION); + unsigned errorNumber; + switch (trap) { + case Trap::Unreachable: + errorNumber = JSMSG_WASM_UNREACHABLE; + break; + case Trap::IntegerOverflow: + errorNumber = JSMSG_WASM_INTEGER_OVERFLOW; + break; + case Trap::InvalidConversionToInteger: + errorNumber = JSMSG_WASM_INVALID_CONVERSION; + break; + case Trap::IntegerDivideByZero: + errorNumber = JSMSG_WASM_INT_DIVIDE_BY_ZERO; + break; + case Trap::ImpreciseSimdConversion: + errorNumber = JSMSG_SIMD_FAILED_CONVERSION; + break; + default: + MOZ_CRASH("unexpected trap"); + } + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, errorNumber); } static int32_t @@ -246,18 +250,12 @@ wasm::AddressOf(SymbolicAddress imm, ExclusiveContext* cx) return FuncCast(WasmReportOverRecursed, Args_General0); case SymbolicAddress::OnOutOfBounds: return FuncCast(OnOutOfBounds, Args_General0); - case SymbolicAddress::OnImpreciseConversion: - return FuncCast(OnImpreciseConversion, Args_General0); case SymbolicAddress::BadIndirectCall: return FuncCast(BadIndirectCall, Args_General0); - case SymbolicAddress::UnreachableTrap: - return FuncCast(UnreachableTrap, Args_General0); - case SymbolicAddress::IntegerOverflowTrap: - return FuncCast(IntegerOverflowTrap, Args_General0); - case SymbolicAddress::InvalidConversionToIntegerTrap: - return FuncCast(InvalidConversionToIntegerTrap, Args_General0); case SymbolicAddress::HandleExecutionInterrupt: return FuncCast(WasmHandleExecutionInterrupt, Args_General0); + case SymbolicAddress::HandleTrap: + return FuncCast(HandleTrap, Args_General1); case SymbolicAddress::InvokeImport_Void: return FuncCast(InvokeImport_Void, Args_General3); case SymbolicAddress::InvokeImport_I32: diff --git a/js/src/asmjs/WasmTypes.h b/js/src/asmjs/WasmTypes.h index 8f90f06053..bfe9f3c561 100644 --- a/js/src/asmjs/WasmTypes.h +++ b/js/src/asmjs/WasmTypes.h @@ -263,7 +263,7 @@ class Sig Sig(Sig&& rhs) : args_(Move(rhs.args_)), ret_(rhs.ret_) {} Sig(ValTypeVector&& args, ExprType ret) : args_(Move(args)), ret_(ret) {} - bool clone(const Sig& rhs) { + MOZ_MUST_USE bool clone(const Sig& rhs) { ret_ = rhs.ret_; MOZ_ASSERT(args_.empty()); return args_.appendAll(rhs.args_); @@ -611,12 +611,9 @@ enum class SymbolicAddress StackLimit, ReportOverRecursed, OnOutOfBounds, - OnImpreciseConversion, BadIndirectCall, - UnreachableTrap, - IntegerOverflowTrap, - InvalidConversionToIntegerTrap, HandleExecutionInterrupt, + HandleTrap, InvokeImport_Void, InvokeImport_I32, InvokeImport_I64, @@ -631,7 +628,28 @@ AddressOf(SymbolicAddress imm, ExclusiveContext* cx); // Extracts low and high from an int64 object {low: int32, high: int32}, for // testing purposes mainly. -bool ReadI64Object(JSContext* cx, HandleValue v, int64_t* val); +MOZ_MUST_USE bool ReadI64Object(JSContext* cx, HandleValue v, int64_t* val); + +// A wasm::Trap is a reason for why we reached a trap in executed code. Each +// different trap is mapped to a different error message. + +enum class Trap +{ + // The Unreachable opcode has been executed. + Unreachable, + // An integer arithmetic operation led to an overflow. + IntegerOverflow, + // Trying to coerce NaN to an integer. + InvalidConversionToInteger, + // Integer division by zero. + IntegerDivideByZero, + + // (asm.js only) SIMD float to int conversion failed because the input + // wasn't in bounds. + ImpreciseSimdConversion, + + Limit +}; // A wasm::JumpTarget represents one of a special set of stubs that can be // jumped to from any function. Because wasm modules can be larger than the @@ -640,13 +658,16 @@ bool ReadI64Object(JSContext* cx, HandleValue v, int64_t* val); enum class JumpTarget { + // Traps + Unreachable = unsigned(Trap::Unreachable), + IntegerOverflow = unsigned(Trap::IntegerOverflow), + InvalidConversionToInteger = unsigned(Trap::InvalidConversionToInteger), + IntegerDivideByZero = unsigned(Trap::IntegerDivideByZero), + ImpreciseSimdConversion = unsigned(Trap::ImpreciseSimdConversion), + // Non-traps StackOverflow, OutOfBounds, - ConversionError, BadIndirectCall, - UnreachableTrap, - IntegerOverflowTrap, - InvalidConversionToIntegerTrap, Throw, Limit }; diff --git a/js/src/builtin/AtomicsObject.h b/js/src/builtin/AtomicsObject.h index 17dc8cc1c2..4695dd3b0e 100644 --- a/js/src/builtin/AtomicsObject.h +++ b/js/src/builtin/AtomicsObject.h @@ -17,21 +17,21 @@ class AtomicsObject : public JSObject public: static const Class class_; static JSObject* initClass(JSContext* cx, Handle global); - static bool toString(JSContext* cx, unsigned int argc, Value* vp); + static MOZ_MUST_USE bool toString(JSContext* cx, unsigned int argc, Value* vp); }; -bool atomics_compareExchange(JSContext* cx, unsigned argc, Value* vp); -bool atomics_exchange(JSContext* cx, unsigned argc, Value* vp); -bool atomics_load(JSContext* cx, unsigned argc, Value* vp); -bool atomics_store(JSContext* cx, unsigned argc, Value* vp); -bool atomics_add(JSContext* cx, unsigned argc, Value* vp); -bool atomics_sub(JSContext* cx, unsigned argc, Value* vp); -bool atomics_and(JSContext* cx, unsigned argc, Value* vp); -bool atomics_or(JSContext* cx, unsigned argc, Value* vp); -bool atomics_xor(JSContext* cx, unsigned argc, Value* vp); -bool atomics_isLockFree(JSContext* cx, unsigned argc, Value* vp); -bool atomics_wait(JSContext* cx, unsigned argc, Value* vp); -bool atomics_wake(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool atomics_compareExchange(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool atomics_exchange(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool atomics_load(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool atomics_store(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool atomics_add(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool atomics_sub(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool atomics_and(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool atomics_or(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool atomics_xor(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool atomics_isLockFree(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool atomics_wait(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool atomics_wake(JSContext* cx, unsigned argc, Value* vp); /* asm.js callouts */ int32_t atomics_add_asm_callout(int32_t vt, int32_t offset, int32_t value); @@ -45,14 +45,14 @@ int32_t atomics_xchg_asm_callout(int32_t vt, int32_t offset, int32_t value); class FutexRuntime { public: - static bool initialize(); + static MOZ_MUST_USE bool initialize(); static void destroy(); static void lock(); static void unlock(); FutexRuntime(); - bool initInstance(); + MOZ_MUST_USE bool initInstance(); void destroyInstance(); // Parameters to wake(). @@ -77,7 +77,7 @@ public: // wait() will not wake up spuriously. It will return true and // set *result to a return code appropriate for // Atomics.wait() on success, and return false on error. - bool wait(JSContext* cx, double timeout, WaitResult* result); + MOZ_MUST_USE bool wait(JSContext* cx, double timeout, WaitResult* result); // Wake the thread represented by this Runtime. // diff --git a/js/src/builtin/Eval.h b/js/src/builtin/Eval.h index e37a99ac91..03a75d5999 100644 --- a/js/src/builtin/Eval.h +++ b/js/src/builtin/Eval.h @@ -17,17 +17,17 @@ namespace js { // JSOP_EVAL which in turn calls DirectEval. Thus, even though IndirectEval is // the callee function object for *all* calls to eval, it is by construction // only ever called in the case indirect eval. -extern bool +extern MOZ_MUST_USE bool IndirectEval(JSContext* cx, unsigned argc, Value* vp); // Performs a direct eval of |v| (a string containing code, or another value // that will be vacuously returned), which must correspond to the currently- // executing stack frame, which must be a script frame. -extern bool +extern MOZ_MUST_USE bool DirectEval(JSContext* cx, HandleValue v, MutableHandleValue vp); // Performs a direct eval called from Ion code. -extern bool +extern MOZ_MUST_USE bool DirectEvalStringFromIon(JSContext* cx, HandleObject scopeObj, HandleScript callerScript, HandleValue newTargetValue, HandleString str, diff --git a/js/src/builtin/Intl.h b/js/src/builtin/Intl.h index 0e5d2251c1..4800e29154 100644 --- a/js/src/builtin/Intl.h +++ b/js/src/builtin/Intl.h @@ -38,7 +38,7 @@ InitIntlClass(JSContext* cx, HandleObject obj); * * Usage: collator = intl_Collator(locales, options) */ -extern bool +extern MOZ_MUST_USE bool intl_Collator(JSContext* cx, unsigned argc, Value* vp); /** @@ -49,7 +49,7 @@ intl_Collator(JSContext* cx, unsigned argc, Value* vp); * * Usage: availableLocales = intl_Collator_availableLocales() */ -extern bool +extern MOZ_MUST_USE bool intl_Collator_availableLocales(JSContext* cx, unsigned argc, Value* vp); /** @@ -60,7 +60,7 @@ intl_Collator_availableLocales(JSContext* cx, unsigned argc, Value* vp); * * Usage: collations = intl_availableCollations(locale) */ -extern bool +extern MOZ_MUST_USE bool intl_availableCollations(JSContext* cx, unsigned argc, Value* vp); /** @@ -73,7 +73,7 @@ intl_availableCollations(JSContext* cx, unsigned argc, Value* vp); * * Usage: result = intl_CompareStrings(collator, x, y) */ -extern bool +extern MOZ_MUST_USE bool intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp); @@ -86,7 +86,7 @@ intl_CompareStrings(JSContext* cx, unsigned argc, Value* vp); * * Usage: numberFormat = intl_NumberFormat(locales, options) */ -extern bool +extern MOZ_MUST_USE bool intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp); /** @@ -97,7 +97,7 @@ intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp); * * Usage: availableLocales = intl_NumberFormat_availableLocales() */ -extern bool +extern MOZ_MUST_USE bool intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp); /** @@ -107,7 +107,7 @@ intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp); * * Usage: defaultNumberingSystem = intl_numberingSystem(locale) */ -extern bool +extern MOZ_MUST_USE bool intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp); /** @@ -118,7 +118,7 @@ intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp); * * Usage: formatted = intl_FormatNumber(numberFormat, x) */ -extern bool +extern MOZ_MUST_USE bool intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp); @@ -131,7 +131,7 @@ intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp); * * Usage: dateTimeFormat = intl_DateTimeFormat(locales, options) */ -extern bool +extern MOZ_MUST_USE bool intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp); /** @@ -142,7 +142,7 @@ intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp); * * Usage: availableLocales = intl_DateTimeFormat_availableLocales() */ -extern bool +extern MOZ_MUST_USE bool intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp); /** @@ -153,7 +153,7 @@ intl_DateTimeFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp); * * Usage: calendars = intl_availableCalendars(locale) */ -extern bool +extern MOZ_MUST_USE bool intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp); /** @@ -164,7 +164,7 @@ intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp); * * Usage: pattern = intl_patternForSkeleton(locale, skeleton) */ -extern bool +extern MOZ_MUST_USE bool intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp); /** @@ -176,7 +176,7 @@ intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp); * * Usage: formatted = intl_FormatDateTime(dateTimeFormat, x) */ -extern bool +extern MOZ_MUST_USE bool intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp); /** diff --git a/js/src/builtin/MapObject.cpp b/js/src/builtin/MapObject.cpp index 71aa3ea86d..28db814b62 100644 --- a/js/src/builtin/MapObject.cpp +++ b/js/src/builtin/MapObject.cpp @@ -843,29 +843,6 @@ js::InitMapClass(JSContext* cx, HandleObject obj) /*** SetIterator *********************************************************************************/ -namespace { - -class SetIteratorObject : public NativeObject -{ - public: - static const Class class_; - - enum { TargetSlot, KindSlot, RangeSlot, SlotCount }; - static const JSFunctionSpec methods[]; - static SetIteratorObject* create(JSContext* cx, HandleObject setobj, ValueSet* data, - SetObject::IteratorKind kind); - static bool next(JSContext* cx, unsigned argc, Value* vp); - static void finalize(FreeOp* fop, JSObject* obj); - - private: - static inline bool is(HandleValue v); - inline ValueSet::Range* range(); - inline SetObject::IteratorKind kind() const; - static bool next_impl(JSContext* cx, const CallArgs& args); -}; - -} /* anonymous namespace */ - static const ClassOps SetIteratorObjectClassOps = { nullptr, /* addProperty */ nullptr, /* delProperty */ diff --git a/js/src/builtin/MapObject.h b/js/src/builtin/MapObject.h index 5eeeb02ebe..bf464de369 100644 --- a/js/src/builtin/MapObject.h +++ b/js/src/builtin/MapObject.h @@ -37,7 +37,7 @@ class HashableValue HashableValue() : value(UndefinedValue()) {} - bool setValue(JSContext* cx, HandleValue v); + MOZ_MUST_USE bool setValue(JSContext* cx, HandleValue v); HashNumber hash() const; bool operator==(const HashableValue& other) const; HashableValue mark(JSTracer* trc) const; @@ -51,7 +51,7 @@ class HashableValue template <> class RootedBase { public: - bool setValue(JSContext* cx, HandleValue v) { + MOZ_MUST_USE bool setValue(JSContext* cx, HandleValue v) { return static_cast*>(this)->get().setValue(cx, v); } Value value() const { @@ -88,25 +88,27 @@ class MapObject : public NativeObject { static JSObject* initClass(JSContext* cx, JSObject* obj); static const Class class_; - static bool getKeysAndValuesInterleaved(JSContext* cx, HandleObject obj, + static MOZ_MUST_USE bool getKeysAndValuesInterleaved(JSContext* cx, HandleObject obj, JS::MutableHandle> entries); - static bool entries(JSContext* cx, unsigned argc, Value* vp); - static bool has(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool entries(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool has(JSContext* cx, unsigned argc, Value* vp); static MapObject* create(JSContext* cx, HandleObject proto = nullptr); // Publicly exposed Map calls for JSAPI access (webidl maplike/setlike // interfaces, etc.) static uint32_t size(JSContext *cx, HandleObject obj); - static bool get(JSContext *cx, HandleObject obj, HandleValue key, MutableHandleValue rval); - static bool has(JSContext *cx, HandleObject obj, HandleValue key, bool* rval); - static bool delete_(JSContext *cx, HandleObject obj, HandleValue key, bool* rval); + static MOZ_MUST_USE bool get(JSContext *cx, HandleObject obj, HandleValue key, + MutableHandleValue rval); + static MOZ_MUST_USE bool has(JSContext *cx, HandleObject obj, HandleValue key, bool* rval); + static MOZ_MUST_USE bool delete_(JSContext *cx, HandleObject obj, HandleValue key, bool* rval); // Set call for public JSAPI exposure. Does not actually return map object // as stated in spec, expects caller to return a value. for instance, with // webidl maplike/setlike, should return interface object. - static bool set(JSContext *cx, HandleObject obj, HandleValue key, HandleValue val); - static bool clear(JSContext *cx, HandleObject obj); - static bool iterator(JSContext *cx, IteratorKind kind, HandleObject obj, MutableHandleValue iter); + static MOZ_MUST_USE bool set(JSContext *cx, HandleObject obj, HandleValue key, HandleValue val); + static MOZ_MUST_USE bool clear(JSContext *cx, HandleObject obj); + static MOZ_MUST_USE bool iterator(JSContext *cx, IteratorKind kind, HandleObject obj, + MutableHandleValue iter); private: static const ClassOps classOps_; @@ -119,29 +121,29 @@ class MapObject : public NativeObject { static ValueMap & extract(CallReceiver call); static void mark(JSTracer* trc, JSObject* obj); static void finalize(FreeOp* fop, JSObject* obj); - static bool construct(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp); static bool is(HandleValue v); static bool is(HandleObject o); - static bool iterator_impl(JSContext* cx, const CallArgs& args, IteratorKind kind); + static MOZ_MUST_USE bool iterator_impl(JSContext* cx, const CallArgs& args, IteratorKind kind); - static bool size_impl(JSContext* cx, const CallArgs& args); - static bool size(JSContext* cx, unsigned argc, Value* vp); - static bool get_impl(JSContext* cx, const CallArgs& args); - static bool get(JSContext* cx, unsigned argc, Value* vp); - static bool has_impl(JSContext* cx, const CallArgs& args); - static bool set_impl(JSContext* cx, const CallArgs& args); - static bool set(JSContext* cx, unsigned argc, Value* vp); - static bool delete_impl(JSContext* cx, const CallArgs& args); - static bool delete_(JSContext* cx, unsigned argc, Value* vp); - static bool keys_impl(JSContext* cx, const CallArgs& args); - static bool keys(JSContext* cx, unsigned argc, Value* vp); - static bool values_impl(JSContext* cx, const CallArgs& args); - static bool values(JSContext* cx, unsigned argc, Value* vp); - static bool entries_impl(JSContext* cx, const CallArgs& args); - static bool clear_impl(JSContext* cx, const CallArgs& args); - static bool clear(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool size_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool size(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool get_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool get(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool has_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool set_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool set(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool delete_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool delete_(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool keys_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool keys(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool values_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool values(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool entries_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool clear_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool clear(JSContext* cx, unsigned argc, Value* vp); }; class MapIteratorObject : public NativeObject @@ -163,8 +165,8 @@ class MapIteratorObject : public NativeObject MapObject::IteratorKind kind); static void finalize(FreeOp* fop, JSObject* obj); - static bool next(JSContext* cx, Handle mapIterator, - HandleArrayObject resultPairObj); + static MOZ_MUST_USE bool next(JSContext* cx, Handle mapIterator, + HandleArrayObject resultPairObj); static JSObject* createResultPair(JSContext* cx); @@ -178,19 +180,21 @@ class SetObject : public NativeObject { static JSObject* initClass(JSContext* cx, JSObject* obj); static const Class class_; - static bool keys(JSContext *cx, HandleObject obj, JS::MutableHandle> keys); - static bool values(JSContext *cx, unsigned argc, Value *vp); - static bool add(JSContext *cx, HandleObject obj, HandleValue key); - static bool has(JSContext *cx, unsigned argc, Value *vp); + static MOZ_MUST_USE bool keys(JSContext *cx, HandleObject obj, + JS::MutableHandle> keys); + static MOZ_MUST_USE bool values(JSContext *cx, unsigned argc, Value *vp); + static MOZ_MUST_USE bool add(JSContext *cx, HandleObject obj, HandleValue key); + static MOZ_MUST_USE bool has(JSContext *cx, unsigned argc, Value *vp); // Publicly exposed Set calls for JSAPI access (webidl maplike/setlike // interfaces, etc.) static SetObject* create(JSContext *cx, HandleObject proto = nullptr); static uint32_t size(JSContext *cx, HandleObject obj); - static bool has(JSContext *cx, HandleObject obj, HandleValue key, bool* rval); - static bool clear(JSContext *cx, HandleObject obj); - static bool iterator(JSContext *cx, IteratorKind kind, HandleObject obj, MutableHandleValue iter); - static bool delete_(JSContext *cx, HandleObject obj, HandleValue key, bool *rval); + static MOZ_MUST_USE bool has(JSContext *cx, HandleObject obj, HandleValue key, bool* rval); + static MOZ_MUST_USE bool clear(JSContext *cx, HandleObject obj); + static MOZ_MUST_USE bool iterator(JSContext *cx, IteratorKind kind, HandleObject obj, + MutableHandleValue iter); + static MOZ_MUST_USE bool delete_(JSContext *cx, HandleObject obj, HandleValue key, bool *rval); private: static const ClassOps classOps_; @@ -209,20 +213,39 @@ class SetObject : public NativeObject { static bool is(HandleValue v); static bool is(HandleObject o); - static bool iterator_impl(JSContext* cx, const CallArgs& args, IteratorKind kind); + static MOZ_MUST_USE bool iterator_impl(JSContext* cx, const CallArgs& args, IteratorKind kind); - static bool size_impl(JSContext* cx, const CallArgs& args); - static bool size(JSContext* cx, unsigned argc, Value* vp); - static bool has_impl(JSContext* cx, const CallArgs& args); - static bool add_impl(JSContext* cx, const CallArgs& args); - static bool add(JSContext* cx, unsigned argc, Value* vp); - static bool delete_impl(JSContext* cx, const CallArgs& args); - static bool delete_(JSContext* cx, unsigned argc, Value* vp); - static bool values_impl(JSContext* cx, const CallArgs& args); - static bool entries_impl(JSContext* cx, const CallArgs& args); - static bool entries(JSContext* cx, unsigned argc, Value* vp); - static bool clear_impl(JSContext* cx, const CallArgs& args); - static bool clear(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool size_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool size(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool has_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool add_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool add(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool delete_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool delete_(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool values_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool entries_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool entries(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool clear_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool clear(JSContext* cx, unsigned argc, Value* vp); +}; + +class SetIteratorObject : public NativeObject +{ + public: + static const Class class_; + + enum { TargetSlot, KindSlot, RangeSlot, SlotCount }; + static const JSFunctionSpec methods[]; + static SetIteratorObject* create(JSContext* cx, HandleObject setobj, ValueSet* data, + SetObject::IteratorKind kind); + static bool next(JSContext* cx, unsigned argc, Value* vp); + static void finalize(FreeOp* fop, JSObject* obj); + + private: + static inline bool is(HandleValue v); + inline ValueSet::Range* range(); + inline SetObject::IteratorKind kind() const; + static MOZ_MUST_USE bool next_impl(JSContext* cx, const CallArgs& args); }; extern bool diff --git a/js/src/builtin/Module.js b/js/src/builtin/Module.js index 393779c44c..c8e9052e5b 100644 --- a/js/src/builtin/Module.js +++ b/js/src/builtin/Module.js @@ -241,6 +241,7 @@ function ModuleDeclarationInstantiation() // Step 16.iv InstantiateModuleFunctionDeclarations(module); } +_SetCanonicalName(ModuleDeclarationInstantiation, "ModuleDeclarationInstantiation"); // 15.2.1.16.5 ModuleEvaluation() function ModuleEvaluation() @@ -268,6 +269,7 @@ function ModuleEvaluation() return EvaluateModule(module); } +_SetCanonicalName(ModuleEvaluation, "ModuleEvaluation"); function ModuleNamespaceEnumerate() { diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp index 38460cf53b..b741edf604 100644 --- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -790,6 +790,18 @@ ModuleObject::evaluated() const return getReservedSlot(EvaluatedSlot).toBoolean(); } +Value +ModuleObject::hostDefinedField() const +{ + return getReservedSlot(HostDefinedSlot); +} + +void +ModuleObject::setHostDefinedField(JS::Value value) +{ + setReservedSlot(HostDefinedSlot, value); +} + ModuleEnvironmentObject& ModuleObject::initialEnvironment() const { @@ -920,6 +932,29 @@ ModuleObject::createNamespace(JSContext* cx, HandleModuleObject self, HandleObje return ns; } +static bool +InvokeSelfHostedMethod(JSContext* cx, HandleModuleObject self, HandlePropertyName name) +{ + RootedValue fval(cx); + if (!GlobalObject::getSelfHostedFunction(cx, cx->global(), name, name, 0, &fval)) + return false; + + RootedValue ignored(cx); + return Call(cx, fval, self, &ignored); +} + +/* static */ bool +ModuleObject::DeclarationInstantiation(JSContext* cx, HandleModuleObject self) +{ + return InvokeSelfHostedMethod(cx, self, cx->names().ModuleDeclarationInstantiation); +} + +/* static */ bool +ModuleObject::Evaluation(JSContext* cx, HandleModuleObject self) +{ + return InvokeSelfHostedMethod(cx, self, cx->names().ModuleEvaluation); +} + DEFINE_GETTER_FUNCTIONS(ModuleObject, namespace_, NamespaceSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, evaluated, EvaluatedSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, requestedModules, RequestedModulesSlot) @@ -961,15 +996,6 @@ GlobalObject::initModuleProto(JSContext* cx, Handle global) return true; } -bool -js::InitModuleClasses(JSContext* cx, HandleObject obj) -{ - Rooted global(cx, &obj->as()); - return GlobalObject::initModuleProto(cx, global) && - GlobalObject::initImportEntryProto(cx, global) && - GlobalObject::initExportEntryProto(cx, global); -} - #undef DEFINE_GETTER_FUNCTIONS #undef DEFINE_STRING_ACCESSOR_METHOD #undef DEFINE_ARRAY_SLOT_ACCESSOR diff --git a/js/src/builtin/ModuleObject.h b/js/src/builtin/ModuleObject.h index f3e53c3e00..53bb97835f 100644 --- a/js/src/builtin/ModuleObject.h +++ b/js/src/builtin/ModuleObject.h @@ -211,6 +211,7 @@ class ModuleObject : public NativeObject EnvironmentSlot, NamespaceSlot, EvaluatedSlot, + HostDefinedSlot, RequestedModulesSlot, ImportEntriesSlot, LocalExportEntriesSlot, @@ -245,6 +246,7 @@ class ModuleObject : public NativeObject ModuleEnvironmentObject* environment() const; ModuleNamespaceObject* namespace_(); bool evaluated() const; + Value hostDefinedField() const; ArrayObject& requestedModules() const; ArrayObject& importEntries() const; ArrayObject& localExportEntries() const; @@ -254,14 +256,27 @@ class ModuleObject : public NativeObject JSObject* namespaceExports(); IndirectBindingMap* namespaceBindings(); + static bool DeclarationInstantiation(JSContext* cx, HandleModuleObject self); + static bool Evaluation(JSContext* cx, HandleModuleObject self); + + void setHostDefinedField(JS::Value value); + + // For intrinsic_CreateModuleEnvironment. void createEnvironment(); + // For BytecodeEmitter. bool noteFunctionDeclaration(ExclusiveContext* cx, HandleAtom name, HandleFunction fun); + + // For intrinsic_InstantiateModuleFunctionDeclarations. static bool instantiateFunctionDeclarations(JSContext* cx, HandleModuleObject self); + // For intrinsic_SetModuleEvaluated. void setEvaluated(); + + // For intrinsic_EvaluateModule. static bool evaluate(JSContext* cx, HandleModuleObject self, MutableHandleValue rval); + // For intrinsic_NewModuleNamespace. static ModuleNamespaceObject* createNamespace(JSContext* cx, HandleModuleObject self, HandleObject exports); @@ -326,8 +341,6 @@ class MOZ_STACK_CLASS ModuleBuilder ArrayObject* createArray(const GCVector& vector); }; -bool InitModuleClasses(JSContext* cx, HandleObject obj); - } // namespace js template<> diff --git a/js/src/builtin/Object.h b/js/src/builtin/Object.h index 360a7c43a4..0ac5c7472a 100644 --- a/js/src/builtin/Object.h +++ b/js/src/builtin/Object.h @@ -19,13 +19,13 @@ class Value; namespace js { // Object constructor native. Exposed only so the JIT can know its address. -bool +MOZ_MUST_USE bool obj_construct(JSContext* cx, unsigned argc, JS::Value* vp); -bool +MOZ_MUST_USE bool obj_propertyIsEnumerable(JSContext* cx, unsigned argc, Value* vp); -bool +MOZ_MUST_USE bool obj_valueOf(JSContext* cx, unsigned argc, JS::Value* vp); PlainObject* @@ -36,32 +36,32 @@ PlainObject* ObjectCreateWithTemplate(JSContext* cx, HandlePlainObject templateObj); // Object methods exposed so they can be installed in the self-hosting global. -bool +MOZ_MUST_USE bool obj_create(JSContext* cx, unsigned argc, JS::Value* vp); -bool +MOZ_MUST_USE bool obj_defineProperty(JSContext* cx, unsigned argc, JS::Value* vp); -bool +MOZ_MUST_USE bool obj_getOwnPropertyNames(JSContext* cx, unsigned argc, JS::Value* vp); -bool +MOZ_MUST_USE bool obj_getOwnPropertyDescriptor(JSContext* cx, unsigned argc, JS::Value* vp); -bool +MOZ_MUST_USE bool obj_getPrototypeOf(JSContext* cx, unsigned argc, JS::Value* vp); -bool +MOZ_MUST_USE bool obj_hasOwnProperty(JSContext* cx, unsigned argc, JS::Value* vp); -bool +MOZ_MUST_USE bool obj_isExtensible(JSContext* cx, unsigned argc, JS::Value* vp); -bool +MOZ_MUST_USE bool obj_toString(JSContext* cx, unsigned argc, JS::Value* vp); // Exposed so SelfHosting.cpp can use it in the OwnPropertyKeys intrinsic -bool +MOZ_MUST_USE bool GetOwnPropertyKeys(JSContext* cx, const JS::CallArgs& args, unsigned flags); /* @@ -69,7 +69,7 @@ GetOwnPropertyKeys(JSContext* cx, const JS::CallArgs& args, unsigned flags); * exposing a jsid to script for Object.getOwnProperty{Names,Symbols} * or scriptable proxy traps. */ -bool +MOZ_MUST_USE bool IdToStringOrSymbol(JSContext* cx, JS::HandleId id, JS::MutableHandleValue result); #if JS_HAS_TOSOURCE @@ -78,7 +78,7 @@ JSString* ObjectToSource(JSContext* cx, JS::HandleObject obj); #endif // JS_HAS_TOSOURCE -extern bool +extern MOZ_MUST_USE bool WatchHandler(JSContext* cx, JSObject* obj, jsid id, JS::Value old, JS::Value* nvp, void* closure); diff --git a/js/src/builtin/Profilers.h b/js/src/builtin/Profilers.h index 77d5d59500..ecd0d0982d 100644 --- a/js/src/builtin/Profilers.h +++ b/js/src/builtin/Profilers.h @@ -30,21 +30,21 @@ typedef int pid_t; * * Returns true if no profilers fail to start. */ -extern JS_PUBLIC_API(bool) +extern MOZ_MUST_USE JS_PUBLIC_API(bool) JS_StartProfiling(const char* profileName, pid_t pid); /** * Stop any profilers that were previously started with JS_StartProfiling. * Returns true if no profilers fail to stop. */ -extern JS_PUBLIC_API(bool) +extern MOZ_MUST_USE JS_PUBLIC_API(bool) JS_StopProfiling(const char* profileName); /** * Write the current profile data to the given file, if applicable to whatever * profiler is being used. */ -extern JS_PUBLIC_API(bool) +extern MOZ_MUST_USE JS_PUBLIC_API(bool) JS_DumpProfile(const char* outfile, const char* profileName); /** @@ -52,13 +52,13 @@ JS_DumpProfile(const char* outfile, const char* profileName); * whether any profilers failed to pause. (Profilers that do not support * pause/resume do not count.) */ -extern JS_PUBLIC_API(bool) +extern MOZ_MUST_USE JS_PUBLIC_API(bool) JS_PauseProfilers(const char* profileName); /** * Resume suspended profilers */ -extern JS_PUBLIC_API(bool) +extern MOZ_MUST_USE JS_PUBLIC_API(bool) JS_ResumeProfilers(const char* profileName); /** @@ -71,23 +71,23 @@ JS_UnsafeGetLastProfilingError(); #ifdef MOZ_CALLGRIND -extern JS_FRIEND_API(bool) +extern MOZ_MUST_USE JS_FRIEND_API(bool) js_StopCallgrind(); -extern JS_FRIEND_API(bool) +extern MOZ_MUST_USE JS_FRIEND_API(bool) js_StartCallgrind(); -extern JS_FRIEND_API(bool) +extern MOZ_MUST_USE JS_FRIEND_API(bool) js_DumpCallgrind(const char* outfile); #endif /* MOZ_CALLGRIND */ #ifdef __linux__ -extern JS_FRIEND_API(bool) +extern MOZ_MUST_USE JS_FRIEND_API(bool) js_StartPerf(); -extern JS_FRIEND_API(bool) +extern MOZ_MUST_USE JS_FRIEND_API(bool) js_StopPerf(); #endif /* __linux__ */ diff --git a/js/src/builtin/Promise.h b/js/src/builtin/Promise.h index eefd44c544..f141db6169 100644 --- a/js/src/builtin/Promise.h +++ b/js/src/builtin/Promise.h @@ -39,8 +39,8 @@ class PromiseObject : public NativeObject return getFixedSlot(PROMISE_RESULT_SLOT); } - bool resolve(JSContext* cx, HandleValue resolutionValue); - bool reject(JSContext* cx, HandleValue rejectionValue); + MOZ_MUST_USE bool resolve(JSContext* cx, HandleValue resolutionValue); + MOZ_MUST_USE bool reject(JSContext* cx, HandleValue rejectionValue); double allocationTime() { return getFixedSlot(PROMISE_ALLOCATION_TIME_SLOT).toNumber(); } double resolutionTime() { return getFixedSlot(PROMISE_RESOLUTION_TIME_SLOT).toNumber(); } @@ -54,7 +54,7 @@ class PromiseObject : public NativeObject MOZ_ASSERT(state() != JS::PromiseState::Pending); return resolutionTime() - allocationTime(); } - bool dependentPromises(JSContext* cx, MutableHandle> values); + MOZ_MUST_USE bool dependentPromises(JSContext* cx, MutableHandle> values); double getID(); }; diff --git a/js/src/builtin/Reflect.h b/js/src/builtin/Reflect.h index b227a6614a..24fdaa2547 100644 --- a/js/src/builtin/Reflect.h +++ b/js/src/builtin/Reflect.h @@ -18,10 +18,10 @@ InitReflect(JSContext* cx, js::HandleObject obj); namespace js { -extern bool +extern MOZ_MUST_USE bool Reflect_getPrototypeOf(JSContext* cx, unsigned argc, Value* vp); -extern bool +extern MOZ_MUST_USE bool Reflect_isExtensible(JSContext* cx, unsigned argc, Value* vp); } diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index d1a5990dda..9c262e9c5f 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -3747,6 +3747,9 @@ reflect_parse(JSContext* cx, uint32_t argc, Value* vp) if (!pn) return false; } else { + if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global())) + return false; + Rooted module(cx, ModuleObject::create(cx, nullptr)); if (!module) return false; diff --git a/js/src/builtin/RegExp.h b/js/src/builtin/RegExp.h index 80d88e104b..b4c1507672 100644 --- a/js/src/builtin/RegExp.h +++ b/js/src/builtin/RegExp.h @@ -30,41 +30,41 @@ enum RegExpStaticsUpdate { UpdateRegExpStatics, DontUpdateRegExpStatics }; * |input| may be nullptr if there is no JSString corresponding to * |chars| and |length|. */ -bool +MOZ_MUST_USE bool ExecuteRegExpLegacy(JSContext* cx, RegExpStatics* res, RegExpObject& reobj, HandleLinearString input, size_t* lastIndex, bool test, MutableHandleValue rval); /* Translation from MatchPairs to a JS array in regexp_exec()'s output format. */ -bool +MOZ_MUST_USE bool CreateRegExpMatchResult(JSContext* cx, HandleString input, const MatchPairs& matches, MutableHandleValue rval); -extern bool +extern MOZ_MUST_USE bool RegExpMatcher(JSContext* cx, unsigned argc, Value* vp); -extern bool +extern MOZ_MUST_USE bool RegExpMatcherRaw(JSContext* cx, HandleObject regexp, HandleString input, int32_t lastIndex, MatchPairs* maybeMatches, MutableHandleValue output); -extern bool +extern MOZ_MUST_USE bool RegExpSearcher(JSContext* cx, unsigned argc, Value* vp); -extern bool +extern MOZ_MUST_USE bool RegExpSearcherRaw(JSContext* cx, HandleObject regexp, HandleString input, int32_t lastIndex, MatchPairs* maybeMatches, int32_t* result); -extern bool +extern MOZ_MUST_USE bool RegExpTester(JSContext* cx, unsigned argc, Value* vp); -extern bool +extern MOZ_MUST_USE bool RegExpTesterRaw(JSContext* cx, HandleObject regexp, HandleString input, int32_t lastIndex, int32_t* endIndex); -extern bool +extern MOZ_MUST_USE bool intrinsic_GetElemBaseForLambda(JSContext* cx, unsigned argc, Value* vp); -extern bool +extern MOZ_MUST_USE bool intrinsic_GetStringDataProperty(JSContext* cx, unsigned argc, Value* vp); /* @@ -76,7 +76,7 @@ intrinsic_GetStringDataProperty(JSContext* cx, unsigned argc, Value* vp); * * Usage: match = regexp_exec_no_statics(regexp, string) */ -extern bool +extern MOZ_MUST_USE bool regexp_exec_no_statics(JSContext* cx, unsigned argc, Value* vp); /* @@ -84,7 +84,7 @@ regexp_exec_no_statics(JSContext* cx, unsigned argc, Value* vp); * * Usage: does_match = regexp_test_no_statics(regexp, string) */ -extern bool +extern MOZ_MUST_USE bool regexp_test_no_statics(JSContext* cx, unsigned argc, Value* vp); /* @@ -104,58 +104,58 @@ regexp_construct_self_hosting(JSContext* cx, unsigned argc, Value* vp); * Dedicated function for RegExp.prototype.split optimized path. * sticky flag is ignored. */ -extern bool +extern MOZ_MUST_USE bool regexp_construct_no_sticky(JSContext* cx, unsigned argc, Value* vp); -extern bool +extern MOZ_MUST_USE bool IsRegExp(JSContext* cx, HandleValue value, bool* result); -extern bool +extern MOZ_MUST_USE bool RegExpCreate(JSContext* cx, HandleValue pattern, HandleValue flags, MutableHandleValue rval); -extern bool +extern MOZ_MUST_USE bool RegExpPrototypeOptimizable(JSContext* cx, unsigned argc, Value* vp); -extern bool +extern MOZ_MUST_USE bool RegExpPrototypeOptimizableRaw(JSContext* cx, JSObject* proto, uint8_t* result); -extern bool +extern MOZ_MUST_USE bool RegExpInstanceOptimizable(JSContext* cx, unsigned argc, Value* vp); -extern bool +extern MOZ_MUST_USE bool RegExpInstanceOptimizableRaw(JSContext* cx, JSObject* rx, JSObject* proto, uint8_t* result); -extern bool +extern MOZ_MUST_USE bool RegExpGetSubstitution(JSContext* cx, HandleLinearString matched, HandleLinearString string, size_t position, HandleObject capturesObj, HandleLinearString replacement, size_t firstDollarIndex, MutableHandleValue rval); -extern bool +extern MOZ_MUST_USE bool GetFirstDollarIndex(JSContext* cx, unsigned argc, Value* vp); -extern bool +extern MOZ_MUST_USE bool GetFirstDollarIndexRaw(JSContext* cx, HandleString str, int32_t* index); extern int32_t GetFirstDollarIndexRawFlat(JSLinearString* text); // RegExp ClassSpec members used in RegExpObject.cpp. -extern bool +extern MOZ_MUST_USE bool regexp_construct(JSContext* cx, unsigned argc, Value* vp); extern const JSPropertySpec regexp_static_props[]; extern const JSPropertySpec regexp_properties[]; extern const JSFunctionSpec regexp_methods[]; // Used in RegExpObject::isOriginalFlagGetter. -extern bool +extern MOZ_MUST_USE bool regexp_global(JSContext* cx, unsigned argc, JS::Value* vp); -extern bool +extern MOZ_MUST_USE bool regexp_ignoreCase(JSContext* cx, unsigned argc, JS::Value* vp); -extern bool +extern MOZ_MUST_USE bool regexp_multiline(JSContext* cx, unsigned argc, JS::Value* vp); -extern bool +extern MOZ_MUST_USE bool regexp_sticky(JSContext* cx, unsigned argc, JS::Value* vp); -extern bool +extern MOZ_MUST_USE bool regexp_unicode(JSContext* cx, unsigned argc, JS::Value* vp); } /* namespace js */ diff --git a/js/src/builtin/SIMD.h b/js/src/builtin/SIMD.h index eab6c6e8f7..70ec98249f 100644 --- a/js/src/builtin/SIMD.h +++ b/js/src/builtin/SIMD.h @@ -916,8 +916,9 @@ class SimdObject : public JSObject { public: static const Class class_; - static bool toString(JSContext* cx, unsigned int argc, Value* vp); - static bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId, bool* resolved); + static MOZ_MUST_USE bool toString(JSContext* cx, unsigned int argc, Value* vp); + static MOZ_MUST_USE bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId, + bool* resolved); }; // These classes implement the concept containing the following constraints: @@ -941,7 +942,7 @@ struct Float32x4 { typedef float Elem; static const unsigned lanes = 4; static const SimdType type = SimdType::Float32x4; - static bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { double d; if (!ToNumber(cx, v, &d)) return false; @@ -957,7 +958,7 @@ struct Float64x2 { typedef double Elem; static const unsigned lanes = 2; static const SimdType type = SimdType::Float64x2; - static bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { return ToNumber(cx, v, out); } static Value ToValue(Elem value) { @@ -969,7 +970,7 @@ struct Int8x16 { typedef int8_t Elem; static const unsigned lanes = 16; static const SimdType type = SimdType::Int8x16; - static bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { return ToInt8(cx, v, out); } static Value ToValue(Elem value) { @@ -981,7 +982,7 @@ struct Int16x8 { typedef int16_t Elem; static const unsigned lanes = 8; static const SimdType type = SimdType::Int16x8; - static bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { return ToInt16(cx, v, out); } static Value ToValue(Elem value) { @@ -993,7 +994,7 @@ struct Int32x4 { typedef int32_t Elem; static const unsigned lanes = 4; static const SimdType type = SimdType::Int32x4; - static bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { return ToInt32(cx, v, out); } static Value ToValue(Elem value) { @@ -1005,7 +1006,7 @@ struct Uint8x16 { typedef uint8_t Elem; static const unsigned lanes = 16; static const SimdType type = SimdType::Uint8x16; - static bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { return ToUint8(cx, v, out); } static Value ToValue(Elem value) { @@ -1017,7 +1018,7 @@ struct Uint16x8 { typedef uint16_t Elem; static const unsigned lanes = 8; static const SimdType type = SimdType::Uint16x8; - static bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { return ToUint16(cx, v, out); } static Value ToValue(Elem value) { @@ -1029,7 +1030,7 @@ struct Uint32x4 { typedef uint32_t Elem; static const unsigned lanes = 4; static const SimdType type = SimdType::Uint32x4; - static bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { return ToUint32(cx, v, out); } static Value ToValue(Elem value) { @@ -1041,7 +1042,7 @@ struct Bool8x16 { typedef int8_t Elem; static const unsigned lanes = 16; static const SimdType type = SimdType::Bool8x16; - static bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { *out = ToBoolean(v) ? -1 : 0; return true; } @@ -1054,7 +1055,7 @@ struct Bool16x8 { typedef int16_t Elem; static const unsigned lanes = 8; static const SimdType type = SimdType::Bool16x8; - static bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { *out = ToBoolean(v) ? -1 : 0; return true; } @@ -1067,7 +1068,7 @@ struct Bool32x4 { typedef int32_t Elem; static const unsigned lanes = 4; static const SimdType type = SimdType::Bool32x4; - static bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { *out = ToBoolean(v) ? -1 : 0; return true; } @@ -1080,7 +1081,7 @@ struct Bool64x2 { typedef int64_t Elem; static const unsigned lanes = 2; static const SimdType type = SimdType::Bool64x2; - static bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { *out = ToBoolean(v) ? -1 : 0; return true; } @@ -1105,7 +1106,7 @@ template bool IsVectorObject(HandleValue v); template -bool ToSimdConstant(JSContext* cx, HandleValue v, jit::SimdConstant* out); +MOZ_MUST_USE bool ToSimdConstant(JSContext* cx, HandleValue v, jit::SimdConstant* out); JSObject* InitSimdClass(JSContext* cx, HandleObject obj); @@ -1118,73 +1119,73 @@ extern const JSJitInfo JitInfo_SimdFloat32x4_extractLane; } // namespace jit #define DECLARE_SIMD_FLOAT32X4_FUNCTION(Name, Func, Operands) \ -extern bool \ +extern MOZ_MUST_USE bool \ simd_float32x4_##Name(JSContext* cx, unsigned argc, Value* vp); FLOAT32X4_FUNCTION_LIST(DECLARE_SIMD_FLOAT32X4_FUNCTION) #undef DECLARE_SIMD_FLOAT32X4_FUNCTION #define DECLARE_SIMD_FLOAT64X2_FUNCTION(Name, Func, Operands) \ -extern bool \ +extern MOZ_MUST_USE bool \ simd_float64x2_##Name(JSContext* cx, unsigned argc, Value* vp); FLOAT64X2_FUNCTION_LIST(DECLARE_SIMD_FLOAT64X2_FUNCTION) #undef DECLARE_SIMD_FLOAT64X2_FUNCTION #define DECLARE_SIMD_INT8X16_FUNCTION(Name, Func, Operands) \ -extern bool \ +extern MOZ_MUST_USE bool \ simd_int8x16_##Name(JSContext* cx, unsigned argc, Value* vp); INT8X16_FUNCTION_LIST(DECLARE_SIMD_INT8X16_FUNCTION) #undef DECLARE_SIMD_INT8X16_FUNCTION #define DECLARE_SIMD_INT16X8_FUNCTION(Name, Func, Operands) \ -extern bool \ +extern MOZ_MUST_USE bool \ simd_int16x8_##Name(JSContext* cx, unsigned argc, Value* vp); INT16X8_FUNCTION_LIST(DECLARE_SIMD_INT16X8_FUNCTION) #undef DECLARE_SIMD_INT16X8_FUNCTION #define DECLARE_SIMD_INT32x4_FUNCTION(Name, Func, Operands) \ -extern bool \ +extern MOZ_MUST_USE bool \ simd_int32x4_##Name(JSContext* cx, unsigned argc, Value* vp); INT32X4_FUNCTION_LIST(DECLARE_SIMD_INT32x4_FUNCTION) #undef DECLARE_SIMD_INT32x4_FUNCTION #define DECLARE_SIMD_UINT8X16_FUNCTION(Name, Func, Operands) \ -extern bool \ +extern MOZ_MUST_USE bool \ simd_uint8x16_##Name(JSContext* cx, unsigned argc, Value* vp); UINT8X16_FUNCTION_LIST(DECLARE_SIMD_UINT8X16_FUNCTION) #undef DECLARE_SIMD_UINT8X16_FUNCTION #define DECLARE_SIMD_UINT16X8_FUNCTION(Name, Func, Operands) \ -extern bool \ +extern MOZ_MUST_USE bool \ simd_uint16x8_##Name(JSContext* cx, unsigned argc, Value* vp); UINT16X8_FUNCTION_LIST(DECLARE_SIMD_UINT16X8_FUNCTION) #undef DECLARE_SIMD_UINT16X8_FUNCTION #define DECLARE_SIMD_UINT32x4_FUNCTION(Name, Func, Operands) \ -extern bool \ +extern MOZ_MUST_USE bool \ simd_uint32x4_##Name(JSContext* cx, unsigned argc, Value* vp); UINT32X4_FUNCTION_LIST(DECLARE_SIMD_UINT32x4_FUNCTION) #undef DECLARE_SIMD_UINT32x4_FUNCTION #define DECLARE_SIMD_BOOL8X16_FUNCTION(Name, Func, Operands) \ -extern bool \ +extern MOZ_MUST_USE bool \ simd_bool8x16_##Name(JSContext* cx, unsigned argc, Value* vp); BOOL8X16_FUNCTION_LIST(DECLARE_SIMD_BOOL8X16_FUNCTION) #undef DECLARE_SIMD_BOOL8X16_FUNCTION #define DECLARE_SIMD_BOOL16X8_FUNCTION(Name, Func, Operands) \ -extern bool \ +extern MOZ_MUST_USE bool \ simd_bool16x8_##Name(JSContext* cx, unsigned argc, Value* vp); BOOL16X8_FUNCTION_LIST(DECLARE_SIMD_BOOL16X8_FUNCTION) #undef DECLARE_SIMD_BOOL16X8_FUNCTION #define DECLARE_SIMD_BOOL32X4_FUNCTION(Name, Func, Operands) \ -extern bool \ +extern MOZ_MUST_USE bool \ simd_bool32x4_##Name(JSContext* cx, unsigned argc, Value* vp); BOOL32X4_FUNCTION_LIST(DECLARE_SIMD_BOOL32X4_FUNCTION) #undef DECLARE_SIMD_BOOL32X4_FUNCTION #define DECLARE_SIMD_BOOL64x2_FUNCTION(Name, Func, Operands) \ -extern bool \ +extern MOZ_MUST_USE bool \ simd_bool64x2_##Name(JSContext* cx, unsigned argc, Value* vp); BOOL64X2_FUNCTION_LIST(DECLARE_SIMD_BOOL64x2_FUNCTION) #undef DECLARE_SIMD_BOOL64x2_FUNCTION diff --git a/js/src/builtin/SymbolObject.h b/js/src/builtin/SymbolObject.h index 287fd30eae..0f204b494a 100644 --- a/js/src/builtin/SymbolObject.h +++ b/js/src/builtin/SymbolObject.h @@ -39,18 +39,18 @@ class SymbolObject : public NativeObject setFixedSlot(PRIMITIVE_VALUE_SLOT, SymbolValue(symbol)); } - static bool construct(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp); // Static methods. - static bool for_(JSContext* cx, unsigned argc, Value* vp); - static bool keyFor(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool for_(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool keyFor(JSContext* cx, unsigned argc, Value* vp); // Methods defined on Symbol.prototype. - static bool toString_impl(JSContext* cx, const CallArgs& args); - static bool toString(JSContext* cx, unsigned argc, Value* vp); - static bool valueOf_impl(JSContext* cx, const CallArgs& args); - static bool valueOf(JSContext* cx, unsigned argc, Value* vp); - static bool toPrimitive(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool toString_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool toString(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool valueOf_impl(JSContext* cx, const CallArgs& args); + static MOZ_MUST_USE bool valueOf(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool toPrimitive(JSContext* cx, unsigned argc, Value* vp); static const JSPropertySpec properties[]; static const JSFunctionSpec methods[]; diff --git a/js/src/builtin/TestingFunctions.h b/js/src/builtin/TestingFunctions.h index d8fdf24673..e60b7d4dbd 100644 --- a/js/src/builtin/TestingFunctions.h +++ b/js/src/builtin/TestingFunctions.h @@ -11,13 +11,13 @@ namespace js { -bool +MOZ_MUST_USE bool DefineTestingFunctions(JSContext* cx, HandleObject obj, bool fuzzingSafe, bool disableOOMFunctions); -bool +MOZ_MUST_USE bool testingFunc_assertFloat32(JSContext* cx, unsigned argc, Value* vp); -bool +MOZ_MUST_USE bool testingFunc_assertRecoveredOnBailout(JSContext* cx, unsigned argc, Value* vp); } /* namespace js */ diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h index 49ef86436c..aec30bcb31 100644 --- a/js/src/builtin/TypedObject.h +++ b/js/src/builtin/TypedObject.h @@ -175,7 +175,7 @@ class TypeDescr : public NativeObject } // Whether id is an 'own' property of objects with this descriptor. - bool hasProperty(const JSAtomState& names, jsid id); + MOZ_MUST_USE bool hasProperty(const JSAtomState& names, jsid id); // Type descriptors may contain a list of their references for use during // scanning. Marking code is optimized to use this list to mark inline @@ -187,7 +187,7 @@ class TypeDescr : public NativeObject // The list is three consecutive arrays of int32_t offsets, with each array // terminated by -1. The arrays store offsets of string, object, and value // references in the descriptor, in that order. - bool hasTraceList() const { + MOZ_MUST_USE bool hasTraceList() const { return !getFixedSlot(JS_DESCR_SLOT_TRACE_LIST).isUndefined(); } const int32_t* traceList() const { @@ -256,7 +256,7 @@ class ScalarTypeDescr : public SimpleTypeDescr return Type(getReservedSlot(JS_DESCR_SLOT_TYPE).toInt32()); } - static bool call(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool call(JSContext* cx, unsigned argc, Value* vp); }; // Enumerates the cases of ScalarTypeDescr::Type which have @@ -307,7 +307,7 @@ class ReferenceTypeDescr : public SimpleTypeDescr return typeName(type()); } - static bool call(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool call(JSContext* cx, unsigned argc, Value* vp); }; #define JS_FOR_EACH_REFERENCE_TYPE_REPR(macro_) \ @@ -340,7 +340,7 @@ class SimdTypeDescr : public ComplexTypeDescr static const Class class_; static int32_t size(SimdType t); static int32_t alignment(SimdType t); - static bool call(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool call(JSContext* cx, unsigned argc, Value* vp); static bool is(const Value& v); SimdType type() const; @@ -349,7 +349,7 @@ class SimdTypeDescr : public ComplexTypeDescr bool IsTypedObjectClass(const Class* clasp); // Defined below bool IsTypedObjectArray(JSObject& obj); -bool CreateUserSizeAndAlignmentProperties(JSContext* cx, HandleTypeDescr obj); +MOZ_MUST_USE bool CreateUserSizeAndAlignmentProperties(JSContext* cx, HandleTypeDescr obj); class ArrayTypeDescr; @@ -387,7 +387,7 @@ class ArrayMetaTypeDescr : public NativeObject // This is the function that gets called when the user // does `new ArrayType(elem)`. It produces an array type object. - static bool construct(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp); }; /* @@ -436,7 +436,7 @@ class StructMetaTypeDescr : public NativeObject // This is the function that gets called when the user // does `new StructType(...)`. It produces a struct type object. - static bool construct(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp); }; class StructTypeDescr : public ComplexTypeDescr @@ -449,7 +449,7 @@ class StructTypeDescr : public ComplexTypeDescr // Set `*out` to the index of the field named `id` and returns true, // or return false if no such field exists. - bool fieldIndex(jsid id, size_t* out) const; + MOZ_MUST_USE bool fieldIndex(jsid id, size_t* out) const; // Return the name of the field at index `index`. JSAtom& fieldName(size_t index) const; @@ -489,44 +489,47 @@ class TypedObject : public JSObject { static const bool IsTypedObjectClass = true; - static bool obj_getArrayElement(JSContext* cx, - Handle typedObj, - Handle typeDescr, - uint32_t index, - MutableHandleValue vp); + static MOZ_MUST_USE bool obj_getArrayElement(JSContext* cx, + Handle typedObj, + Handle typeDescr, + uint32_t index, + MutableHandleValue vp); protected: static const ObjectOps objectOps_; HeapPtrShape shape_; - static bool obj_lookupProperty(JSContext* cx, HandleObject obj, - HandleId id, MutableHandleObject objp, - MutableHandleShape propp); + static MOZ_MUST_USE bool obj_lookupProperty(JSContext* cx, HandleObject obj, + HandleId id, MutableHandleObject objp, + MutableHandleShape propp); - static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id, - Handle desc, - ObjectOpResult& result); + static MOZ_MUST_USE bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id, + Handle desc, + ObjectOpResult& result); - static bool obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp); + static MOZ_MUST_USE bool obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, + bool* foundp); - static bool obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver, - HandleId id, MutableHandleValue vp); + static MOZ_MUST_USE bool obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver, + HandleId id, MutableHandleValue vp); - static bool obj_getElement(JSContext* cx, HandleObject obj, HandleValue receiver, - uint32_t index, MutableHandleValue vp); + static MOZ_MUST_USE bool obj_getElement(JSContext* cx, HandleObject obj, HandleValue receiver, + uint32_t index, MutableHandleValue vp); - static bool obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, - HandleValue receiver, ObjectOpResult& result); + static MOZ_MUST_USE bool obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, + HandleValue v, HandleValue receiver, + ObjectOpResult& result); - static bool obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, - MutableHandle desc); + static MOZ_MUST_USE bool obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, + HandleId id, + MutableHandle desc); - static bool obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id, - ObjectOpResult& result); + static MOZ_MUST_USE bool obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id, + ObjectOpResult& result); - static bool obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties, - bool enumerableOnly); + static MOZ_MUST_USE bool obj_enumerate(JSContext* cx, HandleObject obj, + AutoIdVector& properties, bool enumerableOnly); public: TypedProto& typedProto() const { @@ -558,7 +561,7 @@ class TypedObject : public JSObject return typedMem() + offset; } - inline bool opaque() const; + inline MOZ_MUST_USE bool opaque() const; // Creates a new typed object whose memory is freshly allocated and // initialized with zeroes (or, in the case of references, an appropriate @@ -568,11 +571,11 @@ class TypedObject : public JSObject // User-accessible constructor (`new TypeDescriptor(...)`). Note that the // callee here is the type descriptor. - static bool construct(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp); /* Accessors for self hosted code. */ - static bool GetBuffer(JSContext* cx, unsigned argc, Value* vp); - static bool GetByteOffset(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool GetBuffer(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool GetByteOffset(JSContext* cx, unsigned argc, Value* vp); Shape** addressOfShapeFromGC() { return shape_.unsafeUnbarrieredForTracing(); } }; @@ -723,14 +726,14 @@ class InlineOpaqueTypedObject : public InlineTypedObject * * Constructs a new, unattached instance of `Handle`. */ -bool NewOpaqueTypedObject(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool NewOpaqueTypedObject(JSContext* cx, unsigned argc, Value* vp); /* * Usage: NewDerivedTypedObject(typeObj, owner, offset) * * Constructs a new, unattached instance of `Handle`. */ -bool NewDerivedTypedObject(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool NewDerivedTypedObject(JSContext* cx, unsigned argc, Value* vp); /* * Usage: AttachTypedObject(typedObj, newDatum, newOffset) @@ -738,7 +741,7 @@ bool NewDerivedTypedObject(JSContext* cx, unsigned argc, Value* vp); * Moves `typedObj` to point at the memory referenced by `newDatum` with * the offset `newOffset`. */ -bool AttachTypedObject(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool AttachTypedObject(JSContext* cx, unsigned argc, Value* vp); /* * Usage: SetTypedObjectOffset(typedObj, offset) @@ -746,41 +749,41 @@ bool AttachTypedObject(JSContext* cx, unsigned argc, Value* vp); * Changes the offset for `typedObj` within its buffer to `offset`. * `typedObj` must already be attached. */ -bool SetTypedObjectOffset(JSContext*, unsigned argc, Value* vp); +MOZ_MUST_USE bool SetTypedObjectOffset(JSContext*, unsigned argc, Value* vp); /* * Usage: ObjectIsTypeDescr(obj) * * True if `obj` is a type object. */ -bool ObjectIsTypeDescr(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool ObjectIsTypeDescr(JSContext* cx, unsigned argc, Value* vp); /* * Usage: ObjectIsTypedObject(obj) * * True if `obj` is a transparent or opaque typed object. */ -bool ObjectIsTypedObject(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool ObjectIsTypedObject(JSContext* cx, unsigned argc, Value* vp); /* * Usage: ObjectIsOpaqueTypedObject(obj) * * True if `obj` is an opaque typed object. */ -bool ObjectIsOpaqueTypedObject(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool ObjectIsOpaqueTypedObject(JSContext* cx, unsigned argc, Value* vp); /* * Usage: ObjectIsTransparentTypedObject(obj) * * True if `obj` is a transparent typed object. */ -bool ObjectIsTransparentTypedObject(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool ObjectIsTransparentTypedObject(JSContext* cx, unsigned argc, Value* vp); /* Predicates on type descriptor objects. In all cases, 'obj' must be a type descriptor. */ -bool TypeDescrIsSimpleType(JSContext*, unsigned argc, Value* vp); +MOZ_MUST_USE bool TypeDescrIsSimpleType(JSContext*, unsigned argc, Value* vp); -bool TypeDescrIsArrayType(JSContext*, unsigned argc, Value* vp); +MOZ_MUST_USE bool TypeDescrIsArrayType(JSContext*, unsigned argc, Value* vp); /* * Usage: TypedObjectIsAttached(obj) @@ -788,21 +791,21 @@ bool TypeDescrIsArrayType(JSContext*, unsigned argc, Value* vp); * Given a TypedObject `obj`, returns true if `obj` is * "attached" (i.e., its data pointer is nullptr). */ -bool TypedObjectIsAttached(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool TypedObjectIsAttached(JSContext* cx, unsigned argc, Value* vp); /* * Usage: TypedObjectTypeDescr(obj) * * Given a TypedObject `obj`, returns the object's type descriptor. */ -bool TypedObjectTypeDescr(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool TypedObjectTypeDescr(JSContext* cx, unsigned argc, Value* vp); /* * Usage: ClampToUint8(v) * * Same as the C function ClampDoubleToUint8. `v` must be a number. */ -bool ClampToUint8(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool ClampToUint8(JSContext* cx, unsigned argc, Value* vp); /* * Usage: GetTypedObjectModule() @@ -813,7 +816,7 @@ bool ClampToUint8(JSContext* cx, unsigned argc, Value* vp); * to access them; eventually this should be linked into the module * system. */ -bool GetTypedObjectModule(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool GetTypedObjectModule(JSContext* cx, unsigned argc, Value* vp); /* * Usage: GetSimdTypeDescr(simdTypeRepr) @@ -823,7 +826,7 @@ bool GetTypedObjectModule(JSContext* cx, unsigned argc, Value* vp); * * The SIMD pseudo-module must have been initialized for this to be safe. */ -bool GetSimdTypeDescr(JSContext* cx, unsigned argc, Value* vp); +MOZ_MUST_USE bool GetSimdTypeDescr(JSContext* cx, unsigned argc, Value* vp); /* * Usage: Store_int8(targetDatum, targetOffset, value) @@ -844,7 +847,7 @@ bool GetSimdTypeDescr(JSContext* cx, unsigned argc, Value* vp); #define JS_STORE_SCALAR_CLASS_DEFN(_constant, T, _name) \ class StoreScalar##T { \ public: \ - static bool Func(JSContext* cx, unsigned argc, Value* vp); \ + static MOZ_MUST_USE bool Func(JSContext* cx, unsigned argc, Value* vp); \ static const JSJitInfo JitInfo; \ }; @@ -864,11 +867,11 @@ class StoreScalar##T { \ #define JS_STORE_REFERENCE_CLASS_DEFN(_constant, T, _name) \ class StoreReference##T { \ private: \ - static bool store(JSContext* cx, T* heap, const Value& v, \ - TypedObject* obj, jsid id); \ + static MOZ_MUST_USE bool store(JSContext* cx, T* heap, const Value& v, \ + TypedObject* obj, jsid id); \ \ public: \ - static bool Func(JSContext* cx, unsigned argc, Value* vp); \ + static MOZ_MUST_USE bool Func(JSContext* cx, unsigned argc, Value* vp); \ static const JSJitInfo JitInfo; \ }; @@ -883,7 +886,7 @@ class StoreReference##T { \ #define JS_LOAD_SCALAR_CLASS_DEFN(_constant, T, _name) \ class LoadScalar##T { \ public: \ - static bool Func(JSContext* cx, unsigned argc, Value* vp); \ + static MOZ_MUST_USE bool Func(JSContext* cx, unsigned argc, Value* vp); \ static const JSJitInfo JitInfo; \ }; @@ -901,7 +904,7 @@ class LoadReference##T { \ static void load(T* heap, MutableHandleValue v); \ \ public: \ - static bool Func(JSContext* cx, unsigned argc, Value* vp); \ + static MOZ_MUST_USE bool Func(JSContext* cx, unsigned argc, Value* vp); \ static const JSJitInfo JitInfo; \ }; diff --git a/js/src/builtin/WeakSetObject.h b/js/src/builtin/WeakSetObject.h index 9aa4fc51d6..fe9b74e699 100644 --- a/js/src/builtin/WeakSetObject.h +++ b/js/src/builtin/WeakSetObject.h @@ -24,7 +24,7 @@ class WeakSetObject : public NativeObject static const JSFunctionSpec methods[]; static WeakSetObject* create(JSContext* cx, HandleObject proto = nullptr); - static bool construct(JSContext* cx, unsigned argc, Value* vp); + static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp); }; extern JSObject* diff --git a/js/src/ctypes/CTypes.h b/js/src/ctypes/CTypes.h index 7289dcba0b..965b27ec4f 100644 --- a/js/src/ctypes/CTypes.h +++ b/js/src/ctypes/CTypes.h @@ -177,7 +177,7 @@ GetDeflatedUTF8StringLength(JSContext* maybecx, const CharT* chars, size_t charsLength); template -bool +MOZ_MUST_USE bool DeflateStringToUTF8Buffer(JSContext* maybecx, const CharT* src, size_t srclen, char* dst, size_t* dstlenp); @@ -454,7 +454,7 @@ namespace CType { TypeCode GetTypeCode(JSObject* typeObj); bool TypesEqual(JSObject* t1, JSObject* t2); size_t GetSize(JSObject* obj); - bool GetSafeSize(JSObject* obj, size_t* result); + MOZ_MUST_USE bool GetSafeSize(JSObject* obj, size_t* result); bool IsSizeDefined(JSObject* obj); size_t GetAlignment(JSObject* obj); ffi_type* GetFFIType(JSContext* cx, JSObject* obj); @@ -478,12 +478,12 @@ namespace ArrayType { JSObject* GetBaseType(JSObject* obj); size_t GetLength(JSObject* obj); - bool GetSafeLength(JSObject* obj, size_t* result); + MOZ_MUST_USE bool GetSafeLength(JSObject* obj, size_t* result); UniquePtrFFIType BuildFFIType(JSContext* cx, JSObject* obj); } // namespace ArrayType namespace StructType { - bool DefineInternal(JSContext* cx, JSObject* typeObj, JSObject* fieldsObj); + MOZ_MUST_USE bool DefineInternal(JSContext* cx, JSObject* typeObj, JSObject* fieldsObj); const FieldInfoHash* GetFieldInfo(JSObject* obj); const FieldInfo* LookupField(JSContext* cx, JSObject* obj, JSFlatString* name); @@ -519,9 +519,9 @@ namespace CData { bool IsCDataProto(JSObject* obj); // Attached by JSAPI as the function 'ctypes.cast' - bool Cast(JSContext* cx, unsigned argc, Value* vp); + MOZ_MUST_USE bool Cast(JSContext* cx, unsigned argc, Value* vp); // Attached by JSAPI as the function 'ctypes.getRuntime' - bool GetRuntime(JSContext* cx, unsigned argc, Value* vp); + MOZ_MUST_USE bool GetRuntime(JSContext* cx, unsigned argc, Value* vp); } // namespace CData namespace Int64 { diff --git a/js/src/ctypes/Library.h b/js/src/ctypes/Library.h index 57aa7c8a97..a79f9991f2 100644 --- a/js/src/ctypes/Library.h +++ b/js/src/ctypes/Library.h @@ -6,6 +6,8 @@ #ifndef ctypes_Library_h #define ctypes_Library_h +#include "mozilla/Attributes.h" + #include "js/TypeDecls.h" struct JSCTypesCallbacks; @@ -21,14 +23,14 @@ enum LibrarySlot { namespace Library { - bool Name(JSContext* cx, unsigned argc, JS::Value* vp); + MOZ_MUST_USE bool Name(JSContext* cx, unsigned argc, JS::Value* vp); JSObject* Create(JSContext* cx, JS::Value path, const JSCTypesCallbacks* callbacks); bool IsLibrary(JSObject* obj); PRLibrary* GetLibrary(JSObject* obj); - bool Open(JSContext* cx, unsigned argc, JS::Value* vp); + MOZ_MUST_USE bool Open(JSContext* cx, unsigned argc, JS::Value* vp); } // namespace Library } // namespace ctypes diff --git a/js/src/devtools/automation/autospider.sh b/js/src/devtools/automation/autospider.sh index 27716b57bb..ea0dde7da3 100755 --- a/js/src/devtools/automation/autospider.sh +++ b/js/src/devtools/automation/autospider.sh @@ -55,6 +55,11 @@ if [ ! -f "$ABSDIR/variants/$VARIANT" ]; then exit 1 fi +if [[ "$VARIANT" = "nonunified" ]]; then + # Hack the moz.build files to turn off unified compilation. + find "$SOURCE/js/src" -name moz.build -exec sed -i 's/UNIFIED_SOURCES/SOURCES/' '{}' ';' +fi + (cd "$SOURCE/js/src"; autoconf-2.13 || autoconf2.13 || autoconf213) TRY_OVERRIDE=$SOURCE/js/src/config.try @@ -177,6 +182,7 @@ fi RUN_JSTESTS=true RUN_JITTEST=true RUN_JSAPITESTS=true +RUN_CHECK_STYLE_ONLY=false PARENT=$$ @@ -216,6 +222,10 @@ elif [[ "$VARIANT" = "arm-sim" || "$VARIANT" = "arm-sim-osx" || "$VARIANT" = "plaindebug" ]]; then export JSTESTS_EXTRA_ARGS=--jitflags=debug +elif [[ "$VARIANT" = "nonunified" ]]; then + RUN_JSTESTS=false + RUN_JITTEST=false + RUN_CHECK_STYLE_ONLY=true elif [[ "$VARIANT" = arm64* ]]; then # The ARM64 simulator is slow, so some tests are timing out. # Run a reduced set of test cases so this doesn't take hours. @@ -223,7 +233,11 @@ elif [[ "$VARIANT" = arm64* ]]; then export JITTEST_EXTRA_ARGS="--jitflags=none --args=--baseline-eager -x ion/ -x asm.js/" fi -$COMMAND_PREFIX $MAKE check || exit 1 +if $RUN_CHECK_STYLE_ONLY; then + $COMMAND_PREFIX $MAKE check-style || exit 1 +else + $COMMAND_PREFIX $MAKE check || exit 1 +fi RESULT=0 diff --git a/js/src/devtools/automation/variants/nonunified b/js/src/devtools/automation/variants/nonunified new file mode 100644 index 0000000000..dfc858b7c1 --- /dev/null +++ b/js/src/devtools/automation/variants/nonunified @@ -0,0 +1 @@ +--enable-debug diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index c8d76f78dc..46c5585db9 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -768,10 +768,7 @@ frontend::CompileModule(ExclusiveContext* cx, const ReadOnlyCompileOptions& opti ScriptSourceObject** sourceObjectOut /* = nullptr */) { MOZ_ASSERT(srcBuf.get()); - MOZ_ASSERT(cx->isJSContext() == (alloc == nullptr)); - - if (!alloc) - alloc = &cx->asJSContext()->tempLifoAlloc(); + MOZ_ASSERT(alloc); MOZ_ASSERT_IF(sourceObjectOut, *sourceObjectOut == nullptr); CompileOptions options(cx, optionsInput); @@ -781,19 +778,32 @@ frontend::CompileModule(ExclusiveContext* cx, const ReadOnlyCompileOptions& opti Rooted staticScope(cx, &cx->global()->lexicalScope().staticBlock()); BytecodeCompiler compiler(cx, alloc, options, srcBuf, staticScope, TraceLogger_ParserCompileModule); - RootedModuleObject module(cx, compiler.compileModule()); + ModuleObject* module = compiler.compileModule(); + + // See the comment about sourceObjectOut above. + if (sourceObjectOut) + *sourceObjectOut = compiler.sourceObjectPtr(); + + return module; +} + +ModuleObject* +frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options, + SourceBufferHolder& srcBuf) +{ + if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global())) + return nullptr; + + LifoAlloc* alloc = &cx->asJSContext()->tempLifoAlloc(); + RootedModuleObject module(cx, CompileModule(cx, options, srcBuf, alloc)); if (!module) return nullptr; // This happens in GlobalHelperThreadState::finishModuleParseTask() when a // module is compiled off main thread. - if (cx->isJSContext() && !ModuleObject::FreezeArrayProperties(cx->asJSContext(), module)) + if (!ModuleObject::FreezeArrayProperties(cx->asJSContext(), module)) return nullptr; - // See the comment about sourceObjectOut above. - if (sourceObjectOut) - *sourceObjectOut = compiler.sourceObjectPtr(); - return module; } diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h index 9b73f51d08..0549b60367 100644 --- a/js/src/frontend/BytecodeCompiler.h +++ b/js/src/frontend/BytecodeCompiler.h @@ -33,18 +33,22 @@ CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, ScriptSourceObject** sourceObjectOut = nullptr); ModuleObject* -CompileModule(ExclusiveContext *cx, const ReadOnlyCompileOptions &options, - SourceBufferHolder &srcBuf, LifoAlloc* alloc = nullptr, +CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options, + SourceBufferHolder& srcBuf); + +ModuleObject* +CompileModule(ExclusiveContext* cx, const ReadOnlyCompileOptions& options, + SourceBufferHolder& srcBuf, LifoAlloc* alloc, ScriptSourceObject** sourceObjectOut = nullptr); -bool +MOZ_MUST_USE bool CompileLazyFunction(JSContext* cx, Handle lazy, const char16_t* chars, size_t length); /* * enclosingStaticScope is a static enclosing scope (e.g. a StaticWithScope). * Must be null if the enclosing scope is a global. */ -bool +MOZ_MUST_USE bool CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, const ReadOnlyCompileOptions& options, Handle formals, JS::SourceBufferHolder& srcBuf, @@ -52,12 +56,12 @@ CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, // As above, but defaults to the global lexical scope as the enclosing static // scope. -bool +MOZ_MUST_USE bool CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, const ReadOnlyCompileOptions& options, Handle formals, JS::SourceBufferHolder& srcBuf); -bool +MOZ_MUST_USE bool CompileStarGeneratorBody(JSContext* cx, MutableHandleFunction fun, const ReadOnlyCompileOptions& options, Handle formals, JS::SourceBufferHolder& srcBuf); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index d33b0abc51..38224d37cb 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1938,7 +1938,8 @@ BytecodeEmitter::bindNameToSlotHelper(ParseNode* pn) // translated on dn. if (IsAliasedVarOp(op)) { MOZ_ASSERT(dn->isKnownAliased()); - pn->pn_scopecoord.setSlot(parser->tokenStream, dn->pn_scopecoord.slot()); + if (!pn->pn_scopecoord.setSlot(parser->tokenStream, dn->pn_scopecoord.slot())) + return false; } MOZ_ASSERT(!pn->isOp(op)); diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index fca73f045a..b01d7c35a6 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -37,7 +37,10 @@ class CGConstList { Vector list; public: explicit CGConstList(ExclusiveContext* cx) : list(cx) {} - bool append(Value v) { MOZ_ASSERT_IF(v.isString(), v.toString()->isAtom()); return list.append(v); } + MOZ_MUST_USE bool append(Value v) { + MOZ_ASSERT_IF(v.isString(), v.toString()->isAtom()); + return list.append(v); + } size_t length() const { return list.length(); } void finish(ConstArray* array); }; @@ -58,7 +61,7 @@ struct CGTryNoteList { Vector list; explicit CGTryNoteList(ExclusiveContext* cx) : list(cx) {} - bool append(JSTryNoteKind kind, uint32_t stackDepth, size_t start, size_t end); + MOZ_MUST_USE bool append(JSTryNoteKind kind, uint32_t stackDepth, size_t start, size_t end); size_t length() const { return list.length(); } void finish(TryNoteArray* array); }; @@ -80,7 +83,8 @@ struct CGBlockScopeList { Vector list; explicit CGBlockScopeList(ExclusiveContext* cx) : list(cx) {} - bool append(uint32_t scopeObjectIndex, uint32_t offset, bool inPrologue, uint32_t parent); + MOZ_MUST_USE bool append(uint32_t scopeObjectIndex, uint32_t offset, bool inPrologue, + uint32_t parent); uint32_t findEnclosingScope(uint32_t index); void recordEnd(uint32_t index, uint32_t offset, bool inPrologue); size_t length() const { return list.length(); } @@ -91,7 +95,7 @@ struct CGYieldOffsetList { Vector list; explicit CGYieldOffsetList(ExclusiveContext* cx) : list(cx) {} - bool append(uint32_t offset) { return list.append(offset); } + MOZ_MUST_USE bool append(uint32_t offset) { return list.append(offset); } size_t length() const { return list.length(); } void finish(YieldOffsetArray& array, uint32_t prologueLength); }; @@ -255,8 +259,8 @@ struct BytecodeEmitter bool insideEval, HandleScript evalCaller, bool insideNonGlobalEval, TokenPos bodyPosition, EmitterMode emitterMode = Normal); - bool init(); - bool updateLocalsToFrameSlots(); + MOZ_MUST_USE bool init(); + MOZ_MUST_USE bool updateLocalsToFrameSlots(); StmtInfoBCE* innermostStmt() const { return stmtStack.innermost(); } StmtInfoBCE* innermostScopeStmt() const { return stmtStack.innermostScopeStmt(); } @@ -271,10 +275,10 @@ struct BytecodeEmitter } uint32_t computeHops(ParseNode* pn, BytecodeEmitter** bceOfDefOut); bool isAliasedName(BytecodeEmitter* bceOfDef, ParseNode* pn); - bool computeDefinitionIsAliased(BytecodeEmitter* bceOfDef, Definition* dn, JSOp* op); + MOZ_MUST_USE bool computeDefinitionIsAliased(BytecodeEmitter* bceOfDef, Definition* dn, JSOp* op); MOZ_ALWAYS_INLINE - bool makeAtomIndex(JSAtom* atom, jsatomid* indexp) { + MOZ_MUST_USE bool makeAtomIndex(JSAtom* atom, jsatomid* indexp) { AtomIndexAddPtr p = atomIndices->lookupForAdd(atom); if (p) { *indexp = p.value(); @@ -290,11 +294,11 @@ struct BytecodeEmitter } bool isInLoop(); - bool checkSingletonContext(); + MOZ_MUST_USE bool checkSingletonContext(); // Check whether our function is in a run-once context (a toplevel // run-one script or a run-once lambda). - bool checkRunOnceContext(); + MOZ_MUST_USE bool checkRunOnceContext(); bool needsImplicitThis(); @@ -334,29 +338,29 @@ struct BytecodeEmitter // statement, we define useless code as code with no side effects, because // the main effect, the value left on the stack after the code executes, // will be discarded by a pop bytecode. - bool checkSideEffects(ParseNode* pn, bool* answer); + MOZ_MUST_USE bool checkSideEffects(ParseNode* pn, bool* answer); #ifdef DEBUG - bool checkStrictOrSloppy(JSOp op); + MOZ_MUST_USE bool checkStrictOrSloppy(JSOp op); #endif // Append a new source note of the given type (and therefore size) to the // notes dynamic array, updating noteCount. Return the new note's index // within the array pointed at by current->notes as outparam. - bool newSrcNote(SrcNoteType type, unsigned* indexp = nullptr); - bool newSrcNote2(SrcNoteType type, ptrdiff_t offset, unsigned* indexp = nullptr); - bool newSrcNote3(SrcNoteType type, ptrdiff_t offset1, ptrdiff_t offset2, - unsigned* indexp = nullptr); + MOZ_MUST_USE bool newSrcNote(SrcNoteType type, unsigned* indexp = nullptr); + MOZ_MUST_USE bool newSrcNote2(SrcNoteType type, ptrdiff_t offset, unsigned* indexp = nullptr); + MOZ_MUST_USE bool newSrcNote3(SrcNoteType type, ptrdiff_t offset1, ptrdiff_t offset2, + unsigned* indexp = nullptr); void copySrcNotes(jssrcnote* destination, uint32_t nsrcnotes); - bool setSrcNoteOffset(unsigned index, unsigned which, ptrdiff_t offset); + MOZ_MUST_USE bool setSrcNoteOffset(unsigned index, unsigned which, ptrdiff_t offset); // NB: this function can add at most one extra extended delta note. - bool addToSrcNoteDelta(jssrcnote* sn, ptrdiff_t delta); + MOZ_MUST_USE bool addToSrcNoteDelta(jssrcnote* sn, ptrdiff_t delta); // Finish taking source notes in cx's notePool. If successful, the final // source note count is stored in the out outparam. - bool finishTakingSrcNotes(uint32_t* out); + MOZ_MUST_USE bool finishTakingSrcNotes(uint32_t* out); void setJumpOffsetAt(ptrdiff_t off); @@ -367,46 +371,46 @@ struct BytecodeEmitter }; // Emit code for the tree rooted at pn. - bool emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote = EMIT_LINENOTE); + MOZ_MUST_USE bool emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote = EMIT_LINENOTE); // Emit function code for the tree rooted at body. - bool emitFunctionScript(ParseNode* body); + MOZ_MUST_USE bool emitFunctionScript(ParseNode* body); // Emit module code for the tree rooted at body. - bool emitModuleScript(ParseNode* body); + MOZ_MUST_USE bool emitModuleScript(ParseNode* body); // If op is JOF_TYPESET (see the type barriers comment in TypeInference.h), // reserve a type set to store its result. void checkTypeSet(JSOp op); void updateDepth(ptrdiff_t target); - bool updateLineNumberNotes(uint32_t offset); - bool updateSourceCoordNotes(uint32_t offset); + MOZ_MUST_USE bool updateLineNumberNotes(uint32_t offset); + MOZ_MUST_USE bool updateSourceCoordNotes(uint32_t offset); - bool bindNameToSlot(ParseNode* pn); - bool bindNameToSlotHelper(ParseNode* pn); + MOZ_MUST_USE bool bindNameToSlot(ParseNode* pn); + MOZ_MUST_USE bool bindNameToSlotHelper(ParseNode* pn); void strictifySetNameNode(ParseNode* pn); JSOp strictifySetNameOp(JSOp op); - bool tryConvertFreeName(ParseNode* pn); + MOZ_MUST_USE bool tryConvertFreeName(ParseNode* pn); void popStatement(); void pushStatement(StmtInfoBCE* stmt, StmtType type, ptrdiff_t top); void pushStatementInner(StmtInfoBCE* stmt, StmtType type, ptrdiff_t top); void pushLoopStatement(LoopStmtInfo* stmt, StmtType type, ptrdiff_t top); - bool enterNestedScope(StmtInfoBCE* stmt, ObjectBox* objbox, StmtType stmtType); - bool leaveNestedScope(StmtInfoBCE* stmt); + MOZ_MUST_USE bool enterNestedScope(StmtInfoBCE* stmt, ObjectBox* objbox, StmtType stmtType); + MOZ_MUST_USE bool leaveNestedScope(StmtInfoBCE* stmt); - bool enterBlockScope(StmtInfoBCE* stmtInfo, ObjectBox* objbox, JSOp initialValueOp, - unsigned alreadyPushed = 0); + MOZ_MUST_USE bool enterBlockScope(StmtInfoBCE* stmtInfo, ObjectBox* objbox, JSOp initialValueOp, + unsigned alreadyPushed = 0); - bool computeAliasedSlots(Handle blockScope); + MOZ_MUST_USE bool computeAliasedSlots(Handle blockScope); - bool lookupAliasedName(HandleScript script, PropertyName* name, uint32_t* pslot, - ParseNode* pn = nullptr); - bool lookupAliasedNameSlot(PropertyName* name, ScopeCoordinate* sc); + MOZ_MUST_USE bool lookupAliasedName(HandleScript script, PropertyName* name, uint32_t* pslot, + ParseNode* pn = nullptr); + MOZ_MUST_USE bool lookupAliasedNameSlot(PropertyName* name, ScopeCoordinate* sc); // In a function, block-scoped locals go after the vars, and form part of the // fixed part of a stack frame. Outside a function, there are no fixed vars, @@ -414,145 +418,147 @@ struct BytecodeEmitter // and are thus addressable via GETLOCAL and friends. void computeLocalOffset(Handle blockScope); - bool flushPops(int* npops); + MOZ_MUST_USE bool flushPops(int* npops); - bool emitCheck(ptrdiff_t delta, ptrdiff_t* offset); + MOZ_MUST_USE bool emitCheck(ptrdiff_t delta, ptrdiff_t* offset); // Emit one bytecode. - bool emit1(JSOp op); + MOZ_MUST_USE bool emit1(JSOp op); // Emit two bytecodes, an opcode (op) with a byte of immediate operand // (op1). - bool emit2(JSOp op, uint8_t op1); + MOZ_MUST_USE bool emit2(JSOp op, uint8_t op1); // Emit three bytecodes, an opcode with two bytes of immediate operands. - bool emit3(JSOp op, jsbytecode op1, jsbytecode op2); + MOZ_MUST_USE bool emit3(JSOp op, jsbytecode op1, jsbytecode op2); // Helper to emit JSOP_DUPAT. The argument is the value's depth on the // JS stack, as measured from the top. - bool emitDupAt(unsigned slotFromTop); + MOZ_MUST_USE bool emitDupAt(unsigned slotFromTop); // Emit a bytecode followed by an uint16 immediate operand stored in // big-endian order. - bool emitUint16Operand(JSOp op, uint32_t operand); + MOZ_MUST_USE bool emitUint16Operand(JSOp op, uint32_t operand); // Emit a bytecode followed by an uint32 immediate operand. - bool emitUint32Operand(JSOp op, uint32_t operand); + MOZ_MUST_USE bool emitUint32Operand(JSOp op, uint32_t operand); // Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand. - bool emitN(JSOp op, size_t extra, ptrdiff_t* offset = nullptr); + MOZ_MUST_USE bool emitN(JSOp op, size_t extra, ptrdiff_t* offset = nullptr); - bool emitNumberOp(double dval); + MOZ_MUST_USE bool emitNumberOp(double dval); - bool emitThisLiteral(ParseNode* pn); - bool emitCreateFunctionThis(); - bool emitGetFunctionThis(ParseNode* pn); - bool emitGetThisForSuperBase(ParseNode* pn); - bool emitSetThis(ParseNode* pn); + MOZ_MUST_USE bool emitThisLiteral(ParseNode* pn); + MOZ_MUST_USE bool emitCreateFunctionThis(); + MOZ_MUST_USE bool emitGetFunctionThis(ParseNode* pn); + MOZ_MUST_USE bool emitGetThisForSuperBase(ParseNode* pn); + MOZ_MUST_USE bool emitSetThis(ParseNode* pn); // These functions are used to emit GETLOCAL/GETALIASEDVAR or // SETLOCAL/SETALIASEDVAR for a particular binding on a function's // CallObject. - bool emitLoadFromEnclosingFunctionScope(BindingIter& bi); - bool emitStoreToEnclosingFunctionScope(BindingIter& bi); + MOZ_MUST_USE bool emitLoadFromEnclosingFunctionScope(BindingIter& bi); + MOZ_MUST_USE bool emitStoreToEnclosingFunctionScope(BindingIter& bi); uint32_t computeHopsToEnclosingFunction(); - bool emitJump(JSOp op, ptrdiff_t off, ptrdiff_t* jumpOffset = nullptr); - bool emitCall(JSOp op, uint16_t argc, ParseNode* pn = nullptr); + MOZ_MUST_USE bool emitJump(JSOp op, ptrdiff_t off, ptrdiff_t* jumpOffset = nullptr); + MOZ_MUST_USE bool emitCall(JSOp op, uint16_t argc, ParseNode* pn = nullptr); - bool emitLoopHead(ParseNode* nextpn); - bool emitLoopEntry(ParseNode* nextpn); + MOZ_MUST_USE bool emitLoopHead(ParseNode* nextpn); + MOZ_MUST_USE bool emitLoopEntry(ParseNode* nextpn); // Emit a backpatch op with offset pointing to the previous jump of this // type, so that we can walk back up the chain fixing up the op and jump // offset. - bool emitBackPatchOp(ptrdiff_t* lastp); + MOZ_MUST_USE bool emitBackPatchOp(ptrdiff_t* lastp); void backPatch(ptrdiff_t last, jsbytecode* target, jsbytecode op); - bool emitGoto(StmtInfoBCE* toStmt, ptrdiff_t* lastp, SrcNoteType noteType = SRC_NULL); + MOZ_MUST_USE bool emitGoto(StmtInfoBCE* toStmt, ptrdiff_t* lastp, + SrcNoteType noteType = SRC_NULL); - bool emitIndex32(JSOp op, uint32_t index); - bool emitIndexOp(JSOp op, uint32_t index); + MOZ_MUST_USE bool emitIndex32(JSOp op, uint32_t index); + MOZ_MUST_USE bool emitIndexOp(JSOp op, uint32_t index); - bool emitAtomOp(JSAtom* atom, JSOp op); - bool emitAtomOp(ParseNode* pn, JSOp op); + MOZ_MUST_USE bool emitAtomOp(JSAtom* atom, JSOp op); + MOZ_MUST_USE bool emitAtomOp(ParseNode* pn, JSOp op); - bool emitArrayLiteral(ParseNode* pn); - bool emitArray(ParseNode* pn, uint32_t count, JSOp op); - bool emitArrayComp(ParseNode* pn); + MOZ_MUST_USE bool emitArrayLiteral(ParseNode* pn); + MOZ_MUST_USE bool emitArray(ParseNode* pn, uint32_t count, JSOp op); + MOZ_MUST_USE bool emitArrayComp(ParseNode* pn); - bool emitInternedObjectOp(uint32_t index, JSOp op); - bool emitObjectOp(ObjectBox* objbox, JSOp op); - bool emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op); - bool emitRegExp(uint32_t index); + MOZ_MUST_USE bool emitInternedObjectOp(uint32_t index, JSOp op); + MOZ_MUST_USE bool emitObjectOp(ObjectBox* objbox, JSOp op); + MOZ_MUST_USE bool emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op); + MOZ_MUST_USE bool emitRegExp(uint32_t index); - MOZ_NEVER_INLINE bool emitFunction(ParseNode* pn, bool needsProto = false); - MOZ_NEVER_INLINE bool emitObject(ParseNode* pn); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitFunction(ParseNode* pn, bool needsProto = false); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitObject(ParseNode* pn); - bool emitHoistedFunctionsInList(ParseNode* pn); + MOZ_MUST_USE bool emitHoistedFunctionsInList(ParseNode* pn); - bool emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, PropListType type); + MOZ_MUST_USE bool emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, + PropListType type); // To catch accidental misuse, emitUint16Operand/emit3 assert that they are // not used to unconditionally emit JSOP_GETLOCAL. Variable access should // instead be emitted using EmitVarOp. In special cases, when the caller // definitely knows that a given local slot is unaliased, this function may be // used as a non-asserting version of emitUint16Operand. - bool emitLocalOp(JSOp op, uint32_t slot); + MOZ_MUST_USE bool emitLocalOp(JSOp op, uint32_t slot); - bool emitScopeCoordOp(JSOp op, ScopeCoordinate sc); - bool emitAliasedVarOp(JSOp op, ParseNode* pn); - bool emitAliasedVarOp(JSOp op, ScopeCoordinate sc, MaybeCheckLexical checkLexical); - bool emitUnaliasedVarOp(JSOp op, uint32_t slot, MaybeCheckLexical checkLexical); + MOZ_MUST_USE bool emitScopeCoordOp(JSOp op, ScopeCoordinate sc); + MOZ_MUST_USE bool emitAliasedVarOp(JSOp op, ParseNode* pn); + MOZ_MUST_USE bool emitAliasedVarOp(JSOp op, ScopeCoordinate sc, MaybeCheckLexical checkLexical); + MOZ_MUST_USE bool emitUnaliasedVarOp(JSOp op, uint32_t slot, MaybeCheckLexical checkLexical); - bool emitVarOp(ParseNode* pn, JSOp op); - bool emitVarIncDec(ParseNode* pn); + MOZ_MUST_USE bool emitVarOp(ParseNode* pn, JSOp op); + MOZ_MUST_USE bool emitVarIncDec(ParseNode* pn); - bool emitNameOp(ParseNode* pn, bool callContext); - bool emitNameIncDec(ParseNode* pn); + MOZ_MUST_USE bool emitNameOp(ParseNode* pn, bool callContext); + MOZ_MUST_USE bool emitNameIncDec(ParseNode* pn); - bool maybeEmitVarDecl(JSOp prologueOp, ParseNode* pn, jsatomid* result); - bool emitVariables(ParseNode* pn, VarEmitOption emitOption); - bool emitSingleVariable(ParseNode* pn, ParseNode* binding, ParseNode* initializer, - VarEmitOption emitOption); + MOZ_MUST_USE bool maybeEmitVarDecl(JSOp prologueOp, ParseNode* pn, jsatomid* result); + MOZ_MUST_USE bool emitVariables(ParseNode* pn, VarEmitOption emitOption); + MOZ_MUST_USE bool emitSingleVariable(ParseNode* pn, ParseNode* binding, ParseNode* initializer, + VarEmitOption emitOption); - bool emitNewInit(JSProtoKey key); - bool emitSingletonInitialiser(ParseNode* pn); + MOZ_MUST_USE bool emitNewInit(JSProtoKey key); + MOZ_MUST_USE bool emitSingletonInitialiser(ParseNode* pn); - bool emitPrepareIteratorResult(); - bool emitFinishIteratorResult(bool done); - bool iteratorResultShape(unsigned* shape); + MOZ_MUST_USE bool emitPrepareIteratorResult(); + MOZ_MUST_USE bool emitFinishIteratorResult(bool done); + MOZ_MUST_USE bool iteratorResultShape(unsigned* shape); - bool emitYield(ParseNode* pn); - bool emitYieldOp(JSOp op); - bool emitYieldStar(ParseNode* iter, ParseNode* gen); + MOZ_MUST_USE bool emitYield(ParseNode* pn); + MOZ_MUST_USE bool emitYieldOp(JSOp op); + MOZ_MUST_USE bool emitYieldStar(ParseNode* iter, ParseNode* gen); - bool emitPropLHS(ParseNode* pn); - bool emitPropOp(ParseNode* pn, JSOp op); - bool emitPropIncDec(ParseNode* pn); + MOZ_MUST_USE bool emitPropLHS(ParseNode* pn); + MOZ_MUST_USE bool emitPropOp(ParseNode* pn, JSOp op); + MOZ_MUST_USE bool emitPropIncDec(ParseNode* pn); - bool emitComputedPropertyName(ParseNode* computedPropName); + MOZ_MUST_USE bool emitComputedPropertyName(ParseNode* computedPropName); // Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM // opcode onto the stack in the right order. In the case of SETELEM, the // value to be assigned must already be pushed. enum class EmitElemOption { Get, Set, Call, IncDec, CompoundAssign }; - bool emitElemOperands(ParseNode* pn, EmitElemOption opts); + MOZ_MUST_USE bool emitElemOperands(ParseNode* pn, EmitElemOption opts); - bool emitElemOpBase(JSOp op); - bool emitElemOp(ParseNode* pn, JSOp op); - bool emitElemIncDec(ParseNode* pn); + MOZ_MUST_USE bool emitElemOpBase(JSOp op); + MOZ_MUST_USE bool emitElemOp(ParseNode* pn, JSOp op); + MOZ_MUST_USE bool emitElemIncDec(ParseNode* pn); - bool emitCatch(ParseNode* pn); - bool emitIf(ParseNode* pn); - bool emitWith(ParseNode* pn); + MOZ_MUST_USE bool emitCatch(ParseNode* pn); + MOZ_MUST_USE bool emitIf(ParseNode* pn); + MOZ_MUST_USE bool emitWith(ParseNode* pn); - MOZ_NEVER_INLINE bool emitLabeledStatement(const LabeledStatement* pn); - MOZ_NEVER_INLINE bool emitLetBlock(ParseNode* pnLet); - MOZ_NEVER_INLINE bool emitLexicalScope(ParseNode* pn); - MOZ_NEVER_INLINE bool emitSwitch(ParseNode* pn); - MOZ_NEVER_INLINE bool emitTry(ParseNode* pn); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLabeledStatement(const LabeledStatement* pn); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLetBlock(ParseNode* pnLet); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLexicalScope(ParseNode* pn); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitSwitch(ParseNode* pn); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitTry(ParseNode* pn); // EmitDestructuringLHS assumes the to-be-destructured value has been pushed on // the stack and emits code to destructure a single lhs expression (either a @@ -564,98 +570,99 @@ struct BytecodeEmitter // If emitOption is PushInitialValues, the to-be-destructured value is replaced // with the initial values of the N (where 0 <= N) variables assigned in the // lhs expression. (Same post-condition as EmitDestructuringOpsHelper) - bool emitDestructuringLHS(ParseNode* target, VarEmitOption emitOption); + MOZ_MUST_USE bool emitDestructuringLHS(ParseNode* target, VarEmitOption emitOption); - bool emitDestructuringOps(ParseNode* pattern, bool isLet = false); - bool emitDestructuringOpsHelper(ParseNode* pattern, VarEmitOption emitOption); - bool emitDestructuringOpsArrayHelper(ParseNode* pattern, VarEmitOption emitOption); - bool emitDestructuringOpsObjectHelper(ParseNode* pattern, VarEmitOption emitOption); + MOZ_MUST_USE bool emitDestructuringOps(ParseNode* pattern, bool isLet = false); + MOZ_MUST_USE bool emitDestructuringOpsHelper(ParseNode* pattern, VarEmitOption emitOption); + MOZ_MUST_USE bool emitDestructuringOpsArrayHelper(ParseNode* pattern, VarEmitOption emitOption); + MOZ_MUST_USE bool emitDestructuringOpsObjectHelper(ParseNode* pattern, + VarEmitOption emitOption); typedef bool (*DestructuringDeclEmitter)(BytecodeEmitter* bce, JSOp prologueOp, ParseNode* pn); template - bool emitDestructuringDeclsWithEmitter(JSOp prologueOp, ParseNode* pattern); + MOZ_MUST_USE bool emitDestructuringDeclsWithEmitter(JSOp prologueOp, ParseNode* pattern); - bool emitDestructuringDecls(JSOp prologueOp, ParseNode* pattern); + MOZ_MUST_USE bool emitDestructuringDecls(JSOp prologueOp, ParseNode* pattern); // Emit code to initialize all destructured names to the value on the top of // the stack. - bool emitInitializeDestructuringDecls(JSOp prologueOp, ParseNode* pattern); + MOZ_MUST_USE bool emitInitializeDestructuringDecls(JSOp prologueOp, ParseNode* pattern); // Throw a TypeError if the value atop the stack isn't convertible to an // object, with no overall effect on the stack. - bool emitRequireObjectCoercible(); + MOZ_MUST_USE bool emitRequireObjectCoercible(); // emitIterator expects the iterable to already be on the stack. // It will replace that stack value with the corresponding iterator - bool emitIterator(); + MOZ_MUST_USE bool emitIterator(); // Pops iterator from the top of the stack. Pushes the result of |.next()| // onto the stack. - bool emitIteratorNext(ParseNode* pn, bool allowSelfHosted = false); + MOZ_MUST_USE bool emitIteratorNext(ParseNode* pn, bool allowSelfHosted = false); // Check if the value on top of the stack is "undefined". If so, replace // that value on the stack with the value defined by |defaultExpr|. - bool emitDefault(ParseNode* defaultExpr); + MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr); - bool emitCallSiteObject(ParseNode* pn); - bool emitTemplateString(ParseNode* pn); - bool emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs); + MOZ_MUST_USE bool emitCallSiteObject(ParseNode* pn); + MOZ_MUST_USE bool emitTemplateString(ParseNode* pn); + MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs); - bool emitReturn(ParseNode* pn); - bool emitStatement(ParseNode* pn); - bool emitStatementList(ParseNode* pn); + MOZ_MUST_USE bool emitReturn(ParseNode* pn); + MOZ_MUST_USE bool emitStatement(ParseNode* pn); + MOZ_MUST_USE bool emitStatementList(ParseNode* pn); - bool emitDeleteName(ParseNode* pn); - bool emitDeleteProperty(ParseNode* pn); - bool emitDeleteElement(ParseNode* pn); - bool emitDeleteExpression(ParseNode* pn); + MOZ_MUST_USE bool emitDeleteName(ParseNode* pn); + MOZ_MUST_USE bool emitDeleteProperty(ParseNode* pn); + MOZ_MUST_USE bool emitDeleteElement(ParseNode* pn); + MOZ_MUST_USE bool emitDeleteExpression(ParseNode* pn); // |op| must be JSOP_TYPEOF or JSOP_TYPEOFEXPR. - bool emitTypeof(ParseNode* node, JSOp op); + MOZ_MUST_USE bool emitTypeof(ParseNode* node, JSOp op); - bool emitUnary(ParseNode* pn); - bool emitRightAssociative(ParseNode* pn); - bool emitLeftAssociative(ParseNode* pn); - bool emitLogical(ParseNode* pn); - bool emitSequenceExpr(ParseNode* pn); + MOZ_MUST_USE bool emitUnary(ParseNode* pn); + MOZ_MUST_USE bool emitRightAssociative(ParseNode* pn); + MOZ_MUST_USE bool emitLeftAssociative(ParseNode* pn); + MOZ_MUST_USE bool emitLogical(ParseNode* pn); + MOZ_MUST_USE bool emitSequenceExpr(ParseNode* pn); - MOZ_NEVER_INLINE bool emitIncOrDec(ParseNode* pn); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitIncOrDec(ParseNode* pn); - bool emitConditionalExpression(ConditionalExpression& conditional); + MOZ_MUST_USE bool emitConditionalExpression(ConditionalExpression& conditional); - bool isRestParameter(ParseNode* pn, bool* result); - bool emitOptimizeSpread(ParseNode* arg0, ptrdiff_t* jmp, bool* emitted); + MOZ_MUST_USE bool isRestParameter(ParseNode* pn, bool* result); + MOZ_MUST_USE bool emitOptimizeSpread(ParseNode* arg0, ptrdiff_t* jmp, bool* emitted); - bool emitCallOrNew(ParseNode* pn); - bool emitDebugOnlyCheckSelfHosted(); - bool emitSelfHostedCallFunction(ParseNode* pn); - bool emitSelfHostedResumeGenerator(ParseNode* pn); - bool emitSelfHostedForceInterpreter(ParseNode* pn); - bool emitSelfHostedAllowContentSpread(ParseNode* pn); + MOZ_MUST_USE bool emitCallOrNew(ParseNode* pn); + MOZ_MUST_USE bool emitDebugOnlyCheckSelfHosted(); + MOZ_MUST_USE bool emitSelfHostedCallFunction(ParseNode* pn); + MOZ_MUST_USE bool emitSelfHostedResumeGenerator(ParseNode* pn); + MOZ_MUST_USE bool emitSelfHostedForceInterpreter(ParseNode* pn); + MOZ_MUST_USE bool emitSelfHostedAllowContentSpread(ParseNode* pn); - bool emitComprehensionFor(ParseNode* compFor); - bool emitComprehensionForIn(ParseNode* pn); - bool emitComprehensionForInOrOfVariables(ParseNode* pn, bool* letBlockScope); - bool emitComprehensionForOf(ParseNode* pn); + MOZ_MUST_USE bool emitComprehensionFor(ParseNode* compFor); + MOZ_MUST_USE bool emitComprehensionForIn(ParseNode* pn); + MOZ_MUST_USE bool emitComprehensionForInOrOfVariables(ParseNode* pn, bool* letBlockScope); + MOZ_MUST_USE bool emitComprehensionForOf(ParseNode* pn); - bool emitDo(ParseNode* pn); - bool emitFor(ParseNode* pn); - bool emitForIn(ParseNode* pn); - bool emitForInOrOfVariables(ParseNode* pn); - bool emitCStyleFor(ParseNode* pn); - bool emitWhile(ParseNode* pn); + MOZ_MUST_USE bool emitDo(ParseNode* pn); + MOZ_MUST_USE bool emitFor(ParseNode* pn); + MOZ_MUST_USE bool emitForIn(ParseNode* pn); + MOZ_MUST_USE bool emitForInOrOfVariables(ParseNode* pn); + MOZ_MUST_USE bool emitCStyleFor(ParseNode* pn); + MOZ_MUST_USE bool emitWhile(ParseNode* pn); - bool emitBreak(PropertyName* label); - bool emitContinue(PropertyName* label); + MOZ_MUST_USE bool emitBreak(PropertyName* label); + MOZ_MUST_USE bool emitContinue(PropertyName* label); - bool emitArgsBody(ParseNode* pn); - bool emitDefaultsAndDestructuring(ParseNode* pn); - bool emitLexicalInitialization(ParseNode* pn, JSOp globalDefOp); + MOZ_MUST_USE bool emitArgsBody(ParseNode* pn); + MOZ_MUST_USE bool emitDefaultsAndDestructuring(ParseNode* pn); + MOZ_MUST_USE bool emitLexicalInitialization(ParseNode* pn, JSOp globalDefOp); - bool pushInitialConstants(JSOp op, unsigned n); - bool initializeBlockScopedLocalsFromStack(Handle blockScope); + MOZ_MUST_USE bool pushInitialConstants(JSOp op, unsigned n); + MOZ_MUST_USE bool initializeBlockScopedLocalsFromStack(Handle blockScope); // Emit bytecode for the spread operator. // @@ -665,17 +672,18 @@ struct BytecodeEmitter // |.next()| and put the results into the I-th element of array with // incrementing I, then push the result I (it will be original I + // iteration count). The stack after iteration will look like |ARRAY INDEX|. - bool emitSpread(bool allowSelfHosted = false); + MOZ_MUST_USE bool emitSpread(bool allowSelfHosted = false); // Emit bytecode for a for-of loop. pn should be PNK_FOR, and pn->pn_left // should be PNK_FOROF. - bool emitForOf(ParseNode* pn); + MOZ_MUST_USE bool emitForOf(ParseNode* pn); - bool emitClass(ParseNode* pn); - bool emitSuperPropLHS(ParseNode* superBase, bool isCall = false); - bool emitSuperPropOp(ParseNode* pn, JSOp op, bool isCall = false); - bool emitSuperElemOperands(ParseNode* pn, EmitElemOption opts = EmitElemOption::Get); - bool emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall = false); + MOZ_MUST_USE bool emitClass(ParseNode* pn); + MOZ_MUST_USE bool emitSuperPropLHS(ParseNode* superBase, bool isCall = false); + MOZ_MUST_USE bool emitSuperPropOp(ParseNode* pn, JSOp op, bool isCall = false); + MOZ_MUST_USE bool emitSuperElemOperands(ParseNode* pn, + EmitElemOption opts = EmitElemOption::Get); + MOZ_MUST_USE bool emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall = false); }; } /* namespace frontend */ diff --git a/js/src/frontend/FoldConstants.h b/js/src/frontend/FoldConstants.h index a3d7a96c5f..274daaaaef 100644 --- a/js/src/frontend/FoldConstants.h +++ b/js/src/frontend/FoldConstants.h @@ -25,10 +25,10 @@ namespace frontend { // return false; // if (!FoldConstants(cx, &pn, parser)) // return false; -bool +MOZ_MUST_USE bool FoldConstants(ExclusiveContext* cx, ParseNode** pnp, Parser* parser); -inline bool +inline MOZ_MUST_USE bool FoldConstants(ExclusiveContext* cx, SyntaxParseHandler::Node* pnp, Parser* parser) { diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 80909a4276..7963445335 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -173,7 +173,7 @@ class FullParseHandler return callSite; } - bool addToCallSiteObject(ParseNode* callSiteObj, ParseNode* rawNode, ParseNode* cookedNode) { + void addToCallSiteObject(ParseNode* callSiteObj, ParseNode* rawNode, ParseNode* cookedNode) { MOZ_ASSERT(callSiteObj->isKind(PNK_CALLSITEOBJ)); addArrayElement(callSiteObj, cookedNode); @@ -184,7 +184,6 @@ class FullParseHandler * don't want to deal with this outside this method */ setEndPosition(callSiteObj, callSiteObj->pn_head); - return true; } ParseNode* newThisLiteral(const TokenPos& pos, ParseNode* thisName) { @@ -290,7 +289,7 @@ class FullParseHandler return literal; } - bool addElision(ParseNode* literal, const TokenPos& pos) { + MOZ_MUST_USE bool addElision(ParseNode* literal, const TokenPos& pos) { ParseNode* elision = new_(PNK_ELISION, pos); if (!elision) return false; @@ -299,7 +298,7 @@ class FullParseHandler return true; } - bool addSpreadElement(ParseNode* literal, uint32_t begin, ParseNode* inner) { + MOZ_MUST_USE bool addSpreadElement(ParseNode* literal, uint32_t begin, ParseNode* inner) { TokenPos pos(begin, inner->pn_pos.end); ParseNode* spread = new_(PNK_SPREAD, JSOP_NOP, pos, inner); if (!spread) @@ -350,7 +349,7 @@ class FullParseHandler return new_(PNK_SUPERBASE, JSOP_NOP, pos, thisName); } - bool addPrototypeMutation(ParseNode* literal, uint32_t begin, ParseNode* expr) { + MOZ_MUST_USE bool addPrototypeMutation(ParseNode* literal, uint32_t begin, ParseNode* expr) { // Object literals with mutated [[Prototype]] are non-constant so that // singleton objects will have Object.prototype as their [[Prototype]]. setListFlag(literal, PNX_NONCONST); @@ -362,7 +361,7 @@ class FullParseHandler return true; } - bool addPropertyDefinition(ParseNode* literal, ParseNode* key, ParseNode* val) { + MOZ_MUST_USE bool addPropertyDefinition(ParseNode* literal, ParseNode* key, ParseNode* val) { MOZ_ASSERT(literal->isKind(PNK_OBJECT)); MOZ_ASSERT(literal->isArity(PN_LIST)); MOZ_ASSERT(key->isKind(PNK_NUMBER) || @@ -377,7 +376,7 @@ class FullParseHandler return true; } - bool addShorthand(ParseNode* literal, ParseNode* name, ParseNode* expr) { + MOZ_MUST_USE bool addShorthand(ParseNode* literal, ParseNode* name, ParseNode* expr) { MOZ_ASSERT(literal->isKind(PNK_OBJECT)); MOZ_ASSERT(literal->isArity(PN_LIST)); MOZ_ASSERT(name->isKind(PNK_OBJECT_PROPERTY_NAME)); @@ -392,7 +391,8 @@ class FullParseHandler return true; } - bool addObjectMethodDefinition(ParseNode* literal, ParseNode* key, ParseNode* fn, JSOp op) + MOZ_MUST_USE bool addObjectMethodDefinition(ParseNode* literal, ParseNode* key, ParseNode* fn, + JSOp op) { MOZ_ASSERT(literal->isArity(PN_LIST)); MOZ_ASSERT(key->isKind(PNK_NUMBER) || @@ -408,8 +408,8 @@ class FullParseHandler return true; } - bool addClassMethodDefinition(ParseNode* methodList, ParseNode* key, ParseNode* fn, JSOp op, - bool isStatic) + MOZ_MUST_USE bool addClassMethodDefinition(ParseNode* methodList, ParseNode* key, ParseNode* fn, + JSOp op, bool isStatic) { MOZ_ASSERT(methodList->isKind(PNK_CLASSMETHODLIST)); MOZ_ASSERT(key->isKind(PNK_NUMBER) || @@ -445,7 +445,7 @@ class FullParseHandler } template - bool isFunctionStmt(ParseNode* stmt, PC* pc) { + MOZ_MUST_USE bool isFunctionStmt(ParseNode* stmt, PC* pc) { if (!pc->sc->strict()) { while (stmt->isKind(PNK_LABEL)) stmt = stmt->as().statement(); @@ -480,7 +480,7 @@ class FullParseHandler list->pn_xflags |= PNX_FUNCDEFS; } - bool prependInitialYield(ParseNode* stmtList, ParseNode* genName) { + MOZ_MUST_USE bool prependInitialYield(ParseNode* stmtList, ParseNode* genName) { MOZ_ASSERT(stmtList->isKind(PNK_STATEMENTLIST)); TokenPos yieldPos(stmtList->pn_pos.begin, stmtList->pn_pos.begin + 1); @@ -669,10 +669,11 @@ class FullParseHandler return new_(lhs, index, lhs->pn_pos.begin, end); } - inline bool addCatchBlock(ParseNode* catchList, ParseNode* letBlock, - ParseNode* catchName, ParseNode* catchGuard, ParseNode* catchBody); + inline MOZ_MUST_USE bool addCatchBlock(ParseNode* catchList, ParseNode* letBlock, + ParseNode* catchName, ParseNode* catchGuard, + ParseNode* catchBody); - inline bool setLastFunctionArgumentDefault(ParseNode* funcpn, ParseNode* pn); + inline MOZ_MUST_USE bool setLastFunctionArgumentDefault(ParseNode* funcpn, ParseNode* pn); inline void setLastFunctionArgumentDestructuring(ParseNode* funcpn, ParseNode* pn); ParseNode* newFunctionDefinition() { @@ -761,7 +762,7 @@ class FullParseHandler return node->isKind(PNK_SUPERBASE); } - inline bool finishInitializerAssignment(ParseNode* pn, ParseNode* init); + inline MOZ_MUST_USE bool finishInitializerAssignment(ParseNode* pn, ParseNode* init); inline void setLexicalDeclarationOp(ParseNode* pn, JSOp op); void setBeginPosition(ParseNode* pn, ParseNode* oth) { diff --git a/js/src/frontend/NameFunctions.h b/js/src/frontend/NameFunctions.h index 0773db60b7..71fd7b4a67 100644 --- a/js/src/frontend/NameFunctions.h +++ b/js/src/frontend/NameFunctions.h @@ -17,7 +17,7 @@ namespace frontend { class ParseNode; -bool +MOZ_MUST_USE bool NameFunctions(ExclusiveContext* cx, ParseNode* pn); } /* namespace frontend */ diff --git a/js/src/frontend/ParseMaps.h b/js/src/frontend/ParseMaps.h index d4a7435947..7af86972f4 100644 --- a/js/src/frontend/ParseMaps.h +++ b/js/src/frontend/ParseMaps.h @@ -134,7 +134,7 @@ struct AtomThingMapPtr void init() { clearMap(); } - bool ensureMap(ExclusiveContext* cx); + MOZ_MUST_USE bool ensureMap(ExclusiveContext* cx); void releaseMap(ExclusiveContext* cx); bool hasMap() const { return map_; } @@ -332,7 +332,7 @@ class DefinitionList * return true. Otherwise there is exactly one Definition in the list; do * nothing and return false. */ - bool popFront() { + MOZ_MUST_USE bool popFront() { if (!isMultiple()) return false; @@ -351,8 +351,8 @@ class DefinitionList * Return true on success. On OOM, report on cx and return false. */ template - bool pushFront(ExclusiveContext* cx, LifoAlloc& alloc, - typename ParseHandler::DefinitionNode defn) { + MOZ_MUST_USE bool pushFront(ExclusiveContext* cx, LifoAlloc& alloc, + typename ParseHandler::DefinitionNode defn) { Node* tail; if (isMultiple()) { tail = firstNode(); @@ -370,8 +370,8 @@ class DefinitionList } template - bool appendBack(ExclusiveContext* cx, LifoAlloc& alloc, - typename ParseHandler::DefinitionNode defn) + MOZ_MUST_USE bool appendBack(ExclusiveContext* cx, LifoAlloc& alloc, + typename ParseHandler::DefinitionNode defn) { Node* last = allocNode(cx, alloc, ParseHandler::definitionToBits(defn), nullptr); if (!last) @@ -454,7 +454,7 @@ class AtomDecls ~AtomDecls(); - bool init(); + MOZ_MUST_USE bool init(); void clear() { map->clear(); @@ -490,7 +490,7 @@ class AtomDecls } /* Add-or-update a known-unique definition for |atom|. */ - bool addUnique(JSAtom* atom, DefinitionNode defn) { + MOZ_MUST_USE bool addUnique(JSAtom* atom, DefinitionNode defn) { MOZ_ASSERT(map); AtomDefnListAddPtr p = map->lookupForAdd(atom); if (!p) @@ -500,8 +500,8 @@ class AtomDecls return true; } - bool addShadow(JSAtom* atom, DefinitionNode defn); - bool addShadowedForAnnexB(JSAtom* atom, DefinitionNode defn); + MOZ_MUST_USE bool addShadow(JSAtom* atom, DefinitionNode defn); + MOZ_MUST_USE bool addShadowedForAnnexB(JSAtom* atom, DefinitionNode defn); /* Updating the definition for an entry that is known to exist is infallible. */ void updateFirst(JSAtom* atom, DefinitionNode defn) { diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 9abeb039da..baf4dd2f2e 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -59,21 +59,21 @@ class PackedScopeCoordinate uint32_t hops() const { MOZ_ASSERT(!isFree()); return hops_; } uint32_t slot() const { MOZ_ASSERT(!isFree()); return slot_; } - bool setSlot(TokenStream& ts, uint32_t newSlot) { + MOZ_MUST_USE bool setSlot(TokenStream& ts, uint32_t newSlot) { if (newSlot >= UNKNOWN_SLOT) return ts.reportError(JSMSG_TOO_MANY_LOCALS); slot_ = newSlot; return true; } - bool setHops(TokenStream& ts, uint32_t newHops) { + MOZ_MUST_USE bool setHops(TokenStream& ts, uint32_t newHops) { if (newHops >= UNKNOWN_HOPS) return ts.reportError(JSMSG_TOO_DEEP, js_function_str); hops_ = newHops; return true; } - bool set(TokenStream& ts, uint32_t newHops, uint32_t newSlot) { + MOZ_MUST_USE bool set(TokenStream& ts, uint32_t newHops, uint32_t newSlot) { return setHops(ts, newHops) && setSlot(ts, newSlot); } @@ -919,9 +919,9 @@ class ParseNode ForCopyOnWriteArray }; - bool getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObjects, MutableHandleValue vp, - Value* compare = nullptr, size_t ncompare = 0, - NewObjectKind newKind = TenuredObject); + MOZ_MUST_USE bool getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObjects, + MutableHandleValue vp, Value* compare = nullptr, + size_t ncompare = 0, NewObjectKind newKind = TenuredObject); inline bool isConstant(); template @@ -1391,7 +1391,7 @@ struct CallSiteNode : public ListNode { return node.isKind(PNK_CALLSITEOBJ); } - bool getRawArrayValue(ExclusiveContext* cx, MutableHandleValue vp) { + MOZ_MUST_USE bool getRawArrayValue(ExclusiveContext* cx, MutableHandleValue vp) { return pn_head->getConstantValue(cx, AllowObjects, vp); } }; diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 2a6ec97ed0..e16fada8a5 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -400,7 +400,7 @@ ParseContext::prepareToAddDuplicateArg(HandlePropertyName name, De } template -void +bool ParseContext::updateDecl(TokenStream& ts, JSAtom* atom, Node pn) { Definition* oldDecl = decls_.lookupFirst(atom); @@ -424,7 +424,7 @@ ParseContext::updateDecl(TokenStream& ts, JSAtom* atom, Node pn) newDecl->pn_scopecoord = oldDecl->pn_scopecoord; newDecl->pn_dflags |= PND_BOUND; newDecl->setOp(JSOP_INITLEXICAL); - return; + return true; } if (sc->isGlobalContext() || oldDecl->isDeoptimized()) { @@ -444,14 +444,16 @@ ParseContext::updateDecl(TokenStream& ts, JSAtom* atom, Node pn) !sc->isGlobalContext()) { newDecl->pn_dflags |= PND_BOUND; - newDecl->pn_scopecoord.setSlot(ts, i); + if (!newDecl->pn_scopecoord.setSlot(ts, i)) { + return false; + } newDecl->setOp(JSOP_GETLOCAL); } vars_[i] = newDecl; break; } } - return; + return true; } MOZ_ASSERT(oldDecl->isBound()); @@ -468,6 +470,7 @@ ParseContext::updateDecl(TokenStream& ts, JSAtom* atom, Node pn) MOZ_ASSERT(vars_[oldDecl->pn_scopecoord.slot()] == oldDecl); vars_[oldDecl->pn_scopecoord.slot()] = newDecl; } + return true; } template @@ -1442,7 +1445,8 @@ bool Parser::makeDefIntoUse(Definition* dn, ParseNode* pn, HandleAtom atom) { /* Turn pn into a definition. */ - pc->updateDecl(tokenStream, atom, pn); + if (!pc->updateDecl(tokenStream, atom, pn)) + return false; /* Change all uses of dn to be uses of pn. */ for (ParseNode* pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) { @@ -3007,7 +3011,8 @@ Parser::functionArgsAndBody(InHandling inHandling, ParseNode* return true; } while (false); - blockScopes.resize(oldBlockScopesLength); + if (!blockScopes.resize(oldBlockScopesLength)) + return false; // Continue doing a full parse for this inner function. ParseContext funpc(this, pc, pn, funbox, newDirectives); @@ -3079,7 +3084,8 @@ Parser::appendToCallSiteObj(Node callSiteObj) if (!rawNode) return false; - return handler.addToCallSiteObject(callSiteObj, rawNode, cookedNode); + handler.addToCallSiteObject(callSiteObj, rawNode, cookedNode); + return true; } template <> diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 79521ef3cf..e50def6469 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -82,10 +82,6 @@ struct GenericParseContext {} }; -template -bool -GenerateBlockId(TokenStream& ts, ParseContext* pc, uint32_t& blockid); - /* * The struct ParseContext stores information about the current parsing context, * which is part of the parser state (see the field Parser::pc). The current @@ -201,7 +197,7 @@ struct MOZ_STACK_CLASS ParseContext : public GenericParseContext void prepareToAddDuplicateArg(HandlePropertyName name, DefinitionNode prevDecl); /* See the sad story in MakeDefIntoUse. */ - void updateDecl(TokenStream& ts, JSAtom* atom, Node newDecl); + MOZ_MUST_USE bool updateDecl(TokenStream& ts, JSAtom* atom, Node newDecl); // After a script has been parsed, the parser generates the code's // "bindings". Bindings are a data-structure, ultimately stored in the @@ -288,7 +284,7 @@ struct MOZ_STACK_CLASS ParseContext : public GenericParseContext ~ParseContext(); - bool init(Parser& parser); + MOZ_MUST_USE bool init(Parser& parser); unsigned blockid() { return stmtStack.innermost() ? stmtStack.innermost()->blockid : bodyid; } diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 2de4394167..50eadac93d 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -213,9 +213,7 @@ class SyntaxParseHandler return NodeGeneric; } - bool addToCallSiteObject(Node callSiteObj, Node rawNode, Node cookedNode) { - return true; - } + void addToCallSiteObject(Node callSiteObj, Node rawNode, Node cookedNode) {} Node newThisLiteral(const TokenPos& pos, Node thisName) { return NodeGeneric; } Node newNullLiteral(const TokenPos& pos) { return NodeGeneric; } @@ -261,8 +259,8 @@ class SyntaxParseHandler Node newArrayComprehension(Node body, const TokenPos& pos) { return NodeGeneric; } Node newArrayLiteral(uint32_t begin) { return NodeUnparenthesizedArray; } - bool addElision(Node literal, const TokenPos& pos) { return true; } - bool addSpreadElement(Node literal, uint32_t begin, Node inner) { return true; } + MOZ_MUST_USE bool addElision(Node literal, const TokenPos& pos) { return true; } + MOZ_MUST_USE bool addSpreadElement(Node literal, uint32_t begin, Node inner) { return true; } void addArrayElement(Node literal, Node element) { } Node newCall() { return NodeFunctionCall; } @@ -275,11 +273,11 @@ class SyntaxParseHandler Node newPosHolder(const TokenPos& pos) { return NodeGeneric; } Node newSuperBase(Node thisName, const TokenPos& pos) { return NodeSuperBase; } - bool addPrototypeMutation(Node literal, uint32_t begin, Node expr) { return true; } - bool addPropertyDefinition(Node literal, Node name, Node expr) { return true; } - bool addShorthand(Node literal, Node name, Node expr) { return true; } - bool addObjectMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; } - bool addClassMethodDefinition(Node literal, Node name, Node fn, JSOp op, bool isStatic) { return true; } + MOZ_MUST_USE bool addPrototypeMutation(Node literal, uint32_t begin, Node expr) { return true; } + MOZ_MUST_USE bool addPropertyDefinition(Node literal, Node name, Node expr) { return true; } + MOZ_MUST_USE bool addShorthand(Node literal, Node name, Node expr) { return true; } + MOZ_MUST_USE bool addObjectMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; } + MOZ_MUST_USE bool addClassMethodDefinition(Node literal, Node name, Node fn, JSOp op, bool isStatic) { return true; } Node newYieldExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; } Node newYieldStarExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; } @@ -288,7 +286,7 @@ class SyntaxParseHandler Node newStatementList(unsigned blockid, const TokenPos& pos) { return NodeGeneric; } void addStatementToList(Node list, Node stmt, ParseContext* pc) {} void addCaseStatementToList(Node list, Node stmt, ParseContext* pc) {} - bool prependInitialYield(Node stmtList, Node gen) { return true; } + MOZ_MUST_USE bool prependInitialYield(Node stmtList, Node gen) { return true; } Node newEmptyStatement(const TokenPos& pos) { return NodeEmptyStatement; } Node newSetThis(Node thisName, Node value) { return value; } @@ -323,10 +321,10 @@ class SyntaxParseHandler Node newPropertyByValue(Node pn, Node kid, uint32_t end) { return NodeElement; } - bool addCatchBlock(Node catchList, Node letBlock, - Node catchName, Node catchGuard, Node catchBody) { return true; } + MOZ_MUST_USE bool addCatchBlock(Node catchList, Node letBlock, Node catchName, + Node catchGuard, Node catchBody) { return true; } - bool setLastFunctionArgumentDefault(Node funcpn, Node pn) { return true; } + MOZ_MUST_USE bool setLastFunctionArgumentDefault(Node funcpn, Node pn) { return true; } void setLastFunctionArgumentDestructuring(Node funcpn, Node pn) {} Node newFunctionDefinition() { return NodeFunctionDefinition; } void setFunctionBody(Node pn, Node kid) {} @@ -366,7 +364,7 @@ class SyntaxParseHandler return NodeGeneric; } - bool finishInitializerAssignment(Node pn, Node init) { return true; } + MOZ_MUST_USE bool finishInitializerAssignment(Node pn, Node init) { return true; } void setLexicalDeclarationOp(Node pn, JSOp op) {} void setBeginPosition(Node pn, Node oth) {} diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h index 3a5f2c00db..984f6295d5 100644 --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -343,7 +343,7 @@ class MOZ_STACK_CLASS TokenStream ~TokenStream(); - bool checkOptions(); + MOZ_MUST_USE bool checkOptions(); // Accessors. const Token& currentToken() const { return tokens[cursor]; } @@ -436,7 +436,7 @@ class MOZ_STACK_CLASS TokenStream bool strictMode() const { return strictModeGetter && strictModeGetter->strictMode(); } static JSAtom* atomize(ExclusiveContext* cx, CharBuffer& cb); - bool putIdentInTokenbuf(const char16_t* identStart); + MOZ_MUST_USE bool putIdentInTokenbuf(const char16_t* identStart); struct Flags { @@ -536,7 +536,7 @@ class MOZ_STACK_CLASS TokenStream // Advance to the next token. If the token stream encountered an error, // return false. Otherwise return true and store the token kind in |*ttp|. - bool getToken(TokenKind* ttp, Modifier modifier = None) { + MOZ_MUST_USE bool getToken(TokenKind* ttp, Modifier modifier = None) { // Check for a pushed-back token resulting from mismatching lookahead. if (lookahead != 0) { MOZ_ASSERT(!flags.hadError); @@ -559,7 +559,7 @@ class MOZ_STACK_CLASS TokenStream cursor = (cursor - 1) & ntokensMask; } - bool peekToken(TokenKind* ttp, Modifier modifier = None) { + MOZ_MUST_USE bool peekToken(TokenKind* ttp, Modifier modifier = None) { if (lookahead > 0) { MOZ_ASSERT(!flags.hadError); verifyConsistentModifier(modifier, nextToken()); @@ -572,7 +572,7 @@ class MOZ_STACK_CLASS TokenStream return true; } - bool peekTokenPos(TokenPos* posp, Modifier modifier = None) { + MOZ_MUST_USE bool peekTokenPos(TokenPos* posp, Modifier modifier = None) { if (lookahead == 0) { TokenKind tt; if (!getTokenInternal(&tt, modifier)) @@ -593,7 +593,7 @@ class MOZ_STACK_CLASS TokenStream // TOK_EOL is actually created, just a TOK_EOL TokenKind is returned, and // currentToken() shouldn't be consulted. (This is the only place TOK_EOL // is produced.) - MOZ_ALWAYS_INLINE bool + MOZ_ALWAYS_INLINE MOZ_MUST_USE bool peekTokenSameLine(TokenKind* ttp, Modifier modifier = None) { const Token& curr = currentToken(); @@ -634,7 +634,7 @@ class MOZ_STACK_CLASS TokenStream } // Get the next token from the stream if its kind is |tt|. - bool matchToken(bool* matchedp, TokenKind tt, Modifier modifier = None) { + MOZ_MUST_USE bool matchToken(bool* matchedp, TokenKind tt, Modifier modifier = None) { TokenKind token; if (!getToken(&token, modifier)) return false; @@ -664,8 +664,8 @@ class MOZ_STACK_CLASS TokenStream // on a new line is the start of an ExpressionStatement, not a continuation // of a StatementListItem (or ImportDeclaration or ExportDeclaration, in // modules). - bool matchContextualKeyword(bool* matchedp, Handle keyword, - Modifier modifier = None) + MOZ_MUST_USE bool matchContextualKeyword(bool* matchedp, Handle keyword, + Modifier modifier = None) { TokenKind token; if (!getToken(&token, modifier)) @@ -684,7 +684,7 @@ class MOZ_STACK_CLASS TokenStream return true; } - bool nextTokenEndsExpr(bool* endsExpr) { + MOZ_MUST_USE bool nextTokenEndsExpr(bool* endsExpr) { TokenKind tt; if (!peekToken(&tt)) return false; @@ -715,10 +715,10 @@ class MOZ_STACK_CLASS TokenStream Token lookaheadTokens[maxLookahead]; }; - bool advance(size_t position); + MOZ_MUST_USE bool advance(size_t position); void tell(Position*); void seek(const Position& pos); - bool seek(const Position& pos, const TokenStream& other); + MOZ_MUST_USE bool seek(const Position& pos, const TokenStream& other); #ifdef DEBUG inline bool debugHasNoLookahead() const { return lookahead == 0; @@ -759,10 +759,10 @@ class MOZ_STACK_CLASS TokenStream // null, report a SyntaxError ("if is a reserved identifier") and return // false. If ttp is non-null, return true with the keyword's TokenKind in // *ttp. - bool checkForKeyword(JSAtom* atom, TokenKind* ttp); + MOZ_MUST_USE bool checkForKeyword(JSAtom* atom, TokenKind* ttp); // Same semantics as above, but for the provided keyword. - bool checkForKeyword(const KeywordInfo* kw, TokenKind* ttp); + MOZ_MUST_USE bool checkForKeyword(const KeywordInfo* kw, TokenKind* ttp); // This class maps a userbuf offset (which is 0-indexed) to a line number // (which is 1-indexed) and a column index (which is 0-indexed). @@ -817,8 +817,8 @@ class MOZ_STACK_CLASS TokenStream public: SourceCoords(ExclusiveContext* cx, uint32_t ln); - bool add(uint32_t lineNum, uint32_t lineStartOffset); - bool fill(const SourceCoords& other); + MOZ_MUST_USE bool add(uint32_t lineNum, uint32_t lineStartOffset); + MOZ_MUST_USE bool fill(const SourceCoords& other); bool isOnThisLine(uint32_t offset, uint32_t lineNum, bool* onThisLine) const { uint32_t lineIndex = lineNumToIndex(lineNum); @@ -957,10 +957,10 @@ class MOZ_STACK_CLASS TokenStream const char16_t* ptr; // next char to get }; - bool getTokenInternal(TokenKind* ttp, Modifier modifier); + MOZ_MUST_USE bool getTokenInternal(TokenKind* ttp, Modifier modifier); - bool getBracedUnicode(uint32_t* code); - bool getStringOrTemplateToken(int untilChar, Token** tp); + MOZ_MUST_USE bool getBracedUnicode(uint32_t* code); + MOZ_MUST_USE bool getStringOrTemplateToken(int untilChar, Token** tp); int32_t getChar(); int32_t getCharIgnoreEOL(); @@ -972,13 +972,13 @@ class MOZ_STACK_CLASS TokenStream bool matchUnicodeEscapeIdent(int32_t* c); bool peekChars(int n, char16_t* cp); - bool getDirectives(bool isMultiline, bool shouldWarnDeprecated); - bool getDirective(bool isMultiline, bool shouldWarnDeprecated, - const char* directive, int directiveLength, - const char* errorMsgPragma, - UniquePtr* destination); - bool getDisplayURL(bool isMultiline, bool shouldWarnDeprecated); - bool getSourceMappingURL(bool isMultiline, bool shouldWarnDeprecated); + MOZ_MUST_USE bool getDirectives(bool isMultiline, bool shouldWarnDeprecated); + MOZ_MUST_USE bool getDirective(bool isMultiline, bool shouldWarnDeprecated, + const char* directive, int directiveLength, + const char* errorMsgPragma, + UniquePtr* destination); + MOZ_MUST_USE bool getDisplayURL(bool isMultiline, bool shouldWarnDeprecated); + MOZ_MUST_USE bool getSourceMappingURL(bool isMultiline, bool shouldWarnDeprecated); // |expect| cannot be an EOL char. bool matchChar(int32_t expect) { diff --git a/js/src/gc/Allocator.cpp b/js/src/gc/Allocator.cpp index 0683406038..e60a4abfa9 100644 --- a/js/src/gc/Allocator.cpp +++ b/js/src/gc/Allocator.cpp @@ -217,23 +217,10 @@ js::Allocate(ExclusiveContext* cx) return GCRuntime::tryNewTenuredThing(cx, kind, thingSize); } -#define FOR_ALL_NON_OBJECT_GC_LAYOUTS(macro) \ - macro(JS::Symbol) \ - macro(JSExternalString) \ - macro(JSFatInlineString) \ - macro(JSScript) \ - macro(JSString) \ - macro(js::AccessorShape) \ - macro(js::BaseShape) \ - macro(js::LazyScript) \ - macro(js::ObjectGroup) \ - macro(js::Shape) \ - macro(js::jit::JitCode) - -#define DECL_ALLOCATOR_INSTANCES(type) \ +#define DECL_ALLOCATOR_INSTANCES(allocKind, traceKind, type, sizedType) \ template type* js::Allocate(ExclusiveContext* cx);\ template type* js::Allocate(ExclusiveContext* cx); -FOR_ALL_NON_OBJECT_GC_LAYOUTS(DECL_ALLOCATOR_INSTANCES) +FOR_EACH_NONOBJECT_ALLOCKIND(DECL_ALLOCATOR_INSTANCES) #undef DECL_ALLOCATOR_INSTANCES template diff --git a/js/src/gc/GCInternals.h b/js/src/gc/GCInternals.h index 228f9e92d1..ac51f1ff95 100644 --- a/js/src/gc/GCInternals.h +++ b/js/src/gc/GCInternals.h @@ -85,7 +85,12 @@ class MOZ_RAII AutoStopVerifyingBarriers AutoStopVerifyingBarriers(JSRuntime* rt, bool isShutdown) : gc(&rt->gc) { - restartPreVerifier = gc->endVerifyPreBarriers() && !isShutdown; + if (gc->isVerifyPreBarriersEnabled()) { + gc->endVerifyPreBarriers(); + restartPreVerifier = !isShutdown; + } else { + restartPreVerifier = false; + } } ~AutoStopVerifyingBarriers() { @@ -115,8 +120,8 @@ struct MOZ_RAII AutoStopVerifyingBarriers #endif /* JS_GC_ZEAL */ #ifdef JSGC_HASH_TABLE_CHECKS -void -CheckHashTablesAfterMovingGC(JSRuntime* rt); +void CheckHashTablesAfterMovingGC(JSRuntime* rt); +void CheckHeapAfterMovingGC(JSRuntime* rt); #endif struct MovingTracer : JS::CallbackTracer diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index d7e84703b6..7fa3e586af 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -194,7 +194,7 @@ class GCSchedulingTunables unsigned minEmptyChunkCount(const AutoLockGC&) const { return minEmptyChunkCount_; } unsigned maxEmptyChunkCount() const { return maxEmptyChunkCount_; } - bool setParameter(JSGCParamKey key, uint32_t value, const AutoLockGC& lock); + MOZ_MUST_USE bool setParameter(JSGCParamKey key, uint32_t value, const AutoLockGC& lock); }; /* @@ -584,7 +584,7 @@ class GCRuntime { public: explicit GCRuntime(JSRuntime* rt); - bool init(uint32_t maxbytes, uint32_t maxNurseryBytes); + MOZ_MUST_USE bool init(uint32_t maxbytes, uint32_t maxNurseryBytes); void finishRoots(); void finish(); @@ -593,17 +593,18 @@ class GCRuntime inline bool upcomingZealousGC(); inline bool needZealousGC(); - bool addRoot(Value* vp, const char* name); + MOZ_MUST_USE bool addRoot(Value* vp, const char* name); void removeRoot(Value* vp); void setMarkStackLimit(size_t limit, AutoLockGC& lock); - bool setParameter(JSGCParamKey key, uint32_t value, AutoLockGC& lock); + MOZ_MUST_USE bool setParameter(JSGCParamKey key, uint32_t value, AutoLockGC& lock); uint32_t getParameter(JSGCParamKey key, const AutoLockGC& lock); - bool triggerGC(JS::gcreason::Reason reason); + MOZ_MUST_USE bool triggerGC(JS::gcreason::Reason reason); void maybeAllocTriggerZoneGC(Zone* zone, const AutoLockGC& lock); + // The return value indicates if we were able to do the GC. bool triggerZoneGC(Zone* zone, JS::gcreason::Reason reason); - bool maybeGC(Zone* zone); + MOZ_MUST_USE bool maybeGC(Zone* zone); void maybePeriodicFullGC(); void minorGC(JS::gcreason::Reason reason) { gcstats::AutoPhase ap(stats, gcstats::PHASE_MINOR_GC); @@ -614,6 +615,7 @@ class GCRuntime gcstats::AutoPhase ap(stats, gcstats::PHASE_EVICT_NURSERY); minorGCImpl(reason, nullptr); } + // The return value indicates whether a major GC was performed. bool gcIfRequested(JSContext* cx = nullptr); void gc(JSGCInvocationKind gckind, JS::gcreason::Reason reason); void startGC(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis = 0); @@ -626,7 +628,7 @@ class GCRuntime void triggerFullGCForAtoms() { MOZ_ASSERT(fullGCForAtomsRequested_); fullGCForAtomsRequested_ = false; - triggerGC(JS::gcreason::ALLOC_TRIGGER); + MOZ_RELEASE_ASSERT(triggerGC(JS::gcreason::ALLOC_TRIGGER)); } void runDebugGC(); @@ -755,7 +757,7 @@ class GCRuntime bool isCompactingGCEnabled() const; void setGrayRootsTracer(JSTraceDataOp traceOp, void* data); - bool addBlackRootsTracer(JSTraceDataOp traceOp, void* data); + MOZ_MUST_USE bool addBlackRootsTracer(JSTraceDataOp traceOp, void* data); void removeBlackRootsTracer(JSTraceDataOp traceOp, void* data); void setMaxMallocBytes(size_t value); @@ -770,11 +772,13 @@ class GCRuntime void setObjectsTenuredCallback(JSObjectsTenuredCallback callback, void* data); void callObjectsTenuredCallback(); - bool addFinalizeCallback(JSFinalizeCallback callback, void* data); + MOZ_MUST_USE bool addFinalizeCallback(JSFinalizeCallback callback, void* data); void removeFinalizeCallback(JSFinalizeCallback func); - bool addWeakPointerZoneGroupCallback(JSWeakPointerZoneGroupCallback callback, void* data); + MOZ_MUST_USE bool addWeakPointerZoneGroupCallback(JSWeakPointerZoneGroupCallback callback, + void* data); void removeWeakPointerZoneGroupCallback(JSWeakPointerZoneGroupCallback callback); - bool addWeakPointerCompartmentCallback(JSWeakPointerCompartmentCallback callback, void* data); + MOZ_MUST_USE bool addWeakPointerCompartmentCallback(JSWeakPointerCompartmentCallback callback, + void* data); void removeWeakPointerCompartmentCallback(JSWeakPointerCompartmentCallback callback); JS::GCSliceCallback setSliceCallback(JS::GCSliceCallback callback); JS::GCNurseryCollectionCallback setNurseryCollectionCallback( @@ -807,8 +811,6 @@ class GCRuntime bool isFullGc() const { return isFull; } bool isCompactingGc() const { return isCompacting; } - bool shouldCleanUpEverything() { return cleanUpEverything; } - bool areGrayBitsValid() const { return grayBitsValid; } void setGrayBitsInvalid() { grayBitsValid = false; } @@ -843,7 +845,7 @@ class GCRuntime #ifdef JS_GC_ZEAL void startVerifyPreBarriers(); - bool endVerifyPreBarriers(); + void endVerifyPreBarriers(); void finishVerifier(); bool isVerifyPreBarriersEnabled() const { return !!verifyPreData; } #else @@ -862,7 +864,7 @@ class GCRuntime // Allocator template - bool checkAllocatorState(JSContext* cx, AllocKind kind); + MOZ_MUST_USE bool checkAllocatorState(JSContext* cx, AllocKind kind); template JSObject* tryNewNurseryObject(JSContext* cx, size_t thingSize, size_t nDynamicSlots, const Class* clasp); @@ -890,7 +892,7 @@ class GCRuntime void arenaAllocatedDuringGC(JS::Zone* zone, Arena* arena); // Allocator internals - bool gcIfNeededPerAllocation(JSContext* cx); + MOZ_MUST_USE bool gcIfNeededPerAllocation(JSContext* cx); template static void checkIncrementalZoneState(ExclusiveContext* cx, T* t); static void* refillFreeListFromAnyThread(ExclusiveContext* cx, AllocKind thingKind, @@ -923,16 +925,17 @@ class GCRuntime // Check if the system state is such that GC has been supressed // or otherwise delayed. - bool checkIfGCAllowedInCurrentState(JS::gcreason::Reason reason); + MOZ_MUST_USE bool checkIfGCAllowedInCurrentState(JS::gcreason::Reason reason); gcstats::ZoneGCStats scanZonesBeforeGC(); void collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::Reason reason) JS_HAZ_GC_CALL; - bool gcCycle(bool nonincrementalByAPI, SliceBudget& budget, JS::gcreason::Reason reason); + MOZ_MUST_USE 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); + MOZ_MUST_USE bool beginMarkPhase(JS::gcreason::Reason reason); bool shouldPreserveJITCode(JSCompartment* comp, int64_t currentTime, JS::gcreason::Reason reason); void bufferGrayRoots(); @@ -948,7 +951,7 @@ class GCRuntime void beginSweepPhase(bool lastGC); void findZoneGroups(); - bool findZoneEdgesForWeakMaps(); + MOZ_MUST_USE bool findZoneEdgesForWeakMaps(); void getNextZoneGroup(); void endMarkingZoneGroup(); void beginSweepingZoneGroup(); @@ -969,8 +972,8 @@ class GCRuntime void endCompactPhase(JS::gcreason::Reason reason); void sweepTypesAfterCompacting(Zone* zone); void sweepZoneAfterCompacting(Zone* zone); - bool relocateArenas(Zone* zone, JS::gcreason::Reason reason, Arena*& relocatedListOut, - SliceBudget& sliceBudget); + MOZ_MUST_USE bool relocateArenas(Zone* zone, JS::gcreason::Reason reason, + Arena*& relocatedListOut, SliceBudget& sliceBudget); void updateTypeDescrObjects(MovingTracer* trc, Zone* zone); void updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds, size_t bgTaskCount); void updateAllCellPointers(MovingTracer* trc, Zone* zone); @@ -1049,6 +1052,8 @@ class GCRuntime */ mozilla::Atomic numArenasFreeCommitted; VerifyPreTracer* verifyPreData; + + private: bool chunkAllocationSinceLastGC; int64_t nextFullGCTime; int64_t lastGCTime; diff --git a/js/src/gc/GCTrace.h b/js/src/gc/GCTrace.h index c34f6c5b08..dc3586dd9d 100644 --- a/js/src/gc/GCTrace.h +++ b/js/src/gc/GCTrace.h @@ -17,7 +17,7 @@ namespace gc { #ifdef JS_GC_TRACE -extern bool InitTrace(GCRuntime& gc); +extern MOZ_MUST_USE bool InitTrace(GCRuntime& gc); extern void FinishTrace(); extern bool TraceEnabled(); extern void TraceNurseryAlloc(Cell* thing, size_t size); @@ -33,7 +33,7 @@ extern void TraceTypeNewScript(js::ObjectGroup* group); #else -inline bool InitTrace(GCRuntime& gc) { return true; } +inline MOZ_MUST_USE bool InitTrace(GCRuntime& gc) { return true; } inline void FinishTrace() {} inline bool TraceEnabled() { return false; } inline void TraceNurseryAlloc(Cell* thing, size_t size) {} diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index 6472dbda4d..9a3faca2fb 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -53,6 +53,7 @@ extern bool CurrentThreadIsIonCompiling(); #endif +// The return value indicates if anything was unmarked. extern bool UnmarkGrayCellRecursively(gc::Cell* cell, JS::TraceKind kind); @@ -113,11 +114,60 @@ enum class AllocKind { LAST = LIMIT - 1 }; +// Macro to enumerate the different allocation kinds supplying information about +// the trace kind, C++ type and allocation size. +#define FOR_EACH_OBJECT_ALLOCKIND(D) \ + /* AllocKind TraceKind TypeName SizedType */ \ + D(FUNCTION, Object, JSObject, JSFunction) \ + D(FUNCTION_EXTENDED, Object, JSObject, FunctionExtended) \ + D(OBJECT0, Object, JSObject, JSObject_Slots0) \ + D(OBJECT0_BACKGROUND, Object, JSObject, JSObject_Slots0) \ + D(OBJECT2, Object, JSObject, JSObject_Slots2) \ + D(OBJECT2_BACKGROUND, Object, JSObject, JSObject_Slots2) \ + D(OBJECT4, Object, JSObject, JSObject_Slots4) \ + D(OBJECT4_BACKGROUND, Object, JSObject, JSObject_Slots4) \ + D(OBJECT8, Object, JSObject, JSObject_Slots8) \ + D(OBJECT8_BACKGROUND, Object, JSObject, JSObject_Slots8) \ + D(OBJECT12, Object, JSObject, JSObject_Slots12) \ + D(OBJECT12_BACKGROUND, Object, JSObject, JSObject_Slots12) \ + D(OBJECT16, Object, JSObject, JSObject_Slots16) \ + D(OBJECT16_BACKGROUND, Object, JSObject, JSObject_Slots16) + +#define FOR_EACH_NONOBJECT_ALLOCKIND(D) \ + /* AllocKind TraceKind TypeName SizedType */ \ + D(SCRIPT, Script, JSScript, JSScript) \ + D(LAZY_SCRIPT, LazyScript, js::LazyScript, js::LazyScript) \ + D(SHAPE, Shape, js::Shape, js::Shape) \ + D(ACCESSOR_SHAPE, Shape, js::AccessorShape, js::AccessorShape) \ + D(BASE_SHAPE, BaseShape, js::BaseShape, js::BaseShape) \ + D(OBJECT_GROUP, ObjectGroup, js::ObjectGroup, js::ObjectGroup) \ + D(FAT_INLINE_STRING, String, JSFatInlineString, JSFatInlineString) \ + D(STRING, String, JSString, JSString) \ + D(EXTERNAL_STRING, String, JSExternalString, JSExternalString) \ + D(SYMBOL, Symbol, JS::Symbol, JS::Symbol) \ + D(JITCODE, JitCode, js::jit::JitCode, js::jit::JitCode) + +#define FOR_EACH_ALLOCKIND(D) \ + FOR_EACH_OBJECT_ALLOCKIND(D) \ + FOR_EACH_NONOBJECT_ALLOCKIND(D) + static_assert(int(AllocKind::FIRST) == 0, "Various places depend on AllocKind starting at 0, " "please audit them carefully!"); static_assert(int(AllocKind::OBJECT_FIRST) == 0, "Various places depend on AllocKind::OBJECT_FIRST " "being 0, please audit them carefully!"); +inline bool +IsAllocKind(AllocKind kind) +{ + return kind >= AllocKind::FIRST && kind <= AllocKind::LIMIT; +} + +inline bool +IsValidAllocKind(AllocKind kind) +{ + return kind >= AllocKind::FIRST && kind <= AllocKind::LAST; +} + inline bool IsObjectAllocKind(AllocKind kind) { @@ -130,17 +180,6 @@ IsShapeAllocKind(AllocKind kind) return kind == AllocKind::SHAPE || kind == AllocKind::ACCESSOR_SHAPE; } -inline bool -IsValidAllocKind(AllocKind kind) -{ - return kind >= AllocKind::FIRST && kind <= AllocKind::LAST; -} - -inline bool IsAllocKind(AllocKind kind) -{ - return kind >= AllocKind::FIRST && kind <= AllocKind::LIMIT; -} - // Returns a sequence for use in a range-based for loop, // to iterate over all alloc kinds. inline decltype(mozilla::MakeEnumeratedRange(AllocKind::FIRST, AllocKind::LIMIT)) @@ -181,31 +220,10 @@ static inline JS::TraceKind MapAllocToTraceKind(AllocKind kind) { static const JS::TraceKind map[] = { - JS::TraceKind::Object, /* AllocKind::FUNCTION */ - JS::TraceKind::Object, /* AllocKind::FUNCTION_EXTENDED */ - JS::TraceKind::Object, /* AllocKind::OBJECT0 */ - JS::TraceKind::Object, /* AllocKind::OBJECT0_BACKGROUND */ - JS::TraceKind::Object, /* AllocKind::OBJECT2 */ - JS::TraceKind::Object, /* AllocKind::OBJECT2_BACKGROUND */ - JS::TraceKind::Object, /* AllocKind::OBJECT4 */ - JS::TraceKind::Object, /* AllocKind::OBJECT4_BACKGROUND */ - JS::TraceKind::Object, /* AllocKind::OBJECT8 */ - JS::TraceKind::Object, /* AllocKind::OBJECT8_BACKGROUND */ - JS::TraceKind::Object, /* AllocKind::OBJECT12 */ - JS::TraceKind::Object, /* AllocKind::OBJECT12_BACKGROUND */ - JS::TraceKind::Object, /* AllocKind::OBJECT16 */ - JS::TraceKind::Object, /* AllocKind::OBJECT16_BACKGROUND */ - JS::TraceKind::Script, /* AllocKind::SCRIPT */ - JS::TraceKind::LazyScript, /* AllocKind::LAZY_SCRIPT */ - JS::TraceKind::Shape, /* AllocKind::SHAPE */ - JS::TraceKind::Shape, /* AllocKind::ACCESSOR_SHAPE */ - JS::TraceKind::BaseShape, /* AllocKind::BASE_SHAPE */ - JS::TraceKind::ObjectGroup, /* AllocKind::OBJECT_GROUP */ - JS::TraceKind::String, /* AllocKind::FAT_INLINE_STRING */ - JS::TraceKind::String, /* AllocKind::STRING */ - JS::TraceKind::String, /* AllocKind::EXTERNAL_STRING */ - JS::TraceKind::Symbol, /* AllocKind::SYMBOL */ - JS::TraceKind::JitCode, /* AllocKind::JITCODE */ +#define EXPAND_ELEMENT(allocKind, traceKind, type, sizedType) \ + JS::TraceKind::traceKind, +FOR_EACH_ALLOCKIND(EXPAND_ELEMENT) +#undef EXPAND_ELEMENT }; static_assert(MOZ_ARRAY_LENGTH(map) == size_t(AllocKind::LIMIT), @@ -267,6 +285,7 @@ class TenuredCell : public Cell // Mark bit management. MOZ_ALWAYS_INLINE bool isMarked(uint32_t color = BLACK) const; + // The return value indicates if the cell went from unmarked to marked. MOZ_ALWAYS_INLINE bool markIfUnmarked(uint32_t color = BLACK) const; MOZ_ALWAYS_INLINE void unmark(uint32_t color) const; MOZ_ALWAYS_INLINE void copyMarkBitsFrom(const TenuredCell* src); @@ -296,6 +315,9 @@ class TenuredCell : public Cell static MOZ_ALWAYS_INLINE void writeBarrierPost(void* cellp, TenuredCell* prior, TenuredCell* next); + // Default implementation for kinds that don't require fixup. + void fixupAfterMovingGC() {} + #ifdef DEBUG inline bool isAligned() const; #endif @@ -849,6 +871,7 @@ struct ChunkBitmap return *word & mask; } + // The return value indicates if the cell went from unmarked to marked. MOZ_ALWAYS_INLINE bool markIfUnmarked(const Cell* cell, uint32_t color) { uintptr_t* word, mask; getMarkWordAndMask(cell, BLACK, &word, &mask); @@ -967,7 +990,7 @@ struct Chunk void releaseArena(JSRuntime* rt, Arena* arena, const AutoLockGC& lock); void recycleArena(Arena* arena, SortedArenaList& dest, size_t thingsPerArena); - bool decommitOneFreeArena(JSRuntime* rt, AutoLockGC& lock); + MOZ_MUST_USE bool decommitOneFreeArena(JSRuntime* rt, AutoLockGC& lock); void decommitAllArenasWithoutUnlocking(const AutoLockGC& lock); static Chunk* allocate(JSRuntime* rt); diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 7be1c6f3ba..e04927b701 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -192,6 +192,9 @@ js::CheckTracedThing(JSTracer* trc, T* thing) MOZ_ASSERT(trc); MOZ_ASSERT(thing); + if (!trc->checkEdges()) + return; + thing = MaybeForwarded(thing); /* This function uses data that's not available in the nursery. */ @@ -358,6 +361,9 @@ AssertRootMarkingPhase(JSTracer* trc) // statically select the correct Cell layout for marking. Below, we instantiate // each override with a declaration of the most derived layout type. // +// The use of TraceKind::Null for the case where the type is not matched +// generates a compile error as no template instantiated for that kind. +// // Usage: // BaseGCType::type // @@ -365,16 +371,13 @@ AssertRootMarkingPhase(JSTracer* trc) // BaseGCType::type => JSObject // BaseGCType::type => BaseShape // etc. -template ::value ? JS::TraceKind::Object - : IsBaseOf::value ? JS::TraceKind::String - : IsBaseOf::value ? JS::TraceKind::Symbol - : IsBaseOf::value ? JS::TraceKind::Script - : IsBaseOf::value ? JS::TraceKind::Shape - : IsBaseOf::value ? JS::TraceKind::BaseShape - : IsBaseOf::value ? JS::TraceKind::JitCode - : IsBaseOf::value ? JS::TraceKind::LazyScript - : JS::TraceKind::ObjectGroup> +template ::value ? JS::TraceKind::name : +JS_FOR_EACH_TRACEKIND(EXPAND_MATCH_TYPE) +#undef EXPAND_MATCH_TYPE + JS::TraceKind::Null> + struct BaseGCType; #define IMPL_BASE_GC_TYPE(name, type_, _) \ template struct BaseGCType { typedef type_ type; }; diff --git a/js/src/gc/Marking.h b/js/src/gc/Marking.h index 15da5346b8..0b2ee9773a 100644 --- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -87,13 +87,13 @@ class MarkStack end_ = stack + capacity; } - bool init(JSGCMode gcMode); + MOZ_MUST_USE bool init(JSGCMode gcMode); void setBaseCapacity(JSGCMode mode); size_t maxCapacity() const { return maxCapacity_; } void setMaxCapacity(size_t maxCapacity); - bool push(uintptr_t item) { + MOZ_MUST_USE bool push(uintptr_t item) { if (tos_ == end_) { if (!enlarge(1)) return false; @@ -103,7 +103,7 @@ class MarkStack return true; } - bool push(uintptr_t item1, uintptr_t item2, uintptr_t item3) { + MOZ_MUST_USE bool push(uintptr_t item1, uintptr_t item2, uintptr_t item3) { uintptr_t* nextTos = tos_ + 3; if (nextTos > end_) { if (!enlarge(3)) @@ -130,7 +130,7 @@ class MarkStack void reset(); /* Grow the stack, ensuring there is space for at least count elements. */ - bool enlarge(unsigned count); + MOZ_MUST_USE bool enlarge(unsigned count); void setGCMode(JSGCMode gcMode); @@ -168,7 +168,7 @@ class GCMarker : public JSTracer { public: explicit GCMarker(JSRuntime* rt); - bool init(JSGCMode gcMode); + MOZ_MUST_USE bool init(JSGCMode gcMode); void setMaxCapacity(size_t maxCap) { stack.setMaxCapacity(maxCap); } size_t maxCapacity() const { return stack.maxCapacity(); } @@ -216,7 +216,7 @@ class GCMarker : public JSTracer void delayMarkingArena(gc::Arena* arena); void delayMarkingChildren(const void* thing); void markDelayedChildren(gc::Arena* arena); - bool markDelayedChildren(SliceBudget& budget); + MOZ_MUST_USE bool markDelayedChildren(SliceBudget& budget); bool hasDelayedChildren() const { return !!unmarkedArenaStackTop; } @@ -225,7 +225,7 @@ class GCMarker : public JSTracer return isMarkStackEmpty() && !unmarkedArenaStackTop; } - bool drainMarkStack(SliceBudget& budget); + MOZ_MUST_USE bool drainMarkStack(SliceBudget& budget); void setGCMode(JSGCMode mode) { stack.setGCMode(mode); } @@ -289,7 +289,7 @@ class GCMarker : public JSTracer // Mark the given GC thing, but do not trace its children. Return true // if the thing became marked. template - bool mark(T* thing); + MOZ_MUST_USE bool mark(T* thing); void pushTaggedPtr(StackTag tag, void* ptr) { checkZone(ptr); @@ -319,7 +319,7 @@ class GCMarker : public JSTracer return stack.isEmpty(); } - bool restoreValueArray(JSObject* obj, void** vpp, void** endp); + MOZ_MUST_USE bool restoreValueArray(JSObject* obj, void** vpp, void** endp); void saveValueRanges(); inline void processMarkStackTop(SliceBudget& budget); @@ -451,6 +451,7 @@ struct RewrapTaggedPointer } /* namespace gc */ +// The return value indicates if anything was unmarked. bool UnmarkGrayShapeRecursively(Shape* shape); diff --git a/js/src/gc/Memory.cpp b/js/src/gc/Memory.cpp index 4984d07a04..8c576928e2 100644 --- a/js/src/gc/Memory.cpp +++ b/js/src/gc/Memory.cpp @@ -262,14 +262,13 @@ MarkPagesUnused(void* p, size_t size) return p2 == p; } -bool +void MarkPagesInUse(void* p, size_t size) { if (!DecommitEnabled()) - return true; + return; MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0); - return true; } size_t @@ -320,7 +319,6 @@ bool MarkPagesInUse(void* p, size_t size) { MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0); - return true; } size_t @@ -399,10 +397,9 @@ bool MarkPagesInUse(void* p, size_t size) { if (!DecommitEnabled()) - return true; + return; MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0); - return true; } size_t @@ -667,14 +664,13 @@ MarkPagesUnused(void* p, size_t size) return result != -1; } -bool +void MarkPagesInUse(void* p, size_t size) { if (!DecommitEnabled()) - return true; + return; MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0); - return true; } size_t diff --git a/js/src/gc/Memory.h b/js/src/gc/Memory.h index 5e932626f3..967eb191b4 100644 --- a/js/src/gc/Memory.h +++ b/js/src/gc/Memory.h @@ -29,7 +29,7 @@ bool MarkPagesUnused(void* p, size_t size); // Undo |MarkPagesUnused|: tell the OS that the given pages are of interest // and should be paged in and out normally. This may be a no-op on some // platforms. -bool MarkPagesInUse(void* p, size_t size); +void MarkPagesInUse(void* p, size_t size); // Returns #(hard faults) + #(soft faults) size_t GetPageFaultCount(); diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index 735225f51f..f4fdd034ca 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -380,9 +380,7 @@ js::TenuringTracer::TenuringTracer(JSRuntime* rt, Nursery* nursery) void js::Nursery::collect(JSRuntime* rt, JS::gcreason::Reason reason, ObjectGroupList* pretenureGroups) { - if (rt->mainThread.suppressGC) - return; - + MOZ_ASSERT(!rt->mainThread.suppressGC); JS_AbortIfWrongThread(rt); StoreBuffer& sb = rt->gc.storeBuffer; @@ -502,6 +500,13 @@ js::Nursery::collect(JSRuntime* rt, JS::gcreason::Reason reason, ObjectGroupList #endif TIME_END(checkHashTables); + TIME_START(checkHeap); +#ifdef JS_GC_ZEAL + if (rt->hasZealMode(ZealMode::CheckHeapOnMovingGC)) + CheckHeapAfterMovingGC(rt); +#endif + TIME_END(checkHeap); + // Resize the nursery. TIME_START(resize); double promotionRate = mover.tenuredSize / double(allocationEnd() - start()); @@ -548,6 +553,7 @@ js::Nursery::collect(JSRuntime* rt, JS::gcreason::Reason reason, ObjectGroupList {"mcWCll", TIME_TOTAL(traceWholeCells)}, {"mkGnrc", TIME_TOTAL(traceGenericEntries)}, {"ckTbls", TIME_TOTAL(checkHashTables)}, + {"ckHeap", TIME_TOTAL(checkHeap)}, {"mkRntm", TIME_TOTAL(markRuntime)}, {"mkDbgr", TIME_TOTAL(markDebugger)}, {"clrNOC", TIME_TOTAL(clearNewObjectCache)}, diff --git a/js/src/gc/Nursery.h b/js/src/gc/Nursery.h index ee7741ec32..67f32e5b03 100644 --- a/js/src/gc/Nursery.h +++ b/js/src/gc/Nursery.h @@ -110,7 +110,7 @@ class Nursery {} ~Nursery(); - bool init(uint32_t maxNurseryBytes); + MOZ_MUST_USE bool init(uint32_t maxNurseryBytes); bool exists() const { return numNurseryChunks_ != 0; } size_t numChunks() const { return numNurseryChunks_; } @@ -171,7 +171,7 @@ class Nursery * sets |*ref| to the new location of the object and returns true. Otherwise * returns false and leaves |*ref| unset. */ - MOZ_ALWAYS_INLINE bool getForwardedPointer(JSObject** ref) const; + MOZ_ALWAYS_INLINE MOZ_MUST_USE bool getForwardedPointer(JSObject** ref) const; /* Forward a slots/elements pointer stored in an Ion frame. */ void forwardBufferPointer(HeapSlot** pSlotsElems); @@ -188,7 +188,7 @@ class Nursery void waitBackgroundFreeEnd(); - bool addedUniqueIdToCell(gc::Cell* cell) { + MOZ_MUST_USE bool addedUniqueIdToCell(gc::Cell* cell) { if (!IsInsideNursery(cell) || !isEnabled()) return true; MOZ_ASSERT(cellsWithUid_.initialized()); diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 2bbe1cf5b6..5fa4748a94 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -160,7 +160,7 @@ struct Statistics /* Create a convenient type for referring to tables of phase times. */ using PhaseTimeTable = int64_t[NumTimingArrays][PHASE_LIMIT]; - static bool initialize(); + static MOZ_MUST_USE bool initialize(); explicit Statistics(JSRuntime* rt); ~Statistics(); @@ -174,8 +174,8 @@ struct Statistics void endSlice(); void setSliceCycleCount(unsigned cycleCount); - bool startTimingMutator(); - bool stopTimingMutator(double& mutator_ms, double& gc_ms); + MOZ_MUST_USE bool startTimingMutator(); + MOZ_MUST_USE bool stopTimingMutator(double& mutator_ms, double& gc_ms); void reset(const char* reason) { if (!aborted) diff --git a/js/src/gc/StoreBuffer.cpp b/js/src/gc/StoreBuffer.cpp index 580ae94721..f360a4ad4c 100644 --- a/js/src/gc/StoreBuffer.cpp +++ b/js/src/gc/StoreBuffer.cpp @@ -66,11 +66,11 @@ StoreBuffer::disable() enabled_ = false; } -bool +void StoreBuffer::clear() { if (!enabled_) - return true; + return; aboutToOverflow_ = false; cancelIonCompilations_ = false; @@ -80,8 +80,6 @@ StoreBuffer::clear() bufferSlot.clear(); bufferWholeCell.clear(); bufferGeneric.clear(); - - return true; } void diff --git a/js/src/gc/StoreBuffer.h b/js/src/gc/StoreBuffer.h index 9f26de05fb..fe356d6334 100644 --- a/js/src/gc/StoreBuffer.h +++ b/js/src/gc/StoreBuffer.h @@ -74,7 +74,7 @@ class StoreBuffer explicit MonoTypeBuffer() : last_(T()) {} ~MonoTypeBuffer() { stores_.finish(); } - bool init() { + MOZ_MUST_USE bool init() { if (!stores_.initialized() && !stores_.init()) return false; clear(); @@ -141,7 +141,7 @@ class StoreBuffer explicit GenericBuffer() : storage_(nullptr) {} ~GenericBuffer() { js_delete(storage_); } - bool init() { + MOZ_MUST_USE bool init() { if (!storage_) storage_ = js_new(LifoAllocBlockSize); clear(); @@ -409,7 +409,7 @@ class StoreBuffer void disable(); bool isEnabled() const { return enabled_; } - bool clear(); + void clear(); /* Get the overflowed status. */ bool isAboutToOverflow() const { return aboutToOverflow_; } diff --git a/js/src/gc/Tracer.cpp b/js/src/gc/Tracer.cpp index d8fb179b65..d95b94dde1 100644 --- a/js/src/gc/Tracer.cpp +++ b/js/src/gc/Tracer.cpp @@ -126,6 +126,27 @@ js::TraceChildren(JSTracer* trc, void* thing, JS::TraceKind kind) DispatchTraceKindTyped(f, kind, trc, thing); } +namespace { +struct TraceIncomingFunctor { + JSTracer* trc_; + const JS::CompartmentSet& compartments_; + TraceIncomingFunctor(JSTracer* trc, const JS::CompartmentSet& compartments) + : trc_(trc), compartments_(compartments) + {} + using ReturnType = void; + template + ReturnType operator()(T tp) { + if (!compartments_.has((*tp)->compartment())) + return; + TraceManuallyBarrieredEdge(trc_, tp, "cross-compartment wrapper"); + } + // StringWrappers are just used to avoid copying strings + // across zones multiple times, and don't hold a strong + // reference. + ReturnType operator()(JSString** tp) {} +}; +} // namespace (anonymous) + JS_PUBLIC_API(void) JS::TraceIncomingCCWs(JSTracer* trc, const JS::CompartmentSet& compartments) { @@ -134,44 +155,9 @@ JS::TraceIncomingCCWs(JSTracer* trc, const JS::CompartmentSet& compartments) continue; for (JSCompartment::WrapperEnum e(comp); !e.empty(); e.popFront()) { - const CrossCompartmentKey& key = e.front().key(); - JSObject* obj; - JSScript* script; - - switch (key.kind) { - case CrossCompartmentKey::StringWrapper: - // StringWrappers are just used to avoid copying strings - // across zones multiple times, and don't hold a strong - // reference. - continue; - - case CrossCompartmentKey::ObjectWrapper: - case CrossCompartmentKey::DebuggerObject: - case CrossCompartmentKey::DebuggerSource: - case CrossCompartmentKey::DebuggerEnvironment: - case CrossCompartmentKey::DebuggerWasmScript: - case CrossCompartmentKey::DebuggerWasmSource: - obj = static_cast(key.wrapped); - // Ignore CCWs whose wrapped value doesn't live in our given - // set of zones. - if (!compartments.has(obj->compartment())) - continue; - - TraceManuallyBarrieredEdge(trc, &obj, "cross-compartment wrapper"); - MOZ_ASSERT(obj == key.wrapped); - break; - - case CrossCompartmentKey::DebuggerScript: - script = static_cast(key.wrapped); - // Ignore CCWs whose wrapped value doesn't live in our given - // set of compartments. - if (!compartments.has(script->compartment())) - continue; - - TraceManuallyBarrieredEdge(trc, &script, "cross-compartment wrapper"); - MOZ_ASSERT(script == key.wrapped); - break; - } + mozilla::DebugOnly prior = e.front().key(); + e.front().mutableKey().applyToWrapped(TraceIncomingFunctor(trc, compartments)); + MOZ_ASSERT(e.front().key() == prior); } } } diff --git a/js/src/gc/Verifier.cpp b/js/src/gc/Verifier.cpp index 33002dfc91..0ffc9bdd91 100644 --- a/js/src/gc/Verifier.cpp +++ b/js/src/gc/Verifier.cpp @@ -8,6 +8,8 @@ # include #endif +#include "mozilla/IntegerPrintfMacros.h" + #include "jscntxt.h" #include "jsgc.h" #include "jsprf.h" @@ -302,13 +304,13 @@ AssertMarkedOrAllocated(const EdgeValue& edge) MOZ_CRASH(); } -bool +void gc::GCRuntime::endVerifyPreBarriers() { VerifyPreTracer* trc = verifyPreData; if (!trc) - return false; + return; MOZ_ASSERT(!JS::IsGenerationalGCEnabled(rt)); @@ -357,7 +359,6 @@ gc::GCRuntime::endVerifyPreBarriers() marker.stop(); js_delete(trc); - return true; } /*** Barrier Verifier Scheduling ***/ @@ -414,3 +415,123 @@ js::gc::GCRuntime::finishVerifier() } #endif /* JS_GC_ZEAL */ + +#ifdef JSGC_HASH_TABLE_CHECKS + +class CheckHeapTracer : public JS::CallbackTracer +{ + public: + explicit CheckHeapTracer(JSRuntime* rt); + bool init(); + bool check(); + + private: + void onChild(const JS::GCCellPtr& thing) override; + + struct WorkItem { + WorkItem(JS::GCCellPtr thing, const char* name, int parentIndex) + : thing(thing), name(name), parentIndex(parentIndex), processed(false) + {} + + JS::GCCellPtr thing; + const char* name; + int parentIndex; + bool processed; + }; + + JSRuntime* rt; + bool oom; + size_t failures; + HashSet, SystemAllocPolicy> visited; + Vector stack; + int parentIndex; +}; + +CheckHeapTracer::CheckHeapTracer(JSRuntime* rt) + : CallbackTracer(rt, TraceWeakMapKeysValues), + rt(rt), + oom(false), + failures(0), + parentIndex(-1) +{ + setCheckEdges(false); +} + +bool +CheckHeapTracer::init() +{ + return visited.init(); +} + +void +CheckHeapTracer::onChild(const JS::GCCellPtr& thing) +{ + Cell* cell = thing.asCell(); + if (visited.lookup(cell)) + return; + + if (!visited.put(cell)) { + oom = true; + return; + } + + if (!IsGCThingValidAfterMovingGC(cell)) { + failures++; + fprintf(stderr, "Stale pointer %p\n", cell); + const char* name = contextName(); + for (int index = parentIndex; index != -1; index = stack[index].parentIndex) { + const WorkItem& parent = stack[index]; + cell = parent.thing.asCell(); + fprintf(stderr, " from %s %p %s edge\n", + GCTraceKindToAscii(cell->getTraceKind()), cell, name); + name = parent.name; + } + fprintf(stderr, " from root %s\n", name); + return; + } + + WorkItem item(thing, contextName(), parentIndex); + if (!stack.append(item)) + oom = true; +} + +bool +CheckHeapTracer::check() +{ + // The analysis thinks that markRuntime might GC by calling a GC callback. + JS::AutoSuppressGCAnalysis nogc(rt); + rt->gc.markRuntime(this, GCRuntime::TraceRuntime); + + while (!stack.empty()) { + WorkItem item = stack.back(); + if (item.processed) { + stack.popBack(); + } else { + parentIndex = stack.length() - 1; + TraceChildren(this, item.thing); + stack.back().processed = true; + } + } + + if (oom) + return false; + + if (failures) { + fprintf(stderr, "Heap check: %zu failure(s) out of %" PRIu32 " pointers checked\n", + failures, visited.count()); + } + MOZ_RELEASE_ASSERT(failures == 0); + + return true; +} + +void +js::gc::CheckHeapAfterMovingGC(JSRuntime* rt) +{ + MOZ_ASSERT(rt->isHeapCollecting()); + CheckHeapTracer tracer(rt); + if (!tracer.init() || !tracer.check()) + fprintf(stderr, "OOM checking heap\n"); +} + +#endif /* JSGC_HASH_TABLE_CHECKS */ diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index 22791a0065..93b51feb13 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -672,7 +672,7 @@ class ZoneAllocPolicy void free_(void* p) { js_free(p); } void reportAllocOverflow() const {} - bool checkSimulatedOOM() const { + MOZ_MUST_USE bool checkSimulatedOOM() const { return !js::oom::ShouldFailWithOOM(); } }; diff --git a/js/src/gdb/tests/test-GCCellPtr.cpp b/js/src/gdb/tests/test-GCCellPtr.cpp index f3d4e2f369..cc5802dbe7 100644 --- a/js/src/gdb/tests/test-GCCellPtr.cpp +++ b/js/src/gdb/tests/test-GCCellPtr.cpp @@ -8,7 +8,7 @@ FRAGMENT(GCCellPtr, simple) { JS::Rooted glob(cx, JS::CurrentGlobalOrNull(cx)); JS::Rooted empty(cx, JS_NewStringCopyN(cx, nullptr, 0)); - JS::Rooted unique(cx, NewSymbol(cx, nullptr)); + JS::Rooted unique(cx, JS::NewSymbol(cx, nullptr)); JS::GCCellPtr object(glob.get()); JS::GCCellPtr string(empty.get()); diff --git a/js/src/gdb/tests/test-JSObject.cpp b/js/src/gdb/tests/test-JSObject.cpp index 7a6bdf167d..2be10e89cf 100644 --- a/js/src/gdb/tests/test-JSObject.cpp +++ b/js/src/gdb/tests/test-JSObject.cpp @@ -17,7 +17,7 @@ FRAGMENT(JSObject, simple) { JSObject* funcRaw = func; static const JSClass cls = { "\xc7X" }; - RootedObject badClassName(cx, JS_NewObject(cx, &cls)); + JS::RootedObject badClassName(cx, JS_NewObject(cx, &cls)); breakpoint(); diff --git a/js/src/gdb/tests/test-unwind.cpp b/js/src/gdb/tests/test-unwind.cpp index 446283433c..6c8b7b86ab 100644 --- a/js/src/gdb/tests/test-unwind.cpp +++ b/js/src/gdb/tests/test-unwind.cpp @@ -5,9 +5,9 @@ #include static bool -Something(JSContext* cx, unsigned argc, Value* vp) +Something(JSContext* cx, unsigned argc, JS::Value* vp) { - CallArgs args = CallArgsFromVp(argc, vp); + JS::CallArgs args = CallArgsFromVp(argc, vp); args.rval().setInt32(23); breakpoint(); return true; diff --git a/js/src/jit-test/tests/wasm/basic-integer.js b/js/src/jit-test/tests/wasm/basic-integer.js index 77c36be015..055f14efe6 100644 --- a/js/src/jit-test/tests/wasm/basic-integer.js +++ b/js/src/jit-test/tests/wasm/basic-integer.js @@ -5,44 +5,58 @@ assertEq(wasmEvalText('(module (func (result i32) (i32.const -2147483648)) (expo assertEq(wasmEvalText('(module (func (result i32) (i32.const 4294967295)) (export "" 0))')(), -1); function testUnary(type, opcode, op, expect) { - assertEq(wasmEvalText('(module (func (param ' + type + ') (result ' + type + ') (' + type + '.' + opcode + ' (get_local 0))) (export "" 0))')(op), expect); + assertEq(wasmEvalText(`(module (func (param ${type}) (result ${type}) (${type}.${opcode} (get_local 0))) (export "" 0))`)(op), expect); } -function testBinary(type, opcode, lhs, rhs, expect) { - if (type === 'i64') { - let lobj = createI64(lhs); - let robj = createI64(rhs); - expect = createI64(expect); +function testBinary64(opcode, lhs, rhs, expect) { + let lobj = createI64(lhs); + let robj = createI64(rhs); + expect = createI64(expect); - assertEqI64(wasmEvalText(`(module (func (param i64) (param i64) (result i64) (i64.${opcode} (get_local 0) (get_local 1))) (export "" 0))`)(lobj, robj), expect); - // The same, but now the RHS is a constant. - assertEqI64(wasmEvalText(`(module (func (param i64) (param i64) (result i64) (i64.${opcode} (get_local 0) (i64.const ${rhs}))) (export "" 0))`)(lobj, robj), expect); - // LHS and RHS are constants. - assertEqI64(wasmEvalText(`(module (func (param i64) (param i64) (result i64) (i64.${opcode} (i64.const ${lhs}) (i64.const ${rhs}))) (export "" 0))`)(lobj, robj), expect); - } else { - assertEq(wasmEvalText(`(module (func (param ${type}) (param ${type}) (result ${type}) (${type}.${opcode} (get_local 0) (get_local 1))) (export "" 0))`)(lhs, rhs), expect); - } + assertEqI64(wasmEvalText(`(module (func (param i64) (param i64) (result i64) (i64.${opcode} (get_local 0) (get_local 1))) (export "" 0))`)(lobj, robj), expect); + // The same, but now the RHS is a constant. + assertEqI64(wasmEvalText(`(module (func (param i64) (result i64) (i64.${opcode} (get_local 0) (i64.const ${rhs}))) (export "" 0))`)(lobj), expect); + // LHS and RHS are constants. + assertEqI64(wasmEvalText(`(module (func (result i64) (i64.${opcode} (i64.const ${lhs}) (i64.const ${rhs}))) (export "" 0))`)(), expect); } -function testComparison(type, opcode, lhs, rhs, expect) { - if (type === 'i64') { - let lobj = createI64(lhs); - let robj = createI64(rhs); +function testBinary32(opcode, lhs, rhs, expect) { + assertEq(wasmEvalText(`(module (func (param i32) (param i32) (result i32) (i32.${opcode} (get_local 0) (get_local 1))) (export "" 0))`)(lhs, rhs), expect); + // The same, but now the RHS is a constant. + assertEq(wasmEvalText(`(module (func (param i32) (result i32) (i32.${opcode} (get_local 0) (i32.const ${rhs}))) (export "" 0))`)(lhs), expect); + // LHS and RHS are constants. + assertEq(wasmEvalText(`(module (func (result i32) (i32.${opcode} (i32.const ${lhs}) (i32.const ${rhs}))) (export "" 0))`)(), expect); +} - assertEq(wasmEvalText(`(module - (func (param i64) (param i64) (result i32) (i64.${opcode} (get_local 0) (get_local 1))) - (export "" 0))`)(lobj, robj), expect); +function testComparison32(opcode, lhs, rhs, expect) { + assertEq(wasmEvalText(`(module (func (param i32) (param i32) (result i32) (i32.${opcode} (get_local 0) (get_local 1))) (export "" 0))`)(lhs, rhs), expect); +} +function testComparison64(opcode, lhs, rhs, expect) { + let lobj = createI64(lhs); + let robj = createI64(rhs); - // Also test if, for the compare-and-branch path. - assertEq(wasmEvalText(`(module - (func (param i64) (param i64) (result i32) - (if (i64.${opcode} (get_local 0) (get_local 1)) - (i32.const 1) - (i32.const 0))) - (export "" 0))`)(lobj, robj), expect); - } else { - assertEq(wasmEvalText('(module (func (param ' + type + ') (param ' + type + ') (result i32) (' + type + '.' + opcode + ' (get_local 0) (get_local 1))) (export "" 0))')(lhs, rhs), expect); - } + assertEq(wasmEvalText(`(module + (func (param i64) (param i64) (result i32) (i64.${opcode} (get_local 0) (get_local 1))) + (export "" 0))`)(lobj, robj), expect); + + // Also test if, for the compare-and-branch path. + assertEq(wasmEvalText(`(module + (func (param i64) (param i64) (result i32) + (if (i64.${opcode} (get_local 0) (get_local 1)) + (i32.const 1) + (i32.const 0))) + (export "" 0))`)(lobj, robj), expect); +} + +function testTrap64(opcode, lhs, rhs, expect) { + let lobj = createI64(lhs); + let robj = createI64(rhs); + + assertErrorMessage(() => wasmEvalText(`(module (func (param i64) (param i64) (result i64) (i64.${opcode} (get_local 0) (get_local 1))) (export "" 0))`)(lobj, robj), Error, expect); + // The same, but now the RHS is a constant. + assertErrorMessage(() => wasmEvalText(`(module (func (param i64) (result i64) (i64.${opcode} (get_local 0) (i64.const ${rhs}))) (export "" 0))`)(lobj), Error, expect); + // LHS and RHS are constants. + assertErrorMessage(wasmEvalText(`(module (func (result i64) (i64.${opcode} (i64.const ${lhs}) (i64.const ${rhs}))) (export "" 0))`), Error, expect); } testUnary('i32', 'clz', 40, 26); @@ -58,32 +72,35 @@ testUnary('i32', 'eqz', 0, 1); testUnary('i32', 'eqz', 1, 0); testUnary('i32', 'eqz', 0xFFFFFFFF, 0); -testBinary('i32', 'add', 40, 2, 42); -testBinary('i32', 'sub', 40, 2, 38); -testBinary('i32', 'mul', 40, 2, 80); -testBinary('i32', 'div_s', -40, 2, -20); -testBinary('i32', 'div_u', -40, 2, 2147483628); -testBinary('i32', 'rem_s', 40, -3, 1); -testBinary('i32', 'rem_u', 40, -3, 40); -testBinary('i32', 'and', 42, 6, 2); -testBinary('i32', 'or', 42, 6, 46); -testBinary('i32', 'xor', 42, 2, 40); -testBinary('i32', 'shl', 40, 2, 160); -testBinary('i32', 'shr_s', -40, 2, -10); -testBinary('i32', 'shr_u', -40, 2, 1073741814); -//testBinary('i32', 'rotl', 40, 2, 160); // NYI: rotate -//testBinary('i32', 'rotr', 40, 2, 10); // NYI: rotate +testBinary32('add', 40, 2, 42); +testBinary32('sub', 40, 2, 38); +testBinary32('mul', 40, 2, 80); +testBinary32('div_s', -40, 2, -20); +testBinary32('div_u', -40, 2, 2147483628); +testBinary32('rem_s', 40, -3, 1); +testBinary32('rem_u', 40, -3, 40); +testBinary32('and', 42, 6, 2); +testBinary32('or', 42, 6, 46); +testBinary32('xor', 42, 2, 40); +testBinary32('shl', 40, 2, 160); +testBinary32('shr_s', -40, 2, -10); +testBinary32('shr_u', -40, 2, 1073741814); -testComparison('i32', 'eq', 40, 40, 1); -testComparison('i32', 'ne', 40, 40, 0); -testComparison('i32', 'lt_s', 40, 40, 0); -testComparison('i32', 'lt_u', 40, 40, 0); -testComparison('i32', 'le_s', 40, 40, 1); -testComparison('i32', 'le_u', 40, 40, 1); -testComparison('i32', 'gt_s', 40, 40, 0); -testComparison('i32', 'gt_u', 40, 40, 0); -testComparison('i32', 'ge_s', 40, 40, 1); -testComparison('i32', 'ge_u', 40, 40, 1); +testBinary32('rotl', 40, 2, 160); +testBinary32('rotl', 40, 34, 160); +testBinary32('rotr', 40, 2, 10); +testBinary32('rotr', 40, 34, 10); + +testComparison32('eq', 40, 40, 1); +testComparison32('ne', 40, 40, 0); +testComparison32('lt_s', 40, 40, 0); +testComparison32('lt_u', 40, 40, 0); +testComparison32('le_s', 40, 40, 1); +testComparison32('le_u', 40, 40, 1); +testComparison32('gt_s', 40, 40, 0); +testComparison32('gt_u', 40, 40, 0); +testComparison32('ge_s', 40, 40, 1); +testComparison32('ge_u', 40, 40, 1); //testUnary('i64', 'clz', 40, 58); // TODO: NYI //testUnary('i64', 'ctz', 40, 0); // TODO: NYI @@ -94,82 +111,81 @@ if (hasI64()) { setJitCompilerOption('wasm.test-mode', 1); - testBinary('i64', 'add', 40, 2, 42); - testBinary('i64', 'add', "0x1234567887654321", -1, "0x1234567887654320"); - testBinary('i64', 'add', "0xffffffffffffffff", 1, 0); - testBinary('i64', 'sub', 40, 2, 38); - testBinary('i64', 'sub', "0x1234567887654321", "0x123456789", "0x12345677641fdb98"); - testBinary('i64', 'sub', 3, 5, -2); - testBinary('i64', 'mul', 40, 2, 80); - testBinary('i64', 'mul', -1, 2, -2); - testBinary('i64', 'mul', 0x123456, "0x9876543210", "0xad77d2c5f941160"); - testBinary('i64', 'div_s', -40, 2, -20); - testBinary('i64', 'div_s', "0x1234567887654321", 2, "0x91a2b3c43b2a190"); - testBinary('i64', 'div_s', "0x1234567887654321", "0x1000000000", "0x1234567"); - testBinary('i64', 'div_u', -40, 2, "0x7fffffffffffffec"); - testBinary('i64', 'div_u', "0x1234567887654321", 9, "0x205d0b80f0b4059"); - testBinary('i64', 'rem_s', 40, -3, 1); - testBinary('i64', 'rem_s', "0x1234567887654321", "0x1000000000", "0x887654321"); - testBinary('i64', 'rem_s', "0x7fffffffffffffff", -1, 0); - testBinary('i64', 'rem_s', "0x8000000000000001", 1000, -807); - testBinary('i64', 'rem_s', "0x8000000000000000", -1, 0); - testBinary('i64', 'rem_u', 40, -3, 40); - testBinary('i64', 'rem_u', "0x1234567887654321", "0x1000000000", "0x887654321"); - testBinary('i64', 'rem_u', "0x8000000000000000", -1, "0x8000000000000000"); - testBinary('i64', 'rem_u', "0x8ff00ff00ff00ff0", "0x100000001", "0x80000001"); + testBinary64('add', 40, 2, 42); + testBinary64('add', "0x1234567887654321", -1, "0x1234567887654320"); + testBinary64('add', "0xffffffffffffffff", 1, 0); + testBinary64('sub', 40, 2, 38); + testBinary64('sub', "0x1234567887654321", "0x123456789", "0x12345677641fdb98"); + testBinary64('sub', 3, 5, -2); + testBinary64('mul', 40, 2, 80); + testBinary64('mul', -1, 2, -2); + testBinary64('mul', 0x123456, "0x9876543210", "0xad77d2c5f941160"); + testBinary64('div_s', -40, 2, -20); + testBinary64('div_s', "0x1234567887654321", 2, "0x91a2b3c43b2a190"); + testBinary64('div_s', "0x1234567887654321", "0x1000000000", "0x1234567"); + testBinary64('div_u', -40, 2, "0x7fffffffffffffec"); + testBinary64('div_u', "0x1234567887654321", 9, "0x205d0b80f0b4059"); + testBinary64('rem_s', 40, -3, 1); + testBinary64('rem_s', "0x1234567887654321", "0x1000000000", "0x887654321"); + testBinary64('rem_s', "0x7fffffffffffffff", -1, 0); + testBinary64('rem_s', "0x8000000000000001", 1000, -807); + testBinary64('rem_s', "0x8000000000000000", -1, 0); + testBinary64('rem_u', 40, -3, 40); + testBinary64('rem_u', "0x1234567887654321", "0x1000000000", "0x887654321"); + testBinary64('rem_u', "0x8000000000000000", -1, "0x8000000000000000"); + testBinary64('rem_u', "0x8ff00ff00ff00ff0", "0x100000001", "0x80000001"); - // These should trap, but for now we match the i32 version. - testBinary('i64', 'div_s', 10, 0, 0); - testBinary('i64', 'div_s', "0x8000000000000000", -1, "0x8000000000000000"); - testBinary('i64', 'div_u', 0, 0, 0); - testBinary('i64', 'rem_s', 10, 0, 0); - testBinary('i64', 'rem_u', 10, 0, 0); + testTrap64('div_s', 10, 0, /integer divide by zero/); + testTrap64('div_s', "0x8000000000000000", -1, /integer overflow/); + testTrap64('div_u', 0, 0, /integer divide by zero/); + testTrap64('rem_s', 10, 0, /integer divide by zero/); + testTrap64('rem_u', 10, 0, /integer divide by zero/); - testBinary('i64', 'and', 42, 6, 2); - testBinary('i64', 'or', 42, 6, 46); - testBinary('i64', 'xor', 42, 2, 40); - testBinary('i64', 'and', "0x8765432112345678", "0xffff0000ffff0000", "0x8765000012340000"); - testBinary('i64', 'or', "0x8765432112345678", "0xffff0000ffff0000", "0xffff4321ffff5678"); - testBinary('i64', 'xor', "0x8765432112345678", "0xffff0000ffff0000", "0x789a4321edcb5678"); - testBinary('i64', 'shl', 40, 2, 160); - testBinary('i64', 'shr_s', -40, 2, -10); - testBinary('i64', 'shr_u', -40, 2, "0x3ffffffffffffff6"); - testBinary('i64', 'shl', 0xff00ff, 28, "0xff00ff0000000"); - testBinary('i64', 'shl', 1, 63, "0x8000000000000000"); - testBinary('i64', 'shl', 1, 64, 1); - testBinary('i64', 'shr_s', "0xff00ff0000000", 28, 0xff00ff); - testBinary('i64', 'shr_u', "0x8ffff00ff0000000", 56, 0x8f); - //testBinary('i64', 'rotl', 40, 2, 160); // NYI: rotate - //testBinary('i64', 'rotr', 40, 2, 10); // NYI: rotate + testBinary64('and', 42, 6, 2); + testBinary64('or', 42, 6, 46); + testBinary64('xor', 42, 2, 40); + testBinary64('and', "0x8765432112345678", "0xffff0000ffff0000", "0x8765000012340000"); + testBinary64('or', "0x8765432112345678", "0xffff0000ffff0000", "0xffff4321ffff5678"); + testBinary64('xor', "0x8765432112345678", "0xffff0000ffff0000", "0x789a4321edcb5678"); + testBinary64('shl', 40, 2, 160); + testBinary64('shr_s', -40, 2, -10); + testBinary64('shr_u', -40, 2, "0x3ffffffffffffff6"); + testBinary64('shl', 0xff00ff, 28, "0xff00ff0000000"); + testBinary64('shl', 1, 63, "0x8000000000000000"); + testBinary64('shl', 1, 64, 1); + testBinary64('shr_s', "0xff00ff0000000", 28, 0xff00ff); + testBinary64('shr_u', "0x8ffff00ff0000000", 56, 0x8f); + testBinary64('rotl', 40, 2, 160); + testBinary64('rotr', 40, 2, 10); - testComparison('i64', 'eq', 40, 40, 1); - testComparison('i64', 'ne', 40, 40, 0); - testComparison('i64', 'lt_s', 40, 40, 0); - testComparison('i64', 'lt_u', 40, 40, 0); - testComparison('i64', 'le_s', 40, 40, 1); - testComparison('i64', 'le_u', 40, 40, 1); - testComparison('i64', 'gt_s', 40, 40, 0); - testComparison('i64', 'gt_u', 40, 40, 0); - testComparison('i64', 'ge_s', 40, 40, 1); - testComparison('i64', 'ge_u', 40, 40, 1); - testComparison('i64', 'eq', "0x400012345678", "0x400012345678", 1); - testComparison('i64', 'ne', "0x400012345678", "0x400012345678", 0); - testComparison('i64', 'ne', "0x400012345678", "0x500012345678", 1); - testComparison('i64', 'eq', "0xffffffffffffffff", -1, 1); - testComparison('i64', 'lt_s', "0x8000000012345678", "0x1", 1); - testComparison('i64', 'lt_u', "0x8000000012345678", "0x1", 0); - testComparison('i64', 'le_s', -1, 0, 1); - testComparison('i64', 'le_u', -1, -1, 1); - testComparison('i64', 'gt_s', 1, "0x8000000000000000", 1); - testComparison('i64', 'gt_u', 1, "0x8000000000000000", 0); - testComparison('i64', 'ge_s', 1, "0x8000000000000000", 1); - testComparison('i64', 'ge_u', 1, "0x8000000000000000", 0); + testComparison64('eq', 40, 40, 1); + testComparison64('ne', 40, 40, 0); + testComparison64('lt_s', 40, 40, 0); + testComparison64('lt_u', 40, 40, 0); + testComparison64('le_s', 40, 40, 1); + testComparison64('le_u', 40, 40, 1); + testComparison64('gt_s', 40, 40, 0); + testComparison64('gt_u', 40, 40, 0); + testComparison64('ge_s', 40, 40, 1); + testComparison64('ge_u', 40, 40, 1); + testComparison64('eq', "0x400012345678", "0x400012345678", 1); + testComparison64('ne', "0x400012345678", "0x400012345678", 0); + testComparison64('ne', "0x400012345678", "0x500012345678", 1); + testComparison64('eq', "0xffffffffffffffff", -1, 1); + testComparison64('lt_s', "0x8000000012345678", "0x1", 1); + testComparison64('lt_u', "0x8000000012345678", "0x1", 0); + testComparison64('le_s', -1, 0, 1); + testComparison64('le_u', -1, -1, 1); + testComparison64('gt_s', 1, "0x8000000000000000", 1); + testComparison64('gt_u', 1, "0x8000000000000000", 0); + testComparison64('ge_s', 1, "0x8000000000000000", 1); + testComparison64('ge_u', 1, "0x8000000000000000", 0); setJitCompilerOption('wasm.test-mode', 0); } else { // Sleeper test: once i64 works on more platforms, remove this if-else. try { - testComparison('i64', 'eq', 40, 40, 1); + testComparison64('eq', 40, 40, 1); assertEq(0, 1); } catch(e) { assertEq(e.toString().indexOf("NYI on this platform") >= 0, true); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 5cd86b3268..f57838c50c 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -11203,7 +11203,7 @@ void CodeGenerator::visitAsmThrowUnreachable(LAsmThrowUnreachable* lir) { MOZ_ASSERT(gen->compilingAsmJS()); - masm.jump(wasm::JumpTarget::UnreachableTrap); + masm.jump(wasm::JumpTarget::Unreachable); } typedef bool (*RecompileFn)(JSContext*); diff --git a/js/src/jit/IonCode.h b/js/src/jit/IonCode.h index 6e9ab2295e..761ed70846 100644 --- a/js/src/jit/IonCode.h +++ b/js/src/jit/IonCode.h @@ -114,7 +114,6 @@ class JitCode : public gc::TenuredCell void traceChildren(JSTracer* trc); void finalize(FreeOp* fop); - void fixupAfterMovingGC() {} void setInvalidated() { invalidated_ = true; } diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 0cd36a0c22..fdacbdef65 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -6875,6 +6875,7 @@ class MDiv : public MBinaryArithInstruction bool canBeDivideByZero_; bool canBeNegativeDividend_; bool unsigned_; + bool trapOnError_; MDiv(MDefinition* left, MDefinition* right, MIRType type) : MBinaryArithInstruction(left, right), @@ -6882,7 +6883,8 @@ class MDiv : public MBinaryArithInstruction canBeNegativeOverflow_(true), canBeDivideByZero_(true), canBeNegativeDividend_(true), - unsigned_(false) + unsigned_(false), + trapOnError_(false) { if (type != MIRType::Value) specialization_ = type; @@ -6898,10 +6900,11 @@ class MDiv : public MBinaryArithInstruction return new(alloc) MDiv(left, right, type); } static MDiv* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right, - MIRType type, bool unsignd) + MIRType type, bool unsignd, bool trapOnError = false) { MDiv* div = new(alloc) MDiv(left, right, type); div->unsigned_ = unsignd; + div->trapOnError_ = trapOnError; if (type == MIRType::Int32) div->setTruncateKind(Truncate); return div; @@ -6965,6 +6968,10 @@ class MDiv : public MBinaryArithInstruction return isTruncated() || isTruncatedIndirectly(); } + bool trapOnError() const { + return trapOnError_; + } + bool isFloat32Commutative() const override { return true; } void computeRange(TempAllocator& alloc) override; @@ -6980,8 +6987,11 @@ class MDiv : public MBinaryArithInstruction } bool congruentTo(const MDefinition* ins) const override { - return MBinaryArithInstruction::congruentTo(ins) && - unsigned_ == ins->toDiv()->isUnsigned(); + if (!MBinaryArithInstruction::congruentTo(ins)) + return false; + const MDiv* other = ins->toDiv(); + MOZ_ASSERT(other->trapOnError() == trapOnError_); + return unsigned_ == other->isUnsigned(); } ALLOW_CLONE(MDiv) @@ -6993,13 +7003,15 @@ class MMod : public MBinaryArithInstruction bool canBeNegativeDividend_; bool canBePowerOfTwoDivisor_; bool canBeDivideByZero_; + bool trapOnError_; MMod(MDefinition* left, MDefinition* right, MIRType type) : MBinaryArithInstruction(left, right), unsigned_(false), canBeNegativeDividend_(true), canBePowerOfTwoDivisor_(true), - canBeDivideByZero_(true) + canBeDivideByZero_(true), + trapOnError_(false) { if (type != MIRType::Value) specialization_ = type; @@ -7012,10 +7024,11 @@ class MMod : public MBinaryArithInstruction return new(alloc) MMod(left, right, MIRType::Value); } static MMod* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right, - MIRType type, bool unsignd) + MIRType type, bool unsignd, bool trapOnError = false) { MMod* mod = new(alloc) MMod(left, right, type); mod->unsigned_ = unsignd; + mod->trapOnError_ = trapOnError; if (type == MIRType::Int32) mod->setTruncateKind(Truncate); return mod; @@ -7049,6 +7062,10 @@ class MMod : public MBinaryArithInstruction return unsigned_; } + bool trapOnError() const { + return trapOnError_; + } + bool writeRecoverData(CompactBufferWriter& writer) const override; bool canRecoverOnBailout() const override { return specialization_ < MIRType::Object; diff --git a/js/src/jit/RangeAnalysis.cpp b/js/src/jit/RangeAnalysis.cpp index 8746589355..f9d25e0e7e 100644 --- a/js/src/jit/RangeAnalysis.cpp +++ b/js/src/jit/RangeAnalysis.cpp @@ -1919,7 +1919,8 @@ RangeAnalysis::analyzeLoop(MBasicBlock* header) #ifdef DEBUG if (JitSpewEnabled(JitSpew_Range)) { Sprinter sp(GetJitContext()->cx); - sp.init(); + if (!sp.init()) + return false; iterationBound->boundSum.dump(sp); JitSpew(JitSpew_Range, "computed symbolic bound on backedges: %s", sp.string()); diff --git a/js/src/jit/arm/CodeGenerator-arm.cpp b/js/src/jit/arm/CodeGenerator-arm.cpp index 50a00a0dab..3e2e86ddda 100644 --- a/js/src/jit/arm/CodeGenerator-arm.cpp +++ b/js/src/jit/arm/CodeGenerator-arm.cpp @@ -501,12 +501,16 @@ CodeGeneratorARM::divICommon(MDiv* mir, Register lhs, Register rhs, Register out // If EQ (LHS == INT32_MIN), sets EQ if rhs == -1. masm.ma_cmp(rhs, Imm32(-1), Assembler::Equal); if (mir->canTruncateOverflow()) { - // (-INT32_MIN)|0 = INT32_MIN - Label skip; - masm.ma_b(&skip, Assembler::NotEqual); - masm.ma_mov(Imm32(INT32_MIN), output); - masm.ma_b(&done); - masm.bind(&skip); + if (mir->trapOnError()) { + masm.ma_b(wasm::JumpTarget::IntegerOverflow, Assembler::Equal); + } else { + // (-INT32_MIN)|0 = INT32_MIN + Label skip; + masm.ma_b(&skip, Assembler::NotEqual); + masm.ma_mov(Imm32(INT32_MIN), output); + masm.ma_b(&done); + masm.bind(&skip); + } } else { MOZ_ASSERT(mir->fallible()); bailoutIf(Assembler::Equal, snapshot); @@ -517,12 +521,16 @@ CodeGeneratorARM::divICommon(MDiv* mir, Register lhs, Register rhs, Register out if (mir->canBeDivideByZero()) { masm.ma_cmp(rhs, Imm32(0)); if (mir->canTruncateInfinities()) { - // Infinity|0 == 0 - Label skip; - masm.ma_b(&skip, Assembler::NotEqual); - masm.ma_mov(Imm32(0), output); - masm.ma_b(&done); - masm.bind(&skip); + if (mir->trapOnError()) { + masm.ma_b(wasm::JumpTarget::IntegerDivideByZero, Assembler::Equal); + } else { + // Infinity|0 == 0 + Label skip; + masm.ma_b(&skip, Assembler::NotEqual); + masm.ma_mov(Imm32(0), output); + masm.ma_b(&done); + masm.bind(&skip); + } } else { MOZ_ASSERT(mir->fallible()); bailoutIf(Assembler::Equal, snapshot); @@ -544,7 +552,6 @@ CodeGeneratorARM::divICommon(MDiv* mir, Register lhs, Register rhs, Register out void CodeGeneratorARM::visitDivI(LDivI* ins) { - // Extract the registers from this instruction. Register lhs = ToRegister(ins->lhs()); Register rhs = ToRegister(ins->rhs()); Register temp = ToRegister(ins->getTemp(0)); @@ -578,7 +585,6 @@ extern "C" { void CodeGeneratorARM::visitSoftDivI(LSoftDivI* ins) { - // Extract the registers from this instruction. Register lhs = ToRegister(ins->lhs()); Register rhs = ToRegister(ins->rhs()); Register output = ToRegister(ins->output()); @@ -673,12 +679,16 @@ CodeGeneratorARM::modICommon(MMod* mir, Register lhs, Register rhs, Register out masm.ma_cmp(rhs, Imm32(0)); masm.ma_cmp(lhs, Imm32(0), Assembler::LessThan); if (mir->isTruncated()) { - // NaN|0 == 0 and (0 % -X)|0 == 0 - Label skip; - masm.ma_b(&skip, Assembler::NotEqual); - masm.ma_mov(Imm32(0), output); - masm.ma_b(&done); - masm.bind(&skip); + if (mir->trapOnError()) { + masm.ma_b(wasm::JumpTarget::IntegerDivideByZero, Assembler::Equal); + } else { + // NaN|0 == 0 and (0 % -X)|0 == 0 + Label skip; + masm.ma_b(&skip, Assembler::NotEqual); + masm.ma_mov(Imm32(0), output); + masm.ma_b(&done); + masm.bind(&skip); + } } else { MOZ_ASSERT(mir->fallible()); bailoutIf(Assembler::Equal, snapshot); @@ -2648,12 +2658,16 @@ CodeGeneratorARM::generateUDivModZeroCheck(Register rhs, Register output, Label* if (mir->canBeDivideByZero()) { masm.ma_cmp(rhs, Imm32(0)); if (mir->isTruncated()) { - Label skip; - masm.ma_b(&skip, Assembler::NotEqual); - // Infinity|0 == 0 - masm.ma_mov(Imm32(0), output); - masm.ma_b(done); - masm.bind(&skip); + if (mir->trapOnError()) { + masm.ma_b(wasm::JumpTarget::IntegerDivideByZero, Assembler::Equal); + } else { + Label skip; + masm.ma_b(&skip, Assembler::NotEqual); + // Infinity|0 == 0 + masm.ma_mov(Imm32(0), output); + masm.ma_b(done); + masm.bind(&skip); + } } else { // Bailout for divide by zero MOZ_ASSERT(mir->fallible()); @@ -2965,8 +2979,8 @@ CodeGeneratorARM::visitOutOfLineWasmTruncateCheck(OutOfLineWasmTruncateCheck* oo // Handle errors. masm.bind(&fail); - masm.jump(wasm::JumpTarget::IntegerOverflowTrap); + masm.jump(wasm::JumpTarget::IntegerOverflow); masm.bind(&inputIsNaN); - masm.jump(wasm::JumpTarget::InvalidConversionToIntegerTrap); + masm.jump(wasm::JumpTarget::InvalidConversionToInteger); } diff --git a/js/src/jit/arm/Lowering-arm.cpp b/js/src/jit/arm/Lowering-arm.cpp index c9b9b43e6e..c7ca4eb5cd 100644 --- a/js/src/jit/arm/Lowering-arm.cpp +++ b/js/src/jit/arm/Lowering-arm.cpp @@ -314,6 +314,7 @@ LIRGeneratorARM::lowerModI(MMod* mod) return; } if (shift < 31 && (1 << (shift+1)) - 1 == rhs) { + MOZ_ASSERT(rhs); LModMaskI* lir = new(alloc()) LModMaskI(useRegister(mod->lhs()), temp(), temp(), shift+1); if (mod->fallible()) assignSnapshot(lir, Bailout_DoubleOutput); diff --git a/js/src/jit/x64/CodeGenerator-x64.cpp b/js/src/jit/x64/CodeGenerator-x64.cpp index 7802be0915..e6199e5775 100644 --- a/js/src/jit/x64/CodeGenerator-x64.cpp +++ b/js/src/jit/x64/CodeGenerator-x64.cpp @@ -432,29 +432,21 @@ CodeGeneratorX64::visitDivOrModI64(LDivOrModI64* lir) if (lhs != rax) masm.mov(lhs, rax); - // Handle divide by zero. For now match asm.js and return 0, but - // eventually this should trap. + // Handle divide by zero. if (lir->canBeDivideByZero()) { - Label nonZero; - masm.branchTestPtr(Assembler::NonZero, rhs, rhs, &nonZero); - masm.xorl(output, output); - masm.jump(&done); - masm.bind(&nonZero); + masm.testPtr(rhs, rhs); + masm.j(Assembler::Zero, wasm::JumpTarget::IntegerDivideByZero); } - // Handle an integer overflow exception from INT64_MIN / -1. Eventually - // signed integer division should trap, instead of returning the - // LHS (INT64_MIN). + // Handle an integer overflow exception from INT64_MIN / -1. if (lir->canBeNegativeOverflow()) { Label notmin; masm.branchPtr(Assembler::NotEqual, lhs, ImmWord(INT64_MIN), ¬min); masm.branchPtr(Assembler::NotEqual, rhs, ImmWord(-1), ¬min); - if (lir->mir()->isMod()) { + if (lir->mir()->isMod()) masm.xorl(output, output); - } else { - if (lhs != output) - masm.mov(lhs, output); - } + else + masm.jump(wasm::JumpTarget::IntegerOverflow); masm.jump(&done); masm.bind(¬min); } @@ -484,14 +476,10 @@ CodeGeneratorX64::visitUDivOrMod64(LUDivOrMod64* lir) Label done; - // Prevent divide by zero. For now match asm.js and return 0, but - // eventually this should trap. + // Prevent divide by zero. if (lir->canBeDivideByZero()) { - Label nonZero; - masm.branchTestPtr(Assembler::NonZero, rhs, rhs, &nonZero); - masm.xorl(output, output); - masm.jump(&done); - masm.bind(&nonZero); + masm.testPtr(rhs, rhs); + masm.j(Assembler::Zero, wasm::JumpTarget::IntegerDivideByZero); } // Zero extend the lhs into rdx to make (rdx:rax). diff --git a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp index 64f0b9a0e7..0a30118765 100644 --- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp @@ -1115,9 +1115,13 @@ CodeGeneratorX86Shared::visitUDivOrMod(LUDivOrMod* ins) if (ins->canBeDivideByZero()) { masm.test32(rhs, rhs); if (ins->mir()->isTruncated()) { - if (!ool) - ool = new(alloc()) ReturnZero(output); - masm.j(Assembler::Zero, ool->entry()); + if (ins->trapOnError()) { + masm.j(Assembler::Zero, wasm::JumpTarget::IntegerDivideByZero); + } else { + if (!ool) + ool = new(alloc()) ReturnZero(output); + masm.j(Assembler::Zero, ool->entry()); + } } else { bailoutIf(Assembler::Zero, ins->snapshot()); } @@ -1159,11 +1163,14 @@ CodeGeneratorX86Shared::visitUDivOrModConstant(LUDivOrModConstant *ins) { bool isDiv = (output == edx); if (d == 0) { - if (ins->mir()->isTruncated()) - masm.xorl(output, output); - else + if (ins->mir()->isTruncated()) { + if (ins->trapOnError()) + masm.jump(wasm::JumpTarget::IntegerDivideByZero); + else + masm.xorl(output, output); + } else { bailout(ins->snapshot()); - + } return; } @@ -1261,7 +1268,7 @@ CodeGeneratorX86Shared::visitDivPowTwoI(LDivPowTwoI* ins) bailoutIf(Assembler::Zero, ins->snapshot()); } - if (shift != 0) { + if (shift) { if (!mir->isTruncated()) { // If the remainder is != 0, bailout since this must be a double. masm.test32(lhs, Imm32(UINT32_MAX >> (32 - shift))); @@ -1288,20 +1295,19 @@ CodeGeneratorX86Shared::visitDivPowTwoI(LDivPowTwoI* ins) if (negativeDivisor) masm.negl(lhs); } - } else if (shift == 0) { - if (negativeDivisor) { - // INT32_MIN / -1 overflows. - masm.negl(lhs); - if (!mir->isTruncated()) - bailoutIf(Assembler::Overflow, ins->snapshot()); - } + return; + } - else if (mir->isUnsigned() && !mir->isTruncated()) { - // Unsigned division by 1 can overflow if output is not - // truncated. - masm.test32(lhs, lhs); - bailoutIf(Assembler::Signed, ins->snapshot()); - } + if (negativeDivisor) { + // INT32_MIN / -1 overflows. + masm.negl(lhs); + if (!mir->isTruncated()) + bailoutIf(Assembler::Overflow, ins->snapshot()); + } else if (mir->isUnsigned() && !mir->isTruncated()) { + // Unsigned division by 1 can overflow if output is not + // truncated. + masm.test32(lhs, lhs); + bailoutIf(Assembler::Signed, ins->snapshot()); } } @@ -1414,7 +1420,9 @@ CodeGeneratorX86Shared::visitDivI(LDivI* ins) // Handle divide by zero. if (mir->canBeDivideByZero()) { masm.test32(rhs, rhs); - if (mir->canTruncateInfinities()) { + if (mir->trapOnError()) { + masm.j(Assembler::Zero, wasm::JumpTarget::IntegerDivideByZero); + } else if (mir->canTruncateInfinities()) { // Truncated division by zero is zero (Infinity|0 == 0) if (!ool) ool = new(alloc()) ReturnZero(output); @@ -1431,7 +1439,9 @@ CodeGeneratorX86Shared::visitDivI(LDivI* ins) masm.cmp32(lhs, Imm32(INT32_MIN)); masm.j(Assembler::NotEqual, ¬min); masm.cmp32(rhs, Imm32(-1)); - if (mir->canTruncateOverflow()) { + if (mir->trapOnError()) { + masm.j(Assembler::Equal, wasm::JumpTarget::IntegerOverflow); + } else if (mir->canTruncateOverflow()) { // (-INT32_MIN)|0 == INT32_MIN and INT32_MIN is already in the // output register (lhs == eax). masm.j(Assembler::Equal, &done); @@ -1577,9 +1587,13 @@ CodeGeneratorX86Shared::visitModI(LModI* ins) if (ins->mir()->canBeDivideByZero()) { masm.test32(rhs, rhs); if (ins->mir()->isTruncated()) { - if (!ool) - ool = new(alloc()) ReturnZero(edx); - masm.j(Assembler::Zero, ool->entry()); + if (ins->mir()->trapOnError()) { + masm.j(Assembler::Zero, wasm::JumpTarget::IntegerDivideByZero); + } else { + if (!ool) + ool = new(alloc()) ReturnZero(edx); + masm.j(Assembler::Zero, ool->entry()); + } } else { bailoutIf(Assembler::Zero, ins->snapshot()); } @@ -2498,7 +2512,7 @@ CodeGeneratorX86Shared::visitOutOfLineSimdFloatToIntCheck(OutOfLineSimdFloatToIn masm.jump(ool->rejoin()); if (gen->compilingAsmJS()) { - masm.bindLater(&onConversionError, wasm::JumpTarget::ConversionError); + masm.bindLater(&onConversionError, wasm::JumpTarget::ImpreciseSimdConversion); } else { masm.bind(&onConversionError); bailout(ool->ins()->snapshot()); @@ -2577,7 +2591,7 @@ CodeGeneratorX86Shared::visitFloat32x4ToUint32x4(LFloat32x4ToUint32x4* ins) masm.cmp32(temp, Imm32(0)); if (gen->compilingAsmJS()) - masm.j(Assembler::NotEqual, wasm::JumpTarget::ConversionError); + masm.j(Assembler::NotEqual, wasm::JumpTarget::ImpreciseSimdConversion); else bailoutIf(Assembler::NotEqual, ins->snapshot()); } @@ -4071,10 +4085,10 @@ CodeGeneratorX86Shared::visitOutOfLineWasmTruncateCheck(OutOfLineWasmTruncateChe // Handle errors. masm.bind(&fail); - masm.jump(wasm::JumpTarget::IntegerOverflowTrap); + masm.jump(wasm::JumpTarget::IntegerOverflow); masm.bind(&inputIsNaN); - masm.jump(wasm::JumpTarget::InvalidConversionToIntegerTrap); + masm.jump(wasm::JumpTarget::InvalidConversionToInteger); } void diff --git a/js/src/jit/x86-shared/LIR-x86-shared.h b/js/src/jit/x86-shared/LIR-x86-shared.h index 3f5dbef974..9d036313f3 100644 --- a/js/src/jit/x86-shared/LIR-x86-shared.h +++ b/js/src/jit/x86-shared/LIR-x86-shared.h @@ -161,6 +161,12 @@ class LUDivOrMod : public LBinaryMath<1> return mir_->toMod()->canBeDivideByZero(); return mir_->toDiv()->canBeDivideByZero(); } + + bool trapOnError() const { + if (mir_->isMod()) + return mir_->toMod()->trapOnError(); + return mir_->toDiv()->trapOnError(); + } }; class LUDivOrModConstant : public LInstructionHelper<1, 1, 1> @@ -192,6 +198,11 @@ class LUDivOrModConstant : public LInstructionHelper<1, 1, 1> return mir_->toMod()->canBeNegativeDividend(); return mir_->toDiv()->canBeNegativeDividend(); } + bool trapOnError() const { + if (mir_->isMod()) + return mir_->toMod()->trapOnError(); + return mir_->toDiv()->trapOnError(); + } }; class LModPowTwoI : public LInstructionHelper<1,1,0> diff --git a/js/src/js.msg b/js/src/js.msg index 3a73a765a3..3845e559d4 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -352,6 +352,7 @@ MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG, 0, JSEXN_TYPEERR, "second argument, if pr MSG_DEF(JSMSG_WASM_UNREACHABLE, 0, JSEXN_ERR, "reached unreachable trap") MSG_DEF(JSMSG_WASM_INTEGER_OVERFLOW, 0, JSEXN_ERR, "integer overflow") MSG_DEF(JSMSG_WASM_INVALID_CONVERSION, 0, JSEXN_ERR, "invalid conversion to integer") +MSG_DEF(JSMSG_WASM_INT_DIVIDE_BY_ZERO, 0, JSEXN_ERR, "integer divide by zero") // Proxy MSG_DEF(JSMSG_BAD_TRAP_RETURN_VALUE, 2, JSEXN_TYPEERR,"trap {1} for {0} returned a primitive value") diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build index 22979d9d03..519b585186 100644 --- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -55,6 +55,7 @@ UNIFIED_SOURCES += [ 'testIntString.cpp', 'testIntTypesABI.cpp', 'testIsInsideNursery.cpp', + 'testIteratorObject.cpp', 'testJSEvaluateScript.cpp', 'testLookup.cpp', 'testLooselyEqual.cpp', diff --git a/js/src/jsapi-tests/testGCMarking.cpp b/js/src/jsapi-tests/testGCMarking.cpp index 9f5f12b387..5816226966 100644 --- a/js/src/jsapi-tests/testGCMarking.cpp +++ b/js/src/jsapi-tests/testGCMarking.cpp @@ -5,6 +5,10 @@ * 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 "jsapi.h" + +#include "js/RootingAPI.h" +#include "js/SliceBudget.h" #include "jsapi-tests/tests.h" class CCWTestTracer : public JS::CallbackTracer { @@ -75,3 +79,147 @@ BEGIN_TEST(testTracingIncomingCCWs) return true; } END_TEST(testTracingIncomingCCWs) + +BEGIN_TEST(testIncrementalRoots) +{ + JSRuntime* rt = cx->runtime(); + +#ifdef JS_GC_ZEAL + // Disable zeal modes because this test needs to control exactly when the GC happens. + JS_SetGCZeal(cx, 0, 100); +#endif + + // Construct a big object graph to mark. In JS, the resulting object graph + // is equivalent to: + // + // leaf = {}; + // leaf2 = {}; + // root = { 'obj': { 'obj': ... { 'obj': leaf, 'leaf2': leaf2 } ... } } + // + // with leafOwner the object that has the 'obj' and 'leaf2' properties. + + JS::RootedObject obj(cx, JS_NewObject(cx, nullptr)); + if (!obj) + return false; + + JS::RootedObject root(cx, obj); + + JS::RootedObject leaf(cx); + JS::RootedObject leafOwner(cx); + + for (size_t i = 0; i < 3000; i++) { + JS::RootedObject subobj(cx, JS_NewObject(cx, nullptr)); + if (!subobj) + return false; + if (!JS_DefineProperty(cx, obj, "obj", subobj, 0)) + return false; + leafOwner = obj; + obj = subobj; + leaf = subobj; + } + + // Give the leaf owner a second leaf. + { + JS::RootedObject leaf2(cx, JS_NewObject(cx, nullptr)); + if (!leaf2) + return false; + if (!JS_DefineProperty(cx, leafOwner, "leaf2", leaf2, 0)) + return false; + } + + // This is marked during markRuntime + JS::AutoObjectVector vec(cx); + if (!vec.append(root)) + return false; + + // Tenure everything so intentionally unrooted objects don't move before we + // can use them. + cx->minorGC(JS::gcreason::API); + + // Release all roots except for the AutoObjectVector. + obj = root = nullptr; + + // We need to manipulate interior nodes, but the JSAPI understandably wants + // to make it difficult to do that without rooting things on the stack (by + // requiring Handle parameters). We can do it anyway by using + // fromMarkedLocation. The hazard analysis is OK with this because the + // unrooted variables are not live after they've been pointed to via + // fromMarkedLocation; you're essentially lying to the analysis, saying + // that the unrooted variables are rooted. + // + // The analysis will report this lie in its listing of "unsafe references", + // but we do not break the build based on those as there are too many false + // positives. + JSObject* unrootedLeaf = leaf; + JS::Value unrootedLeafValue = JS::ObjectValue(*leaf); + JSObject* unrootedLeafOwner = leafOwner; + JS::HandleObject leafHandle = JS::HandleObject::fromMarkedLocation(&unrootedLeaf); + JS::HandleValue leafValueHandle = JS::HandleValue::fromMarkedLocation(&unrootedLeafValue); + JS::HandleObject leafOwnerHandle = JS::HandleObject::fromMarkedLocation(&unrootedLeafOwner); + leaf = leafOwner = nullptr; + + // Do the root marking slice. This should mark 'root' and a bunch of its + // descendants. It shouldn't make it all the way through (it gets a budget + // of 1000, and the graph is about 3000 objects deep). + js::SliceBudget budget(js::WorkBudget(1000)); + JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL); + rt->gc.startDebugGC(GC_NORMAL, budget); + + // We'd better be between iGC slices now. There's always a risk that + // something will decide that we need to do a full GC (such as gczeal, but + // that is turned off.) + MOZ_ASSERT(JS::IsIncrementalGCInProgress(rt)); + + // And assert that the mark bits are as we expect them to be. + MOZ_ASSERT(vec[0]->asTenured().isMarked()); + MOZ_ASSERT(!leafHandle->asTenured().isMarked()); + MOZ_ASSERT(!leafOwnerHandle->asTenured().isMarked()); + + // Remember the current GC number so we can assert that no GC occurs + // between operations. + auto currentGCNumber = rt->gc.gcNumber(); + + // Now do the incremental GC's worst nightmare: rip an unmarked object + // 'leaf' out of the graph and stick it into an already-marked region (hang + // it off the un-prebarriered root, in fact). The pre-barrier on the + // overwrite of the source location should cause this object to be marked. + if (!JS_SetProperty(cx, leafOwnerHandle, "obj", JS::UndefinedHandleValue)) + return false; + MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber); + if (!JS_SetProperty(cx, vec[0], "newobj", leafValueHandle)) + return false; + MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber); + MOZ_ASSERT(leafHandle->asTenured().isMarked()); + + // Also take an unmarked object 'leaf2' from the graph and add an + // additional edge from the root to it. This will not be marked by any + // pre-barrier, but it is still in the live graph so it will eventually get + // marked. + // + // Note that the root->leaf2 edge will *not* be marked through, since the + // root is already marked, but that only matters if doing a compacting GC + // and the compacting GC repeats the whole marking phase to update + // pointers. + { + JS::RootedValue leaf2(cx); + if (!JS_GetProperty(cx, leafOwnerHandle, "leaf2", &leaf2)) + return false; + MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber); + MOZ_ASSERT(!leaf2.toObject().asTenured().isMarked()); + if (!JS_SetProperty(cx, vec[0], "leafcopy", leaf2)) + return false; + MOZ_ASSERT(rt->gc.gcNumber() == currentGCNumber); + MOZ_ASSERT(!leaf2.toObject().asTenured().isMarked()); + } + + // Finish the GC using an unlimited budget. + auto unlimited = js::SliceBudget::unlimited(); + rt->gc.debugGCSlice(unlimited); + + // Access the leaf object to try to trigger a crash if it is dead. + if (!JS_SetProperty(cx, leafHandle, "toes", JS::UndefinedHandleValue)) + return false; + + return true; +} +END_TEST(testIncrementalRoots) diff --git a/js/src/jsapi-tests/testIteratorObject.cpp b/js/src/jsapi-tests/testIteratorObject.cpp new file mode 100644 index 0000000000..8437f81ff1 --- /dev/null +++ b/js/src/jsapi-tests/testIteratorObject.cpp @@ -0,0 +1,31 @@ +/* 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 "jsapi-tests/tests.h" + +BEGIN_TEST(testIteratorObject) +{ + using namespace js; + JS::RootedValue result(cx); + + EVAL("new Map([['key1', 'value1'], ['key2', 'value2']]).entries()", &result); + + CHECK(result.isObject()); + JS::RootedObject obj1(cx, &result.toObject()); + ESClassValue class1 = ESClass_Other; + CHECK(GetBuiltinClass(cx, obj1, &class1)); + CHECK(class1 == ESClass_MapIterator); + + EVAL("new Set(['value1', 'value2']).entries()", &result); + + CHECK(result.isObject()); + JS::RootedObject obj2(cx, &result.toObject()); + ESClassValue class2 = ESClass_Other; + CHECK(GetBuiltinClass(cx, obj2, &class2)); + CHECK(class2 == ESClass_SetIterator); + + return true; +} +END_TEST(testIteratorObject) + diff --git a/js/src/jsapi-tests/testMappedArrayBuffer.cpp b/js/src/jsapi-tests/testMappedArrayBuffer.cpp index 9877b7c128..a33ae4694a 100644 --- a/js/src/jsapi-tests/testMappedArrayBuffer.cpp +++ b/js/src/jsapi-tests/testMappedArrayBuffer.cpp @@ -157,7 +157,9 @@ bool TestTransferObject() // Create an Array of transferable values. JS::AutoValueVector argv(cx); - argv.append(v1); + if (!argv.append(v1)) + return false; + JS::RootedObject obj(cx, JS_NewArrayObject(cx, JS::HandleValueArray::subarray(argv, 0, 1))); CHECK(obj); JS::RootedValue transferable(cx, JS::ObjectValue(*obj)); diff --git a/js/src/jsapi-tests/testSetPropertyIgnoringNamedGetter.cpp b/js/src/jsapi-tests/testSetPropertyIgnoringNamedGetter.cpp index 3814ae6a85..3150986dd9 100644 --- a/js/src/jsapi-tests/testSetPropertyIgnoringNamedGetter.cpp +++ b/js/src/jsapi-tests/testSetPropertyIgnoringNamedGetter.cpp @@ -11,9 +11,10 @@ using namespace js; using namespace JS; -class CustomProxyHandler : public DirectProxyHandler { +class CustomProxyHandler : public Wrapper +{ public: - CustomProxyHandler() : DirectProxyHandler(nullptr) {} + CustomProxyHandler() : Wrapper(0) {} bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, MutableHandle desc) const override @@ -31,7 +32,7 @@ class CustomProxyHandler : public DirectProxyHandler { ObjectOpResult& result) const override { Rooted desc(cx); - if (!DirectProxyHandler::getPropertyDescriptor(cx, proxy, id, &desc)) + if (!Wrapper::getPropertyDescriptor(cx, proxy, id, &desc)) return false; return SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, desc, result); } @@ -53,8 +54,8 @@ class CustomProxyHandler : public DirectProxyHandler { } if (ownOnly) - return DirectProxyHandler::getOwnPropertyDescriptor(cx, proxy, id, desc); - return DirectProxyHandler::getPropertyDescriptor(cx, proxy, id, desc); + return Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc); + return Wrapper::getPropertyDescriptor(cx, proxy, id, desc); } }; diff --git a/js/src/jsapi-tests/testThreadingConditionVariable.cpp b/js/src/jsapi-tests/testThreadingConditionVariable.cpp index 34b599be34..8bff09b182 100644 --- a/js/src/jsapi-tests/testThreadingConditionVariable.cpp +++ b/js/src/jsapi-tests/testThreadingConditionVariable.cpp @@ -46,7 +46,7 @@ struct TestState { BEGIN_TEST(testThreadingConditionVariable) { - auto state = MakeUnique(); + auto state = mozilla::MakeUnique(); { js::UniqueLock lock(state->mutex); while (!state->flag) @@ -62,7 +62,7 @@ END_TEST(testThreadingConditionVariable) BEGIN_TEST(testThreadingConditionVariablePredicate) { - auto state = MakeUnique(); + auto state = mozilla::MakeUnique(); { js::UniqueLock lock(state->mutex); state->condition.wait(lock, [&state]() {return state->flag;}); @@ -77,7 +77,7 @@ END_TEST(testThreadingConditionVariablePredicate) BEGIN_TEST(testThreadingConditionVariableUntilOkay) { - auto state = MakeUnique(); + auto state = mozilla::MakeUnique(); { js::UniqueLock lock(state->mutex); while (!state->flag) { @@ -96,7 +96,7 @@ END_TEST(testThreadingConditionVariableUntilOkay) BEGIN_TEST(testThreadingConditionVariableUntilTimeout) { - auto state = MakeUnique(false); + auto state = mozilla::MakeUnique(false); { js::UniqueLock lock(state->mutex); while (!state->flag) { @@ -122,7 +122,7 @@ END_TEST(testThreadingConditionVariableUntilTimeout) BEGIN_TEST(testThreadingConditionVariableUntilOkayPredicate) { - auto state = MakeUnique(); + auto state = mozilla::MakeUnique(); { js::UniqueLock lock(state->mutex); auto to = mozilla::TimeStamp::Now() + mozilla::TimeDuration::FromSeconds(600); @@ -139,7 +139,7 @@ END_TEST(testThreadingConditionVariableUntilOkayPredicate) BEGIN_TEST(testThreadingConditionVariableUntilTimeoutPredicate) { - auto state = MakeUnique(false); + auto state = mozilla::MakeUnique(false); { js::UniqueLock lock(state->mutex); auto to = mozilla::TimeStamp::Now() + mozilla::TimeDuration::FromMilliseconds(10); @@ -154,7 +154,7 @@ END_TEST(testThreadingConditionVariableUntilTimeoutPredicate) BEGIN_TEST(testThreadingConditionVariableForOkay) { - auto state = MakeUnique(); + auto state = mozilla::MakeUnique(); { js::UniqueLock lock(state->mutex); while (!state->flag) { @@ -173,7 +173,7 @@ END_TEST(testThreadingConditionVariableForOkay) BEGIN_TEST(testThreadingConditionVariableForTimeout) { - auto state = MakeUnique(false); + auto state = mozilla::MakeUnique(false); { js::UniqueLock lock(state->mutex); while (!state->flag) { @@ -199,7 +199,7 @@ END_TEST(testThreadingConditionVariableForTimeout) BEGIN_TEST(testThreadingConditionVariableForOkayPredicate) { - auto state = MakeUnique(); + auto state = mozilla::MakeUnique(); { js::UniqueLock lock(state->mutex); auto duration = mozilla::TimeDuration::FromSeconds(600); @@ -216,7 +216,7 @@ END_TEST(testThreadingConditionVariableForOkayPredicate) BEGIN_TEST(testThreadingConditionVariableForTimeoutPredicate) { - auto state = MakeUnique(false); + auto state = mozilla::MakeUnique(false); { js::UniqueLock lock(state->mutex); auto duration = mozilla::TimeDuration::FromMilliseconds(10); diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 38859652cf..eb15b900b5 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4186,6 +4186,32 @@ JS::FinishOffThreadScript(JSContext* maybecx, JSRuntime* rt, void* token) } } +JS_PUBLIC_API(bool) +JS::CompileOffThreadModule(JSContext* cx, const ReadOnlyCompileOptions& options, + const char16_t* chars, size_t length, + OffThreadCompileCallback callback, void* callbackData) +{ + MOZ_ASSERT(CanCompileOffThread(cx, options, length)); + return StartOffThreadParseModule(cx, options, chars, length, callback, callbackData); +} + +JS_PUBLIC_API(JSObject*) +JS::FinishOffThreadModule(JSContext* maybecx, JSRuntime* rt, void* token) +{ + MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt)); + + if (maybecx) { + RootedObject module(maybecx); + { + AutoLastFrameCheck lfc(maybecx); + module = HelperThreadState().finishModuleParseTask(maybecx, rt, token); + } + return module; + } else { + return HelperThreadState().finishModuleParseTask(maybecx, rt, token); + } +} + JS_PUBLIC_API(bool) JS_CompileScript(JSContext* cx, const char* ascii, size_t length, const JS::CompileOptions& options, MutableHandleScript script) @@ -4615,6 +4641,85 @@ JS::Evaluate(JSContext* cx, const ReadOnlyCompileOptions& optionsArg, return ::Evaluate(cx, optionsArg, filename, rval); } +JS_PUBLIC_API(JSFunction*) +JS::GetModuleResolveHook(JSContext* cx) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + return cx->global()->moduleResolveHook(); +} + +JS_PUBLIC_API(void) +JS::SetModuleResolveHook(JSContext* cx, HandleFunction func) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, func); + cx->global()->setModuleResolveHook(func); +} + +JS_PUBLIC_API(bool) +JS::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options, + SourceBufferHolder& srcBuf, JS::MutableHandleObject module) +{ + MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment())); + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + + AutoLastFrameCheck lfc(cx); + + module.set(frontend::CompileModule(cx, options, srcBuf)); + return !!module; +} + +JS_PUBLIC_API(void) +JS::SetModuleHostDefinedField(JSObject* module, JS::Value value) +{ + module->as().setHostDefinedField(value); +} + +JS_PUBLIC_API(JS::Value) +JS::GetModuleHostDefinedField(JSObject* module) +{ + return module->as().hostDefinedField(); +} + +JS_PUBLIC_API(bool) +JS::ModuleDeclarationInstantiation(JSContext* cx, JS::HandleObject moduleArg) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, moduleArg); + return ModuleObject::DeclarationInstantiation(cx, moduleArg.as()); +} + +JS_PUBLIC_API(bool) +JS::ModuleEvaluation(JSContext* cx, JS::HandleObject moduleArg) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, moduleArg); + return ModuleObject::Evaluation(cx, moduleArg.as()); +} + +JS_PUBLIC_API(JSObject*) +JS::GetRequestedModules(JSContext* cx, JS::HandleObject moduleArg) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, moduleArg); + return &moduleArg->as().requestedModules(); +} + +JS_PUBLIC_API(JSScript*) +JS::GetModuleScript(JSContext* cx, JS::HandleObject moduleArg) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, moduleArg); + return moduleArg->as().script(); +} + static JSObject* JS_NewHelper(JSContext* cx, HandleObject ctor, const JS::HandleValueArray& inputArgs) { diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 2cfe230f96..ea2d1f4ba0 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -124,14 +124,14 @@ class MOZ_RAII AutoVectorRooterBase : protected AutoGCRooter size_t length() const { return vector.length(); } bool empty() const { return vector.empty(); } - bool append(const T& v) { return vector.append(v); } - bool appendN(const T& v, size_t len) { return vector.appendN(v, len); } - bool append(const T* ptr, size_t len) { return vector.append(ptr, len); } - bool appendAll(const AutoVectorRooterBase& other) { + MOZ_MUST_USE bool append(const T& v) { return vector.append(v); } + MOZ_MUST_USE bool appendN(const T& v, size_t len) { return vector.appendN(v, len); } + MOZ_MUST_USE bool append(const T* ptr, size_t len) { return vector.append(ptr, len); } + MOZ_MUST_USE bool appendAll(const AutoVectorRooterBase& other) { return vector.appendAll(other.vector); } - bool insert(T* p, const T& val) { return vector.insert(p, val); } + MOZ_MUST_USE bool insert(T* p, const T& val) { return vector.insert(p, val); } /* For use when space has already been reserved. */ void infallibleAppend(const T& v) { vector.infallibleAppend(v); } @@ -139,7 +139,7 @@ class MOZ_RAII AutoVectorRooterBase : protected AutoGCRooter void popBack() { vector.popBack(); } T popCopy() { return vector.popCopy(); } - bool growBy(size_t inc) { + MOZ_MUST_USE bool growBy(size_t inc) { size_t oldLength = vector.length(); if (!vector.growByUninitialized(inc)) return false; @@ -147,7 +147,7 @@ class MOZ_RAII AutoVectorRooterBase : protected AutoGCRooter return true; } - bool resize(size_t newLength) { + MOZ_MUST_USE bool resize(size_t newLength) { size_t oldLength = vector.length(); if (newLength <= oldLength) { vector.shrinkBy(oldLength - newLength); @@ -161,7 +161,7 @@ class MOZ_RAII AutoVectorRooterBase : protected AutoGCRooter void clear() { vector.clear(); } - bool reserve(size_t newLength) { + MOZ_MUST_USE bool reserve(size_t newLength) { return vector.reserve(newLength); } @@ -761,6 +761,16 @@ class MOZ_STACK_CLASS SourceBufferHolder final } } + SourceBufferHolder(SourceBufferHolder&& other) + : data_(other.data_), + length_(other.length_), + ownsChars_(other.ownsChars_) + { + other.data_ = nullptr; + other.length_ = 0; + other.ownsChars_ = false; + } + ~SourceBufferHolder() { if (ownsChars_) js_free(const_cast(data_)); @@ -4142,6 +4152,14 @@ CompileOffThread(JSContext* cx, const ReadOnlyCompileOptions& options, extern JS_PUBLIC_API(JSScript*) FinishOffThreadScript(JSContext* maybecx, JSRuntime* rt, void* token); +extern JS_PUBLIC_API(bool) +CompileOffThreadModule(JSContext* cx, const ReadOnlyCompileOptions& options, + const char16_t* chars, size_t length, + OffThreadCompileCallback callback, void* callbackData); + +extern JS_PUBLIC_API(JSObject*) +FinishOffThreadModule(JSContext* maybecx, JSRuntime* rt, void* token); + /** * Compile a function with scopeChain plus the global as its scope chain. * scopeChain must contain objects in the current compartment of cx. The actual @@ -4285,6 +4303,79 @@ extern JS_PUBLIC_API(bool) Evaluate(JSContext* cx, const ReadOnlyCompileOptions& options, const char* filename, JS::MutableHandleValue rval); +/** + * Get the HostResolveImportedModule hook for a global. + */ +extern JS_PUBLIC_API(JSFunction*) +GetModuleResolveHook(JSContext* cx); + +/** + * Set the HostResolveImportedModule hook for a global to the given function. + */ +extern JS_PUBLIC_API(void) +SetModuleResolveHook(JSContext* cx, JS::HandleFunction func); + +/** + * Parse the given source buffer as a module in the scope of the current global + * of cx and return a source text module record. + */ +extern JS_PUBLIC_API(bool) +CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options, + SourceBufferHolder& srcBuf, JS::MutableHandleObject moduleRecord); + +/** + * Set the [[HostDefined]] field of a source text module record to the given + * value. + */ +extern JS_PUBLIC_API(void) +SetModuleHostDefinedField(JSObject* module, JS::Value value); + +/** + * Get the [[HostDefined]] field of a source text module record. + */ +extern JS_PUBLIC_API(JS::Value) +GetModuleHostDefinedField(JSObject* module); + +/* + * Perform the ModuleDeclarationInstantiation operation on on the give source + * text module record. + * + * This transitively resolves all module dependencies (calling the + * HostResolveImportedModule hook) and initializes the environment record for + * the module. + */ +extern JS_PUBLIC_API(bool) +ModuleDeclarationInstantiation(JSContext* cx, JS::HandleObject moduleRecord); + +/* + * Perform the ModuleEvaluation operation on on the give source text module + * record. + * + * This does nothing if this module has already been evaluated. Otherwise, it + * transitively evaluates all dependences of this module and then evaluates this + * module. + * + * ModuleDeclarationInstantiation must have completed prior to calling this. + */ +extern JS_PUBLIC_API(bool) +ModuleEvaluation(JSContext* cx, JS::HandleObject moduleRecord); + +/* + * Get a list of the module specifiers used by a source text module + * record to request importation of modules. + * + * The result is a JavaScript array of string values. ForOfIterator can be used + * to extract the individual strings. + */ +extern JS_PUBLIC_API(JSObject*) +GetRequestedModules(JSContext* cx, JS::HandleObject moduleRecord); + +/* + * Get the script associated with a module. + */ +extern JS_PUBLIC_API(JSScript*) +GetModuleScript(JSContext* cx, JS::HandleObject moduleRecord); + } /* namespace JS */ extern JS_PUBLIC_API(bool) diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 3a4574f26e..2daba309a1 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -34,6 +34,8 @@ #include "jsobjinlines.h" #include "jsscriptinlines.h" +#include "vm/NativeObject-inl.h" + using namespace js; using namespace js::gc; using namespace js::jit; @@ -217,23 +219,19 @@ class WrapperMapRef : public BufferableRef WrapperMapRef(WrapperMap* map, const CrossCompartmentKey& key) : map(map), key(key) {} + struct TraceFunctor { + JSTracer* trc_; + const char* name_; + TraceFunctor(JSTracer *trc, const char* name) : trc_(trc), name_(name) {} + + using ReturnType = void; + template void operator()(T* t) { TraceManuallyBarrieredEdge(trc_, t, name_); } + }; void trace(JSTracer* trc) override { CrossCompartmentKey prior = key; - if (key.debugger) - TraceManuallyBarrieredEdge(trc, &key.debugger, "CCW debugger"); - if (key.kind == CrossCompartmentKey::ObjectWrapper || - key.kind == CrossCompartmentKey::DebuggerObject || - key.kind == CrossCompartmentKey::DebuggerEnvironment || - key.kind == CrossCompartmentKey::DebuggerSource || - key.kind == CrossCompartmentKey::DebuggerWasmScript || - key.kind == CrossCompartmentKey::DebuggerWasmSource) - { - MOZ_ASSERT(IsInsideNursery(key.wrapped) || - key.wrapped->asTenured().getTraceKind() == JS::TraceKind::Object); - TraceManuallyBarrieredEdge(trc, reinterpret_cast(&key.wrapped), - "CCW wrapped object"); - } - if (key.debugger == prior.debugger && key.wrapped == prior.wrapped) + key.applyToWrapped(TraceFunctor(trc, "ccw wrapped")); + key.applyToDebugger(TraceFunctor(trc, "ccw debugger")); + if (key == prior) return; /* Look for the original entry, which might have been removed. */ @@ -247,6 +245,13 @@ class WrapperMapRef : public BufferableRef }; #ifdef JSGC_HASH_TABLE_CHECKS +namespace { +struct CheckGCThingAfterMovingGCFunctor { + using ReturnType = void; + template void operator()(T* t) { CheckGCThingAfterMovingGC(*t); } +}; +} // namespace (anonymous) + void JSCompartment::checkWrapperMapAfterMovingGC() { @@ -256,24 +261,28 @@ JSCompartment::checkWrapperMapAfterMovingGC() * are discoverable. */ for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { - CrossCompartmentKey key = e.front().key(); - CheckGCThingAfterMovingGC(key.debugger); - CheckGCThingAfterMovingGC(key.wrapped); - CheckGCThingAfterMovingGC( - static_cast(e.front().value().unbarrieredGet().toGCThing())); + e.front().mutableKey().applyToWrapped(CheckGCThingAfterMovingGCFunctor()); + e.front().mutableKey().applyToDebugger(CheckGCThingAfterMovingGCFunctor()); - WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(key); + WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(e.front().key()); MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front()); } } #endif +namespace { +struct IsInsideNurseryFunctor { + using ReturnType = bool; + template bool operator()(T tp) { return IsInsideNursery(*tp); } +}; +} // namespace (anonymous) + bool -JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped, const js::Value& wrapper) +JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped, + const js::Value& wrapper) { - MOZ_ASSERT(wrapped.wrapped); - MOZ_ASSERT_IF(wrapped.kind == CrossCompartmentKey::StringWrapper, wrapper.isString()); - MOZ_ASSERT_IF(wrapped.kind != CrossCompartmentKey::StringWrapper, wrapper.isObject()); + MOZ_ASSERT(wrapped.is() == wrapper.isString()); + MOZ_ASSERT_IF(!wrapped.is(), wrapper.isObject()); /* There's no point allocating wrappers in the nursery since we will tenure them anyway. */ MOZ_ASSERT(!IsInsideNursery(static_cast(wrapper.toGCThing()))); @@ -283,7 +292,9 @@ JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped, con return false; } - if (IsInsideNursery(wrapped.wrapped) || IsInsideNursery(wrapped.debugger)) { + if (const_cast(wrapped).applyToWrapped(IsInsideNurseryFunctor()) || + const_cast(wrapped).applyToDebugger(IsInsideNurseryFunctor())) + { WrapperMapRef ref(&crossCompartmentWrappers, wrapped); cx->runtime()->gc.storeBuffer.putGeneric(ref); } @@ -563,7 +574,7 @@ JSCompartment::traceOutgoingCrossCompartmentWrappers(JSTracer* trc) for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { Value v = e.front().value().unbarrieredGet(); - if (e.front().key().kind == CrossCompartmentKey::ObjectWrapper) { + if (e.front().key().is()) { ProxyObject* wrapper = &v.toObject().as(); /* @@ -767,36 +778,32 @@ JSCompartment::sweepCrossCompartmentWrappers() crossCompartmentWrappers.sweep(); } +namespace { +struct TraceRootFunctor { + JSTracer* trc; + const char* name; + TraceRootFunctor(JSTracer* trc, const char* name) : trc(trc), name(name) {} + using ReturnType = void; + template ReturnType operator()(T* t) { return TraceRoot(trc, t, name); } +}; +struct NeedsSweepUnbarrieredFunctor { + using ReturnType = bool; + template bool operator()(T* t) const { return IsAboutToBeFinalizedUnbarriered(t); } +}; +} // namespace (anonymous) + +void +CrossCompartmentKey::trace(JSTracer* trc) +{ + applyToWrapped(TraceRootFunctor(trc, "CrossCompartmentKey::wrapped")); + applyToDebugger(TraceRootFunctor(trc, "CrossCompartmentKey::debugger")); +} + bool CrossCompartmentKey::needsSweep() { - bool keyDying; - switch (kind) { - case CrossCompartmentKey::ObjectWrapper: - case CrossCompartmentKey::DebuggerObject: - case CrossCompartmentKey::DebuggerEnvironment: - case CrossCompartmentKey::DebuggerSource: - case CrossCompartmentKey::DebuggerWasmScript: - case CrossCompartmentKey::DebuggerWasmSource: - MOZ_ASSERT(IsInsideNursery(wrapped) || - wrapped->asTenured().getTraceKind() == JS::TraceKind::Object); - keyDying = IsAboutToBeFinalizedUnbarriered(reinterpret_cast(&wrapped)); - break; - case CrossCompartmentKey::StringWrapper: - MOZ_ASSERT(wrapped->asTenured().getTraceKind() == JS::TraceKind::String); - keyDying = IsAboutToBeFinalizedUnbarriered(reinterpret_cast(&wrapped)); - break; - case CrossCompartmentKey::DebuggerScript: - MOZ_ASSERT(wrapped->asTenured().getTraceKind() == JS::TraceKind::Script); - keyDying = IsAboutToBeFinalizedUnbarriered(reinterpret_cast(&wrapped)); - break; - default: - MOZ_CRASH("Unknown key kind"); - } - - bool dbgDying = debugger && IsAboutToBeFinalizedUnbarriered(&debugger); - MOZ_ASSERT_IF(keyDying || dbgDying, kind != CrossCompartmentKey::StringWrapper); - return keyDying || dbgDying; + return applyToWrapped(NeedsSweepUnbarrieredFunctor()) || + applyToDebugger(NeedsSweepUnbarrieredFunctor()); } void diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index f2183a3e7c..aff8c356be 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -10,6 +10,7 @@ #include "mozilla/LinkedList.h" #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" +#include "mozilla/Tuple.h" #include "mozilla/Variant.h" #include "mozilla/XorShift128PlusRNG.h" @@ -35,8 +36,10 @@ namespace wasm { class Module; } // namespace wasm -struct NativeIterator; class ClonedBlockObject; +class ScriptSourceObject; +class WasmModuleObject; +struct NativeIterator; /* * A single-entry cache for some base-10 double-to-string conversions. This @@ -71,75 +74,117 @@ class DtoaCache { struct CrossCompartmentKey { - enum Kind { - ObjectWrapper, - StringWrapper, - DebuggerScript, - DebuggerSource, - DebuggerObject, - DebuggerEnvironment, - DebuggerWasmScript, - DebuggerWasmSource + public: + enum DebuggerObjectKind : uint8_t { DebuggerSource, DebuggerEnvironment, DebuggerObject, + DebuggerWasmScript, DebuggerWasmSource }; + using DebuggerAndObject = mozilla::Tuple; + using DebuggerAndScript = mozilla::Tuple; + using WrappedType = mozilla::Variant< + JSObject*, + JSString*, + DebuggerAndScript, + DebuggerAndObject>; + + explicit CrossCompartmentKey(JSObject* obj) : wrapped(obj) { MOZ_RELEASE_ASSERT(obj); } + explicit CrossCompartmentKey(JSString* str) : wrapped(str) { MOZ_RELEASE_ASSERT(str); } + explicit CrossCompartmentKey(JS::Value v) + : wrapped(v.isString() ? WrappedType(v.toString()) : WrappedType(&v.toObject())) + {} + explicit CrossCompartmentKey(NativeObject* debugger, JSObject* obj, DebuggerObjectKind kind) + : wrapped(DebuggerAndObject(debugger, obj, kind)) + { + MOZ_RELEASE_ASSERT(debugger); + MOZ_RELEASE_ASSERT(obj); + } + explicit CrossCompartmentKey(NativeObject* debugger, JSScript* script) + : wrapped(DebuggerAndScript(debugger, script)) + { + MOZ_RELEASE_ASSERT(debugger); + MOZ_RELEASE_ASSERT(script); + } + + bool operator==(const CrossCompartmentKey& other) const { return wrapped == other.wrapped; } + bool operator!=(const CrossCompartmentKey& other) const { return wrapped != other.wrapped; } + + template bool is() const { return wrapped.is(); } + template const T& as() const { return wrapped.as(); } + + template + auto applyToWrapped(F f) -> typename F::ReturnType { + struct WrappedMatcher { + using ReturnType = typename F::ReturnType; + F f_; + explicit WrappedMatcher(F f) : f_(f) {} + ReturnType match(JSObject*& obj) { return f_(&obj); } + ReturnType match(JSString*& str) { return f_(&str); } + ReturnType match(DebuggerAndScript& tpl) { return f_(&mozilla::Get<1>(tpl)); } + ReturnType match(DebuggerAndObject& tpl) { return f_(&mozilla::Get<1>(tpl)); } + } matcher(f); + return wrapped.match(matcher); + } + + template + auto applyToDebugger(F f) -> typename F::ReturnType { + struct DebuggerMatcher { + using ReturnType = typename F::ReturnType; + F f_; + explicit DebuggerMatcher(F f) : f_(f) {} + ReturnType match(JSObject*& obj) { return ReturnType(); } + ReturnType match(JSString*& str) { return ReturnType(); } + ReturnType match(DebuggerAndScript& tpl) { return f_(&mozilla::Get<0>(tpl)); } + ReturnType match(DebuggerAndObject& tpl) { return f_(&mozilla::Get<0>(tpl)); } + } matcher(f); + return wrapped.match(matcher); + } + + // Valid for JSObject* and Debugger keys. Crashes immediately if used on a + // JSString* key. + JSCompartment* compartment() { + struct GetCompartmentFunctor { + using ReturnType = JSCompartment*; + ReturnType operator()(JSObject** tp) const { return (*tp)->compartment(); } + ReturnType operator()(JSScript** tp) const { return (*tp)->compartment(); } + ReturnType operator()(JSString** tp) const { + MOZ_CRASH("invalid ccw key"); return nullptr; + } + }; + return applyToWrapped(GetCompartmentFunctor()); + } + + struct Hasher : public DefaultHasher + { + struct HashFunctor { + using ReturnType = HashNumber; + ReturnType match(JSObject* obj) { return DefaultHasher::hash(obj); } + ReturnType match(JSString* str) { return DefaultHasher::hash(str); } + ReturnType match(const DebuggerAndScript& tpl) { + return DefaultHasher::hash(mozilla::Get<0>(tpl)) ^ + DefaultHasher::hash(mozilla::Get<1>(tpl)); + } + ReturnType match(const DebuggerAndObject& tpl) { + return DefaultHasher::hash(mozilla::Get<0>(tpl)) ^ + DefaultHasher::hash(mozilla::Get<1>(tpl)) ^ + (mozilla::Get<2>(tpl) << 5); + } + }; + static HashNumber hash(const CrossCompartmentKey& key) { + return key.wrapped.match(HashFunctor()); + } + + static bool match(const CrossCompartmentKey& l, const CrossCompartmentKey& k) { + return l.wrapped == k.wrapped; + } }; - - Kind kind; - JSObject* debugger; - js::gc::Cell* wrapped; - - explicit CrossCompartmentKey(JSObject* wrapped) - : kind(ObjectWrapper), debugger(nullptr), wrapped(wrapped) - { - MOZ_RELEASE_ASSERT(wrapped); - } - explicit CrossCompartmentKey(JSString* wrapped) - : kind(StringWrapper), debugger(nullptr), wrapped(wrapped) - { - MOZ_RELEASE_ASSERT(wrapped); - } - explicit CrossCompartmentKey(Value wrappedArg) - : kind(wrappedArg.isString() ? StringWrapper : ObjectWrapper), - debugger(nullptr), - wrapped((js::gc::Cell*)wrappedArg.toGCThing()) - { - MOZ_RELEASE_ASSERT(wrappedArg.isString() || wrappedArg.isObject()); - MOZ_RELEASE_ASSERT(wrapped); - } - explicit CrossCompartmentKey(const RootedValue& wrappedArg) - : kind(wrappedArg.get().isString() ? StringWrapper : ObjectWrapper), - debugger(nullptr), - wrapped((js::gc::Cell*)wrappedArg.get().toGCThing()) - { - MOZ_RELEASE_ASSERT(wrappedArg.isString() || wrappedArg.isObject()); - MOZ_RELEASE_ASSERT(wrapped); - } - CrossCompartmentKey(Kind kind, JSObject* dbg, js::gc::Cell* wrapped) - : kind(kind), debugger(dbg), wrapped(wrapped) - { - MOZ_RELEASE_ASSERT(dbg); - MOZ_RELEASE_ASSERT(wrapped); - } - + void trace(JSTracer* trc); bool needsSweep(); private: CrossCompartmentKey() = delete; -}; - -struct WrapperHasher : public DefaultHasher -{ - static HashNumber hash(const CrossCompartmentKey& key) { - static_assert(sizeof(HashNumber) == sizeof(uint32_t), - "subsequent code assumes a four-byte hash"); - return uint32_t(uintptr_t(key.wrapped)) | uint32_t(key.kind); - } - - static bool match(const CrossCompartmentKey& l, const CrossCompartmentKey& k) { - return l.kind == k.kind && l.debugger == k.debugger && l.wrapped == k.wrapped; - } + WrappedType wrapped; }; using WrapperMap = GCRekeyableHashMap; + CrossCompartmentKey::Hasher, SystemAllocPolicy>; // We must ensure that all newly allocated JSObjects get their metadata // set. However, metadata builders may require the new object be in a sane diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index 4af361c57c..c6255e02cb 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -295,6 +295,10 @@ js::GetBuiltinClass(JSContext* cx, HandleObject obj, ESClassValue* classValue) *classValue = ESClass_Map; else if (obj->is()) *classValue = ESClass_Promise; + else if (obj->is()) + *classValue = ESClass_MapIterator; + else if (obj->is()) + *classValue = ESClass_SetIterator; else *classValue = ESClass_Other; @@ -593,15 +597,29 @@ js::ZoneGlobalsAreAllGray(JS::Zone* zone) return true; } +namespace { +struct VisitGrayCallbackFunctor { + GCThingCallback callback_; + void* closure_; + VisitGrayCallbackFunctor(GCThingCallback callback, void* closure) + : callback_(callback), closure_(closure) + {} + + using ReturnType = void; + template + ReturnType operator()(T tp) const { + if ((*tp)->isTenured() && (*tp)->asTenured().isMarked(gc::GRAY)) + callback_(closure_, JS::GCCellPtr(*tp)); + } +}; +} // namespace (anonymous) + JS_FRIEND_API(void) js::VisitGrayWrapperTargets(Zone* zone, GCThingCallback callback, void* closure) { for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) { - for (JSCompartment::WrapperEnum e(comp); !e.empty(); e.popFront()) { - gc::Cell* thing = e.front().key().wrapped; - if (thing->isTenured() && thing->asTenured().isMarked(gc::GRAY)) - callback(closure, JS::GCCellPtr(thing, thing->asTenured().getTraceKind())); - } + for (JSCompartment::WrapperEnum e(comp); !e.empty(); e.popFront()) + e.front().mutableKey().applyToWrapped(VisitGrayCallbackFunctor(callback, closure)); } } diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 004d8ff021..cef0700749 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -239,6 +239,7 @@ using namespace js; using namespace js::gc; using mozilla::ArrayLength; +using mozilla::Get; using mozilla::Maybe; using mozilla::Swap; @@ -261,46 +262,22 @@ const AllocKind gc::slotsToThingKind[] = { static_assert(JS_ARRAY_LENGTH(slotsToThingKind) == SLOTS_TO_THING_KIND_LIMIT, "We have defined a slot count for each kind."); -// Assert that SortedArenaList::MinThingSize and sizeof(FreeSpan) are <= the -// real minimum thing size. Also assert each size is a multiple of CellSize. -#define CHECK_THING_SIZE_INNER(x_) \ - static_assert(x_ >= SortedArenaList::MinThingSize, \ - #x_ " is less than SortedArenaList::MinThingSize!"); \ - static_assert(x_ >= sizeof(FreeSpan), \ - #x_ " is less than sizeof(FreeSpan)"); \ - static_assert(x_ % CellSize == 0, \ - #x_ " not a multiple of CellSize"); +#define CHECK_THING_SIZE(allocKind, traceKind, type, sizedType) \ + static_assert(sizeof(sizedType) >= SortedArenaList::MinThingSize, \ + #sizedType " is smaller than SortedArenaList::MinThingSize!"); \ + static_assert(sizeof(sizedType) >= sizeof(FreeSpan), \ + #sizedType " is smaller than FreeSpan"); \ + static_assert(sizeof(sizedType) % CellSize == 0, \ + "Size of " #sizedType " is not a multiple of CellSize"); +FOR_EACH_ALLOCKIND(CHECK_THING_SIZE); +#undef CHECK_THING_SIZE -#define CHECK_THING_SIZE(...) { __VA_ARGS__ }; /* Define the array. */ \ - MOZ_FOR_EACH(CHECK_THING_SIZE_INNER, (), (__VA_ARGS__ 0x20)) - -const uint32_t Arena::ThingSizes[] = CHECK_THING_SIZE( - sizeof(JSFunction), /* AllocKind::FUNCTION */ - sizeof(FunctionExtended), /* AllocKind::FUNCTION_EXTENDED */ - sizeof(JSObject_Slots0), /* AllocKind::OBJECT0 */ - sizeof(JSObject_Slots0), /* AllocKind::OBJECT0_BACKGROUND */ - sizeof(JSObject_Slots2), /* AllocKind::OBJECT2 */ - sizeof(JSObject_Slots2), /* AllocKind::OBJECT2_BACKGROUND */ - sizeof(JSObject_Slots4), /* AllocKind::OBJECT4 */ - sizeof(JSObject_Slots4), /* AllocKind::OBJECT4_BACKGROUND */ - sizeof(JSObject_Slots8), /* AllocKind::OBJECT8 */ - sizeof(JSObject_Slots8), /* AllocKind::OBJECT8_BACKGROUND */ - sizeof(JSObject_Slots12), /* AllocKind::OBJECT12 */ - sizeof(JSObject_Slots12), /* AllocKind::OBJECT12_BACKGROUND */ - sizeof(JSObject_Slots16), /* AllocKind::OBJECT16 */ - sizeof(JSObject_Slots16), /* AllocKind::OBJECT16_BACKGROUND */ - sizeof(JSScript), /* AllocKind::SCRIPT */ - sizeof(LazyScript), /* AllocKind::LAZY_SCRIPT */ - sizeof(Shape), /* AllocKind::SHAPE */ - sizeof(AccessorShape), /* AllocKind::ACCESSOR_SHAPE */ - sizeof(BaseShape), /* AllocKind::BASE_SHAPE */ - sizeof(ObjectGroup), /* AllocKind::OBJECT_GROUP */ - sizeof(JSFatInlineString), /* AllocKind::FAT_INLINE_STRING */ - sizeof(JSString), /* AllocKind::STRING */ - sizeof(JSExternalString), /* AllocKind::EXTERNAL_STRING */ - sizeof(JS::Symbol), /* AllocKind::SYMBOL */ - sizeof(jit::JitCode), /* AllocKind::JITCODE */ -); +const uint32_t Arena::ThingSizes[] = { +#define EXPAND_THING_SIZE(allocKind, traceKind, type, sizedType) \ + sizeof(sizedType), +FOR_EACH_ALLOCKIND(EXPAND_THING_SIZE) +#undef EXPAND_THING_SIZE + }; FreeSpan ArenaLists::placeholder; @@ -310,31 +287,10 @@ FreeSpan ArenaLists::placeholder; #define OFFSET(type) uint32_t(ArenaHeaderSize + (ArenaSize - ArenaHeaderSize) % sizeof(type)) const uint32_t Arena::FirstThingOffsets[] = { - OFFSET(JSFunction), /* AllocKind::FUNCTION */ - OFFSET(FunctionExtended), /* AllocKind::FUNCTION_EXTENDED */ - OFFSET(JSObject_Slots0), /* AllocKind::OBJECT0 */ - OFFSET(JSObject_Slots0), /* AllocKind::OBJECT0_BACKGROUND */ - OFFSET(JSObject_Slots2), /* AllocKind::OBJECT2 */ - OFFSET(JSObject_Slots2), /* AllocKind::OBJECT2_BACKGROUND */ - OFFSET(JSObject_Slots4), /* AllocKind::OBJECT4 */ - OFFSET(JSObject_Slots4), /* AllocKind::OBJECT4_BACKGROUND */ - OFFSET(JSObject_Slots8), /* AllocKind::OBJECT8 */ - OFFSET(JSObject_Slots8), /* AllocKind::OBJECT8_BACKGROUND */ - OFFSET(JSObject_Slots12), /* AllocKind::OBJECT12 */ - OFFSET(JSObject_Slots12), /* AllocKind::OBJECT12_BACKGROUND */ - OFFSET(JSObject_Slots16), /* AllocKind::OBJECT16 */ - OFFSET(JSObject_Slots16), /* AllocKind::OBJECT16_BACKGROUND */ - OFFSET(JSScript), /* AllocKind::SCRIPT */ - OFFSET(LazyScript), /* AllocKind::LAZY_SCRIPT */ - OFFSET(Shape), /* AllocKind::SHAPE */ - OFFSET(AccessorShape), /* AllocKind::ACCESSOR_SHAPE */ - OFFSET(BaseShape), /* AllocKind::BASE_SHAPE */ - OFFSET(ObjectGroup), /* AllocKind::OBJECT_GROUP */ - OFFSET(JSFatInlineString), /* AllocKind::FAT_INLINE_STRING */ - OFFSET(JSString), /* AllocKind::STRING */ - OFFSET(JSExternalString), /* AllocKind::EXTERNAL_STRING */ - OFFSET(JS::Symbol), /* AllocKind::SYMBOL */ - OFFSET(jit::JitCode), /* AllocKind::JITCODE */ +#define EXPAND_FIRST_THING_OFFSET(allocKind, traceKind, type, sizedType) \ + OFFSET(sizedType), +FOR_EACH_ALLOCKIND(EXPAND_FIRST_THING_OFFSET) +#undef EXPAND_FIRST_THING_OFFSET }; #undef OFFSET @@ -342,31 +298,10 @@ const uint32_t Arena::FirstThingOffsets[] = { #define COUNT(type) uint32_t((ArenaSize - ArenaHeaderSize) / sizeof(type)) const uint32_t Arena::ThingsPerArena[] = { - COUNT(JSFunction), /* AllocKind::FUNCTION */ - COUNT(FunctionExtended), /* AllocKind::FUNCTION_EXTENDED */ - COUNT(JSObject_Slots0), /* AllocKind::OBJECT0 */ - COUNT(JSObject_Slots0), /* AllocKind::OBJECT0_BACKGROUND */ - COUNT(JSObject_Slots2), /* AllocKind::OBJECT2 */ - COUNT(JSObject_Slots2), /* AllocKind::OBJECT2_BACKGROUND */ - COUNT(JSObject_Slots4), /* AllocKind::OBJECT4 */ - COUNT(JSObject_Slots4), /* AllocKind::OBJECT4_BACKGROUND */ - COUNT(JSObject_Slots8), /* AllocKind::OBJECT8 */ - COUNT(JSObject_Slots8), /* AllocKind::OBJECT8_BACKGROUND */ - COUNT(JSObject_Slots12), /* AllocKind::OBJECT12 */ - COUNT(JSObject_Slots12), /* AllocKind::OBJECT12_BACKGROUND */ - COUNT(JSObject_Slots16), /* AllocKind::OBJECT16 */ - COUNT(JSObject_Slots16), /* AllocKind::OBJECT16_BACKGROUND */ - COUNT(JSScript), /* AllocKind::SCRIPT */ - COUNT(LazyScript), /* AllocKind::LAZY_SCRIPT */ - COUNT(Shape), /* AllocKind::SHAPE */ - COUNT(AccessorShape), /* AllocKind::ACCESSOR_SHAPE */ - COUNT(BaseShape), /* AllocKind::BASE_SHAPE */ - COUNT(ObjectGroup), /* AllocKind::OBJECT_GROUP */ - COUNT(JSFatInlineString), /* AllocKind::FAT_INLINE_STRING */ - COUNT(JSString), /* AllocKind::STRING */ - COUNT(JSExternalString), /* AllocKind::EXTERNAL_STRING */ - COUNT(JS::Symbol), /* AllocKind::SYMBOL */ - COUNT(jit::JitCode), /* AllocKind::JITCODE */ +#define EXPAND_THINGS_PER_ARENA(allocKind, traceKind, type, sizedType) \ + COUNT(sizedType), +FOR_EACH_ALLOCKIND(EXPAND_THINGS_PER_ARENA) +#undef EXPAND_THINGS_PER_ARENA }; #undef COUNT @@ -598,43 +533,12 @@ FinalizeArenas(FreeOp* fop, ArenaLists::KeepArenasEnum keepArenas) { switch (thingKind) { - case AllocKind::FUNCTION: - case AllocKind::FUNCTION_EXTENDED: - case AllocKind::OBJECT0: - case AllocKind::OBJECT0_BACKGROUND: - case AllocKind::OBJECT2: - case AllocKind::OBJECT2_BACKGROUND: - case AllocKind::OBJECT4: - case AllocKind::OBJECT4_BACKGROUND: - case AllocKind::OBJECT8: - case AllocKind::OBJECT8_BACKGROUND: - case AllocKind::OBJECT12: - case AllocKind::OBJECT12_BACKGROUND: - case AllocKind::OBJECT16: - case AllocKind::OBJECT16_BACKGROUND: - return FinalizeTypedArenas(fop, src, dest, thingKind, budget, keepArenas); - case AllocKind::SCRIPT: - return FinalizeTypedArenas(fop, src, dest, thingKind, budget, keepArenas); - case AllocKind::LAZY_SCRIPT: - return FinalizeTypedArenas(fop, src, dest, thingKind, budget, keepArenas); - case AllocKind::SHAPE: - return FinalizeTypedArenas(fop, src, dest, thingKind, budget, keepArenas); - case AllocKind::ACCESSOR_SHAPE: - return FinalizeTypedArenas(fop, src, dest, thingKind, budget, keepArenas); - case AllocKind::BASE_SHAPE: - return FinalizeTypedArenas(fop, src, dest, thingKind, budget, keepArenas); - case AllocKind::OBJECT_GROUP: - return FinalizeTypedArenas(fop, src, dest, thingKind, budget, keepArenas); - case AllocKind::STRING: - return FinalizeTypedArenas(fop, src, dest, thingKind, budget, keepArenas); - case AllocKind::FAT_INLINE_STRING: - return FinalizeTypedArenas(fop, src, dest, thingKind, budget, keepArenas); - case AllocKind::EXTERNAL_STRING: - return FinalizeTypedArenas(fop, src, dest, thingKind, budget, keepArenas); - case AllocKind::SYMBOL: - return FinalizeTypedArenas(fop, src, dest, thingKind, budget, keepArenas); - case AllocKind::JITCODE: - return FinalizeTypedArenas(fop, src, dest, thingKind, budget, keepArenas); +#define EXPAND_CASE(allocKind, traceKind, type, sizedType) \ + case AllocKind::allocKind: \ + return FinalizeTypedArenas(fop, src, dest, thingKind, budget, keepArenas); +FOR_EACH_ALLOCKIND(EXPAND_CASE) +#undef EXPAND_CASE + default: MOZ_CRASH("Invalid alloc kind"); } @@ -1190,25 +1094,26 @@ GCRuntime::getZealBits(uint32_t* zealBits, uint32_t* frequency, uint32_t* schedu const char* gc::ZealModeHelpText = " Specifies how zealous the garbage collector should be. Some of these modes can\n" - " be set simultaneously, e.g. in the shell, gczeal(2); gczeal(4); will activate\n" - " both modes 2 and 4.\n" + " be set simultaneously, by passing multiple level options, e.g. \"2;4\" will activate\n" + " both modes 2 and 4. Modes can be specified by name or number.\n" " \n" " Values:\n" - " 0: Normal amount of collection (resets all modes)\n" - " 1: Collect when roots are added or removed\n" - " 2: Collect when every N allocations (default: 100)\n" - " 3: Collect when the window paints (browser only)\n" - " 4: Verify pre write barriers between instructions\n" - " 5: Verify pre write barriers between paints\n" - " 6: Verify stack rooting\n" - " 7: Collect the nursery every N nursery allocations\n" - " 8: Incremental GC in two slices: 1) mark roots 2) finish collection\n" - " 9: Incremental GC in two slices: 1) mark all 2) new marking and finish\n" - " 10: Incremental GC in multiple slices\n" - " 11: Verify incremental marking\n" - " 12: Always use the individual element post-write barrier, regardless of elements size\n" - " 13: Check internal hashtables on minor GC\n" - " 14: Perform a shrinking collection every N allocations\n"; + " 0: (None) Normal amount of collection (resets all modes)\n" + " 1: (Poke) Collect when roots are added or removed\n" + " 2: (Alloc) Collect when every N allocations (default: 100)\n" + " 3: (FrameGC) Collect when the window paints (browser only)\n" + " 4: (VerifierPre) Verify pre write barriers between instructions\n" + " 5: (FrameVerifierPre) Verify pre write barriers between paints\n" + " 6: (StackRooting) Verify stack rooting\n" + " 7: (GenerationalGC) Collect the nursery every N nursery allocations\n" + " 8: (IncrementalRootsThenFinish) Incremental GC in two slices: 1) mark roots 2) finish collection\n" + " 9: (IncrementalMarkAllThenFinish) Incremental GC in two slices: 1) mark all 2) new marking and finish\n" + " 10: (IncrementalMultipleSlices) Incremental GC in multiple slices\n" + " 11: (IncrementalMarkingValidator) Verify incremental marking\n" + " 12: (ElementsBarrier) Always use the individual element post-write barrier, regardless of elements size\n" + " 13: (CheckHashTablesOnMinorGC) Check internal hashtables on minor GC\n" + " 14: (Compact) Perform a shrinking collection every N allocations\n" + " 15: (CheckHeapOnMovingGC) Walk the heap to check all pointers have been updated\n"; void GCRuntime::setZeal(uint8_t zeal, uint32_t frequency) @@ -1255,26 +1160,63 @@ GCRuntime::setNextScheduled(uint32_t count) bool GCRuntime::parseAndSetZeal(const char* str) { - int zeal = -1; int frequency = -1; + bool foundFrequency = false; + mozilla::Vector zeals; - if (isdigit(str[0])) { - zeal = atoi(str); + static const struct { + const char* const zealMode; + size_t length; + uint32_t zeal; + } zealModes[] = { +#define ZEAL_MODE(name, value) {#name, sizeof(#name) - 1, value}, + JS_FOR_EACH_ZEAL_MODE(ZEAL_MODE) +#undef ZEAL_MODE + {"None", 4, 0} + }; - const char* p = strchr(str, ','); - if (!p) - frequency = JS_DEFAULT_ZEAL_FREQ; - else - frequency = atoi(p + 1); - } + do { + int zeal = -1; - if (zeal < 0 || zeal > int(ZealMode::Limit) || frequency <= 0) { - fprintf(stderr, "Format: JS_GC_ZEAL=level[,N]\n"); - fputs(ZealModeHelpText, stderr); - return false; - } + const char* p = nullptr; + if (isdigit(str[0])) { + zeal = atoi(str); - setZeal(zeal, frequency); + size_t offset = strspn(str, "0123456789"); + p = str + offset; + } else { + for (auto z : zealModes) { + if (!strncmp(str, z.zealMode, z.length)) { + zeal = z.zeal; + p = str + z.length; + break; + } + } + } + if (p) { + if (!*p || *p == ';') { + frequency = JS_DEFAULT_ZEAL_FREQ; + } else if (*p == ',') { + frequency = atoi(p + 1); + foundFrequency = true; + } + } + + if (zeal < 0 || zeal > int(ZealMode::Limit) || frequency <= 0) { + fprintf(stderr, "Format: JS_GC_ZEAL=level(;level)*[,N]\n"); + fputs(ZealModeHelpText, stderr); + return false; + } + + if (!zeals.emplaceBack(zeal)) { + return false; + } + } while (!foundFrequency && + (str = strchr(str, ';')) != nullptr && + str++); + + for (auto z : zeals) + setZeal(z, frequency); return true; } @@ -2500,49 +2442,15 @@ static void UpdateArenaPointers(MovingTracer* trc, Arena* arena) { AllocKind kind = arena->getAllocKind(); - JS::TraceKind traceKind = MapAllocToTraceKind(kind); switch (kind) { - case AllocKind::FUNCTION: - case AllocKind::FUNCTION_EXTENDED: - case AllocKind::OBJECT0: - case AllocKind::OBJECT0_BACKGROUND: - case AllocKind::OBJECT2: - case AllocKind::OBJECT2_BACKGROUND: - case AllocKind::OBJECT4: - case AllocKind::OBJECT4_BACKGROUND: - case AllocKind::OBJECT8: - case AllocKind::OBJECT8_BACKGROUND: - case AllocKind::OBJECT12: - case AllocKind::OBJECT12_BACKGROUND: - case AllocKind::OBJECT16: - case AllocKind::OBJECT16_BACKGROUND: - UpdateArenaPointersTyped(trc, arena, traceKind); - return; - case AllocKind::SCRIPT: - UpdateArenaPointersTyped(trc, arena, traceKind); - return; - case AllocKind::LAZY_SCRIPT: - UpdateArenaPointersTyped(trc, arena, traceKind); - return; - case AllocKind::SHAPE: - UpdateArenaPointersTyped(trc, arena, traceKind); - return; - case AllocKind::ACCESSOR_SHAPE: - UpdateArenaPointersTyped(trc, arena, traceKind); - return; - case AllocKind::BASE_SHAPE: - UpdateArenaPointersTyped(trc, arena, traceKind); - return; - case AllocKind::OBJECT_GROUP: - UpdateArenaPointersTyped(trc, arena, traceKind); - return; - case AllocKind::STRING: - UpdateArenaPointersTyped(trc, arena, traceKind); - return; - case AllocKind::JITCODE: - UpdateArenaPointersTyped(trc, arena, traceKind); +#define EXPAND_CASE(allocKind, traceKind, type, sizedType) \ + case AllocKind::allocKind: \ + UpdateArenaPointersTyped(trc, arena, JS::TraceKind::traceKind); \ return; +FOR_EACH_ALLOCKIND(EXPAND_CASE) +#undef EXPAND_CASE + default: MOZ_CRASH("Invalid alloc kind for UpdateArenaPointers"); } @@ -3340,7 +3248,7 @@ GCRuntime::triggerZoneGC(Zone* zone, JS::gcreason::Reason reason) #ifdef JS_GC_ZEAL if (hasZealMode(ZealMode::Alloc)) { - triggerGC(reason); + MOZ_RELEASE_ASSERT(triggerGC(reason)); return true; } #endif @@ -3353,7 +3261,7 @@ GCRuntime::triggerZoneGC(Zone* zone, JS::gcreason::Reason reason) fullGCForAtomsRequested_ = true; return false; } - triggerGC(reason); + MOZ_RELEASE_ASSERT(triggerGC(reason)); return true; } @@ -3889,8 +3797,15 @@ GCRuntime::sweepZones(FreeOp* fop, bool destroyingRuntime) !zone->hasMarkedCompartments(); if (zoneIsDead || destroyingRuntime) { + // We have just finished sweeping, so we should have freed any + // empty arenas back to their Chunk for future allocation. zone->arenas.checkEmptyFreeLists(); + // We are about to delete the Zone; this will leave the Zone* + // in the arena header dangling if there are any arenas + // remaining at this point. + zone->arenas.checkEmptyArenaLists(); + if (callback) callback(zone); @@ -3906,6 +3821,39 @@ GCRuntime::sweepZones(FreeOp* fop, bool destroyingRuntime) zones.shrinkTo(write - zones.begin()); } +#ifdef DEBUG +static const char* +AllocKindToAscii(AllocKind kind) +{ + switch(kind) { +#define MAKE_CASE(allocKind, traceKind, type, sizedType) \ + case AllocKind:: allocKind: return #allocKind; +FOR_EACH_ALLOCKIND(MAKE_CASE) +#undef MAKE_CASE + + default: + MOZ_CRASH("Unknown AllocKind in AllocKindToAscii"); + } +} +#endif // DEBUG + +void +ArenaLists::checkEmptyArenaList(AllocKind kind) +{ +#ifdef DEBUG + if (!arenaLists[kind].isEmpty()) { + for (Arena* current = arenaLists[kind].head(); current; current = current->next) { + for (ArenaCellIterUnderFinalize i(current); !i.done(); i.next()) { + Cell* t = i.get(); + MOZ_ASSERT(t->asTenured().isMarked(), "unmarked cells should have been finalized"); + fprintf(stderr, "ERROR: GC found live Cell %p of kind %s at shutdown\n", + t, AllocKindToAscii(kind)); + } + } + } +#endif // DEBUG +} + void GCRuntime::purgeRuntime() { @@ -3961,13 +3909,23 @@ class CompartmentCheckTracer : public JS::CallbackTracer JSCompartment* compartment; }; +namespace { +struct IsDestComparatorFunctor { + JS::GCCellPtr dst_; + IsDestComparatorFunctor(JS::GCCellPtr dst) : dst_(dst) {} + + using ReturnType = bool; + template bool operator()(T* t) { return (*t) == dst_.asCell(); } +}; +} // namespace (anonymous) + static bool -InCrossCompartmentMap(JSObject* src, Cell* dst, JS::TraceKind dstKind) +InCrossCompartmentMap(JSObject* src, JS::GCCellPtr dst) { JSCompartment* srccomp = src->compartment(); - if (dstKind == JS::TraceKind::Object) { - Value key = ObjectValue(*static_cast(dst)); + if (dst.is()) { + Value key = ObjectValue(dst.as()); if (WrapperMap::Ptr p = srccomp->lookupWrapper(key)) { if (*p->value().unsafeGet() == ObjectValue(*src)) return true; @@ -3979,8 +3937,11 @@ InCrossCompartmentMap(JSObject* src, Cell* dst, JS::TraceKind dstKind) * know the right hashtable key, so we have to iterate. */ for (JSCompartment::WrapperEnum e(srccomp); !e.empty(); e.popFront()) { - if (e.front().key().wrapped == dst && ToMarkable(e.front().value()) == src) + if (e.front().mutableKey().applyToWrapped(IsDestComparatorFunctor(dst)) && + ToMarkable(e.front().value()) == src) + { return true; + } } return false; @@ -3993,14 +3954,13 @@ struct MaybeCompartmentFunctor { void CompartmentCheckTracer::onChild(const JS::GCCellPtr& thing) { - TenuredCell* tenured = TenuredCell::fromPointer(thing.asCell()); - JSCompartment* comp = DispatchTyped(MaybeCompartmentFunctor(), thing); if (comp && compartment) { MOZ_ASSERT(comp == compartment || runtime()->isAtomsCompartment(comp) || (srcKind == JS::TraceKind::Object && - InCrossCompartmentMap(static_cast(src), tenured, thing.kind()))); + InCrossCompartmentMap(static_cast(src), thing))); } else { + TenuredCell* tenured = TenuredCell::fromPointer(thing.asCell()); MOZ_ASSERT(tenured->zone() == zone || tenured->zone()->isAtomsZone()); } } @@ -4235,24 +4195,9 @@ GCRuntime::markCompartments() /* Set the maybeAlive flag based on cross-compartment edges. */ for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { - const CrossCompartmentKey& key = e.front().key(); - JSCompartment* dest; - switch (key.kind) { - case CrossCompartmentKey::ObjectWrapper: - case CrossCompartmentKey::DebuggerObject: - case CrossCompartmentKey::DebuggerSource: - case CrossCompartmentKey::DebuggerEnvironment: - case CrossCompartmentKey::DebuggerWasmScript: - case CrossCompartmentKey::DebuggerWasmSource: - dest = static_cast(key.wrapped)->compartment(); - break; - case CrossCompartmentKey::DebuggerScript: - dest = static_cast(key.wrapped)->compartment(); - break; - default: - dest = nullptr; - break; - } + if (e.front().key().is()) + continue; + JSCompartment* dest = e.front().mutableKey().compartment(); if (dest) dest->maybeAlive = true; } @@ -4263,6 +4208,7 @@ GCRuntime::markCompartments() * during MarkRuntime. */ + /* Propogate maybeAlive to scheduleForDestruction. */ for (GCCompartmentsIter c(rt); !c.done(); c.next()) { if (!c->maybeAlive && !rt->isAtomsCompartment(c)) c->scheduledForDestruction = true; @@ -4280,8 +4226,8 @@ GCRuntime::markWeakReferences(gcstats::Phase phase) marker.enterWeakMarkingMode(); // TODO bug 1167452: Make weak marking incremental - SliceBudget budget = SliceBudget::unlimited(); - marker.drainMarkStack(budget); + auto unlimited = SliceBudget::unlimited(); + MOZ_RELEASE_ASSERT(marker.drainMarkStack(unlimited)); for (;;) { bool markedAny = false; @@ -4300,7 +4246,7 @@ GCRuntime::markWeakReferences(gcstats::Phase phase) break; auto unlimited = SliceBudget::unlimited(); - marker.drainMarkStack(unlimited); + MOZ_RELEASE_ASSERT(marker.drainMarkStack(unlimited)); } MOZ_ASSERT(marker.isDrained()); @@ -4327,7 +4273,7 @@ GCRuntime::markGrayReferences(gcstats::Phase phase) (*op)(&marker, grayRootTracer.data); } auto unlimited = SliceBudget::unlimited(); - marker.drainMarkStack(unlimited); + MOZ_RELEASE_ASSERT(marker.drainMarkStack(unlimited)); } void @@ -4488,9 +4434,9 @@ js::gc::MarkingValidator::nonIncrementalMark() gc->markRuntime(gcmarker, GCRuntime::MarkRuntime); - auto unlimited = SliceBudget::unlimited(); gc->incrementalState = MARK; - gc->marker.drainMarkStack(unlimited); + auto unlimited = SliceBudget::unlimited(); + MOZ_RELEASE_ASSERT(gc->marker.drainMarkStack(unlimited)); } gc->incrementalState = SWEEP; @@ -4644,7 +4590,7 @@ DropStringWrappers(JSRuntime* rt) */ for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { - if (e.front().key().kind == CrossCompartmentKey::StringWrapper) + if (e.front().key().is()) e.removeFront(); } } @@ -4664,41 +4610,46 @@ DropStringWrappers(JSRuntime* rt) * * Tarjan's algorithm is used to calculate the components. */ +namespace { +struct AddOutgoingEdgeFunctor { + bool needsEdge_; + ComponentFinder& finder_; + + AddOutgoingEdgeFunctor(bool needsEdge, ComponentFinder& finder) + : needsEdge_(needsEdge), finder_(finder) + {} + + using ReturnType = void; + template + ReturnType operator()(T tp) { + TenuredCell& other = (*tp)->asTenured(); + + /* + * Add edge to wrapped object compartment if wrapped object is not + * marked black to indicate that wrapper compartment not be swept + * after wrapped compartment. + */ + if (needsEdge_) { + JS::Zone* zone = other.zone(); + if (zone->isGCMarking()) + finder_.addEdgeTo(zone); + } + } +}; +} // namespace (anonymous) void JSCompartment::findOutgoingEdges(ComponentFinder& finder) { for (js::WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) { - CrossCompartmentKey::Kind kind = e.front().key().kind; - MOZ_ASSERT(kind != CrossCompartmentKey::StringWrapper); - TenuredCell& other = e.front().key().wrapped->asTenured(); - if (kind == CrossCompartmentKey::ObjectWrapper) { - /* - * Add edge to wrapped object compartment if wrapped object is not - * marked black to indicate that wrapper compartment not be swept - * after wrapped compartment. - */ - if (!other.isMarked(BLACK) || other.isMarked(GRAY)) { - JS::Zone* w = other.zone(); - if (w->isGCMarking()) - finder.addEdgeTo(w); - } - } else { - MOZ_ASSERT(kind == CrossCompartmentKey::DebuggerScript || - kind == CrossCompartmentKey::DebuggerSource || - kind == CrossCompartmentKey::DebuggerObject || - kind == CrossCompartmentKey::DebuggerEnvironment || - kind == CrossCompartmentKey::DebuggerWasmScript || - kind == CrossCompartmentKey::DebuggerWasmSource); - /* - * Add edge for debugger object wrappers, to ensure (in conjuction - * with call to Debugger::findCompartmentEdges below) that debugger - * and debuggee objects are always swept in the same group. - */ - JS::Zone* w = other.zone(); - if (w->isGCMarking()) - finder.addEdgeTo(w); + CrossCompartmentKey& key = e.front().mutableKey(); + MOZ_ASSERT(!key.is()); + bool needsEdge = true; + if (key.is()) { + TenuredCell& other = key.as()->asTenured(); + needsEdge = !other.isMarked(BLACK) || other.isMarked(GRAY); } + key.applyToWrapped(AddOutgoingEdgeFunctor(needsEdge, finder)); } } @@ -4859,6 +4810,20 @@ AssertNotOnGrayList(JSObject* obj) } #endif +static void +AssertNoWrappersInGrayList(JSRuntime* rt) +{ +#ifdef DEBUG + for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { + MOZ_ASSERT(!c->gcIncomingGrayPointers); + for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { + if (!e.front().key().is()) + AssertNotOnGrayList(&e.front().value().unbarrieredGet().toObject()); + } + } +#endif +} + static JSObject* CrossCompartmentPointerReferent(JSObject* obj) { @@ -4953,7 +4918,7 @@ MarkIncomingCrossCompartmentPointers(JSRuntime* rt, const uint32_t color) } auto unlimited = SliceBudget::unlimited(); - rt->gc.marker.drainMarkStack(unlimited); + MOZ_RELEASE_ASSERT(rt->gc.marker.drainMarkStack(unlimited)); } static bool @@ -5464,16 +5429,7 @@ GCRuntime::beginSweepPhase(bool destroyingRuntime) releaseObservedTypes = shouldReleaseObservedTypes(); -#ifdef DEBUG - for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { - MOZ_ASSERT(!c->gcIncomingGrayPointers); - for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { - if (e.front().key().kind != CrossCompartmentKey::StringWrapper) - AssertNotOnGrayList(&e.front().value().unbarrieredGet().toObject()); - } - } -#endif - + AssertNoWrappersInGrayList(rt); DropStringWrappers(rt); findZoneGroups(); @@ -5753,16 +5709,9 @@ GCRuntime::endSweepPhase(bool destroyingRuntime) !zone->arenas.arenaListsToSweep[i]); } } - - for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { - MOZ_ASSERT(!c->gcIncomingGrayPointers); - - for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { - if (e.front().key().kind != CrossCompartmentKey::StringWrapper) - AssertNotOnGrayList(&e.front().value().unbarrieredGet().toObject()); - } - } #endif + + AssertNoWrappersInGrayList(rt); } void @@ -5816,6 +5765,10 @@ GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget) #ifdef DEBUG CheckHashTablesAfterMovingGC(rt); #endif +#ifdef JS_GC_ZEAL + if (rt->hasZealMode(ZealMode::CheckHeapOnMovingGC)) + CheckHeapAfterMovingGC(rt); +#endif return zonesToMaybeCompact.isEmpty() ? Finished : NotFinished; } @@ -6782,11 +6735,18 @@ GCRuntime::onOutOfMallocMemory(const AutoLockGC& lock) void GCRuntime::minorGCImpl(JS::gcreason::Reason reason, Nursery::ObjectGroupList* pretenureGroups) { + if (rt->mainThread.suppressGC) + return; + minorGCTriggerReason = JS::gcreason::NO_REASON; TraceLoggerThread* logger = TraceLoggerForMainThread(rt); AutoTraceLog logMinorGC(logger, TraceLogger_MinorGC); nursery.collect(rt, reason, pretenureGroups); - MOZ_ASSERT_IF(!rt->mainThread.suppressGC, nursery.isEmpty()); + MOZ_ASSERT(nursery.isEmpty()); + + AutoLockGC lock(rt); + for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) + maybeAllocTriggerZoneGC(zone, lock); } // Alternate to the runtime-taking form that allows marking object groups as diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 49d84d3a51..409acd22dd 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -53,19 +53,19 @@ enum State { NUM_STATES }; -/* Map from C++ type to alloc kind. JSObject does not have a 1:1 mapping, so must use Arena::thingSize. */ +/* + * Map from C++ type to alloc kind for non-object types. JSObject does not have + * a 1:1 mapping, so must use Arena::thingSize. + * + * The AllocKind is available as MapTypeToFinalizeKind::kind. + */ template struct MapTypeToFinalizeKind {}; -template <> struct MapTypeToFinalizeKind { static const AllocKind kind = AllocKind::SCRIPT; }; -template <> struct MapTypeToFinalizeKind { static const AllocKind kind = AllocKind::LAZY_SCRIPT; }; -template <> struct MapTypeToFinalizeKind { static const AllocKind kind = AllocKind::SHAPE; }; -template <> struct MapTypeToFinalizeKind { static const AllocKind kind = AllocKind::ACCESSOR_SHAPE; }; -template <> struct MapTypeToFinalizeKind { static const AllocKind kind = AllocKind::BASE_SHAPE; }; -template <> struct MapTypeToFinalizeKind { static const AllocKind kind = AllocKind::OBJECT_GROUP; }; -template <> struct MapTypeToFinalizeKind { static const AllocKind kind = AllocKind::FAT_INLINE_STRING; }; -template <> struct MapTypeToFinalizeKind { static const AllocKind kind = AllocKind::STRING; }; -template <> struct MapTypeToFinalizeKind { static const AllocKind kind = AllocKind::EXTERNAL_STRING; }; -template <> struct MapTypeToFinalizeKind { static const AllocKind kind = AllocKind::SYMBOL; }; -template <> struct MapTypeToFinalizeKind { static const AllocKind kind = AllocKind::JITCODE; }; +#define EXPAND_MAPTYPETOFINALIZEKIND(allocKind, traceKind, type, sizedType) \ + template <> struct MapTypeToFinalizeKind { \ + static const AllocKind kind = AllocKind::allocKind; \ + }; +FOR_EACH_NONOBJECT_ALLOCKIND(EXPAND_MAPTYPETOFINALIZEKIND) +#undef EXPAND_MAPTYPETOFINALIZEKIND template struct ParticipatesInCC {}; #define EXPAND_PARTICIPATES_IN_CC(_, type, addToCCKind) \ @@ -739,10 +739,19 @@ class ArenaLists #endif } + void checkEmptyArenaLists() { +#ifdef DEBUG + for (auto i : AllAllocKinds()) + checkEmptyArenaList(i); +#endif + } + void checkEmptyFreeList(AllocKind kind) { MOZ_ASSERT(freeLists[kind]->isEmpty()); } + void checkEmptyArenaList(AllocKind kind); + bool relocateArenas(Zone* zone, Arena*& relocatedListOut, JS::gcreason::Reason reason, SliceBudget& sliceBudget, gcstats::Statistics& stats); @@ -1155,15 +1164,6 @@ Forwarded(const JS::Value& value) return DispatchTyped(ForwardedFunctor(), value); } -inline void -MakeAccessibleAfterMovingGC(void* anyp) {} - -inline void -MakeAccessibleAfterMovingGC(JSObject* obj) { - if (obj->isNative()) - obj->as().updateShapeAfterMovingGC(); -} - template inline T MaybeForwarded(T t) @@ -1176,14 +1176,19 @@ MaybeForwarded(T t) #ifdef JSGC_HASH_TABLE_CHECKS +template +inline bool +IsGCThingValidAfterMovingGC(T* t) +{ + return !IsInsideNursery(t) && !RelocationOverlay::isCellForwarded(t); +} + template inline void CheckGCThingAfterMovingGC(T* t) { - if (t) { - MOZ_RELEASE_ASSERT(!IsInsideNursery(t)); - MOZ_RELEASE_ASSERT(!RelocationOverlay::isCellForwarded(t)); - } + if (t) + MOZ_RELEASE_ASSERT(IsGCThingValidAfterMovingGC(t)); } template @@ -1205,22 +1210,28 @@ CheckValueAfterMovingGC(const JS::Value& value) #endif // JSGC_HASH_TABLE_CHECKS +#define JS_FOR_EACH_ZEAL_MODE(D) \ + D(Poke, 1) \ + D(Alloc, 2) \ + D(FrameGC, 3) \ + D(VerifierPre, 4) \ + D(FrameVerifierPre, 5) \ + D(StackRooting, 6) \ + D(GenerationalGC, 7) \ + D(IncrementalRootsThenFinish, 8) \ + D(IncrementalMarkAllThenFinish, 9) \ + D(IncrementalMultipleSlices, 10) \ + D(IncrementalMarkingValidator, 11) \ + D(ElementsBarrier, 12) \ + D(CheckHashTablesOnMinorGC, 13) \ + D(Compact, 14) \ + D(CheckHeapOnMovingGC, 15) + enum class ZealMode { - Poke = 1, - Alloc = 2, - FrameGC = 3, - VerifierPre = 4, - FrameVerifierPre = 5, - StackRooting = 6, - GenerationalGC = 7, - IncrementalRootsThenFinish = 8, - IncrementalMarkAllThenFinish = 9, - IncrementalMultipleSlices = 10, - IncrementalMarkingValidator = 11, - ElementsBarrier = 12, - CheckHashTablesOnMinorGC = 13, - Compact = 14, - Limit = 14 +#define ZEAL_MODE(name, value) name = value, + JS_FOR_EACH_ZEAL_MODE(ZEAL_MODE) +#undef ZEAL_MODE + Limit = 15 }; enum VerifierType { diff --git a/js/src/jsgcinlines.h b/js/src/jsgcinlines.h index e861eee910..9a8a4b914d 100644 --- a/js/src/jsgcinlines.h +++ b/js/src/jsgcinlines.h @@ -18,6 +18,15 @@ namespace js { namespace gc { +inline void +MakeAccessibleAfterMovingGC(void* anyp) {} + +inline void +MakeAccessibleAfterMovingGC(JSObject* obj) { + if (obj->isNative()) + obj->as().updateShapeAfterMovingGC(); +} + static inline AllocKind GetGCObjectKind(const Class* clasp) { diff --git a/js/src/jspropertytree.cpp b/js/src/jspropertytree.cpp index 026366609e..66e3690870 100644 --- a/js/src/jspropertytree.cpp +++ b/js/src/jspropertytree.cpp @@ -14,6 +14,7 @@ #include "vm/Shape.h" +#include "vm/NativeObject-inl.h" #include "vm/Shape-inl.h" using namespace js; diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 05674d220c..d723cfc038 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -52,6 +52,7 @@ #include "jsfuninlines.h" #include "jsobjinlines.h" +#include "vm/NativeObject-inl.h" #include "vm/ScopeObject-inl.h" #include "vm/Stack-inl.h" diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 13ecac32dd..2481ef017a 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -1971,7 +1971,6 @@ class JSScript : public js::gc::TenuredCell #endif void finalize(js::FreeOp* fop); - void fixupAfterMovingGC() {} static const JS::TraceKind TraceKind = JS::TraceKind::Script; @@ -2426,7 +2425,6 @@ class LazyScript : public gc::TenuredCell friend class GCMarker; void traceChildren(JSTracer* trc); void finalize(js::FreeOp* fop); - void fixupAfterMovingGC() {} static const JS::TraceKind TraceKind = JS::TraceKind::LazyScript; diff --git a/js/src/jswrapper.h b/js/src/jswrapper.h index 31c36fcefe..73b7d939c5 100644 --- a/js/src/jswrapper.h +++ b/js/src/jswrapper.h @@ -44,18 +44,88 @@ class MOZ_STACK_CLASS WrapperOptions : public ProxyOptions { /* * A wrapper is a proxy with a target object to which it generally forwards - * operations, but may restrict access to certain operations or instrument the - * methods in various ways. A wrapper is distinct from a Direct Proxy Handler - * in the sense that it can be "unwrapped" in C++, exposing the underlying - * object (Direct Proxy Handlers have an underlying target object, but don't - * expect to expose this object via any kind of unwrapping operation). Callers - * should be careful to avoid unwrapping security wrappers in the wrong + * operations, but may restrict access to certain operations or augment those + * operations in various ways. + * + * A wrapper can be "unwrapped" in C++, exposing the underlying object. + * Callers should be careful to avoid unwrapping security wrappers in the wrong * context. + * + * Important: If you add a method implementation here, you probably also need + * to add an override in CrossCompartmentWrapper. If you don't, you risk + * compartment mismatches. See bug 945826 comment 0. */ -class JS_FRIEND_API(Wrapper) : public DirectProxyHandler +class JS_FRIEND_API(Wrapper) : public BaseProxyHandler { unsigned mFlags; + public: + explicit MOZ_CONSTEXPR Wrapper(unsigned aFlags, bool aHasPrototype = false, + bool aHasSecurityPolicy = false) + : BaseProxyHandler(&family, aHasPrototype, aHasSecurityPolicy), + mFlags(aFlags) + { } + + virtual bool finalizeInBackground(Value priv) const override; + + /* Standard internal methods. */ + virtual bool getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, + MutableHandle desc) const override; + virtual bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id, + Handle desc, + ObjectOpResult& result) const override; + virtual bool ownPropertyKeys(JSContext* cx, HandleObject proxy, + AutoIdVector& props) const override; + virtual bool delete_(JSContext* cx, HandleObject proxy, HandleId id, + ObjectOpResult& result) const override; + virtual bool enumerate(JSContext* cx, HandleObject proxy, + MutableHandleObject objp) const override; + virtual bool getPrototype(JSContext* cx, HandleObject proxy, + MutableHandleObject protop) const override; + virtual bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto, + ObjectOpResult& result) const override; + virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary, + MutableHandleObject protop) const override; + virtual bool setImmutablePrototype(JSContext* cx, HandleObject proxy, + bool* succeeded) const override; + virtual bool preventExtensions(JSContext* cx, HandleObject proxy, + ObjectOpResult& result) const override; + virtual bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const override; + virtual bool has(JSContext* cx, HandleObject proxy, HandleId id, + bool* bp) const override; + virtual bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, + HandleId id, MutableHandleValue vp) const override; + virtual bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, + HandleValue receiver, ObjectOpResult& result) const override; + virtual bool call(JSContext* cx, HandleObject proxy, const CallArgs& args) const override; + virtual bool construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const override; + + /* SpiderMonkey extensions. */ + virtual bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, + MutableHandle desc) const override; + virtual bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, + bool* bp) const override; + virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy, + AutoIdVector& props) const override; + virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl, + const CallArgs& args) const override; + virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, + bool* bp) const override; + virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, + ESClassValue* classValue) const override; + virtual bool isArray(JSContext* cx, HandleObject proxy, + JS::IsArrayAnswer* answer) const override; + virtual const char* className(JSContext* cx, HandleObject proxy) const override; + virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, + unsigned indent) const override; + virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, + RegExpGuard* g) const override; + virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, + MutableHandleValue vp) const override; + virtual bool isCallable(JSObject* obj) const override; + virtual bool isConstructor(JSObject* obj) const override; + virtual JSObject* weakmapKeyDelegate(JSObject* proxy) const override; + public: using BaseProxyHandler::Action; @@ -77,15 +147,6 @@ class JS_FRIEND_API(Wrapper) : public DirectProxyHandler return mFlags; } - explicit MOZ_CONSTEXPR Wrapper(unsigned aFlags, bool aHasPrototype = false, - bool aHasSecurityPolicy = false) - : DirectProxyHandler(&family, aHasPrototype, aHasSecurityPolicy), - mFlags(aFlags) - { } - - virtual bool finalizeInBackground(Value priv) const override; - virtual bool isConstructor(JSObject* obj) const override; - static const char family; static const Wrapper singleton; static const Wrapper singletonWithPrototype; diff --git a/js/src/moz.build b/js/src/moz.build index 2a88050d1d..28935de78b 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -303,7 +303,6 @@ UNIFIED_SOURCES += [ 'proxy/BaseProxyHandler.cpp', 'proxy/CrossCompartmentWrapper.cpp', 'proxy/DeadObjectProxy.cpp', - 'proxy/DirectProxyHandler.cpp', 'proxy/OpaqueCrossCompartmentWrapper.cpp', 'proxy/Proxy.cpp', 'proxy/ScriptedDirectProxyHandler.cpp', diff --git a/js/src/proxy/BaseProxyHandler.cpp b/js/src/proxy/BaseProxyHandler.cpp index 2d47bf4efa..d56d566866 100644 --- a/js/src/proxy/BaseProxyHandler.cpp +++ b/js/src/proxy/BaseProxyHandler.cpp @@ -277,7 +277,8 @@ BaseProxyHandler::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy } MOZ_ASSERT(i <= props.length()); - props.resize(i); + if (!props.resize(i)) + return false; return true; } diff --git a/js/src/proxy/CrossCompartmentWrapper.cpp b/js/src/proxy/CrossCompartmentWrapper.cpp index 43bbb0b4cc..9486c8a4c8 100644 --- a/js/src/proxy/CrossCompartmentWrapper.cpp +++ b/js/src/proxy/CrossCompartmentWrapper.cpp @@ -487,7 +487,7 @@ js::NukeCrossCompartmentWrappers(JSContext* cx, // Some cross-compartment wrappers are for strings. We're not // interested in those. const CrossCompartmentKey& k = e.front().key(); - if (k.kind != CrossCompartmentKey::ObjectWrapper) + if (!k.is()) continue; AutoWrapperRooter wobj(cx, WrapperValue(e)); @@ -616,12 +616,12 @@ js::RecomputeWrappers(JSContext* cx, const CompartmentFilter& sourceFilter, // Iterate over the wrappers, filtering appropriately. for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { // Filter out non-objects. - const CrossCompartmentKey& k = e.front().key(); - if (k.kind != CrossCompartmentKey::ObjectWrapper) + CrossCompartmentKey& k = e.front().mutableKey(); + if (!k.is()) continue; // Filter by target compartment. - if (!targetFilter.match(static_cast(k.wrapped)->compartment())) + if (!targetFilter.match(k.compartment())) continue; // Add it to the list. diff --git a/js/src/proxy/DirectProxyHandler.cpp b/js/src/proxy/DirectProxyHandler.cpp index a3292fea53..72b5976417 100644 --- a/js/src/proxy/DirectProxyHandler.cpp +++ b/js/src/proxy/DirectProxyHandler.cpp @@ -13,264 +13,4 @@ using namespace js; -bool -DirectProxyHandler::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, - MutableHandle desc) const -{ - assertEnteredPolicy(cx, proxy, id, GET | SET | GET_PROPERTY_DESCRIPTOR); - MOZ_ASSERT(!hasPrototype()); // Should never be called if there's a prototype. - RootedObject target(cx, proxy->as().target()); - return GetPropertyDescriptor(cx, target, id, desc); -} -bool -DirectProxyHandler::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, - MutableHandle desc) const -{ - assertEnteredPolicy(cx, proxy, id, GET | SET | GET_PROPERTY_DESCRIPTOR); - RootedObject target(cx, proxy->as().target()); - return GetOwnPropertyDescriptor(cx, target, id, desc); -} - -bool -DirectProxyHandler::defineProperty(JSContext* cx, HandleObject proxy, HandleId id, - Handle desc, - ObjectOpResult& result) const -{ - assertEnteredPolicy(cx, proxy, id, SET); - RootedObject target(cx, proxy->as().target()); - return DefineProperty(cx, target, id, desc, result); -} - -bool -DirectProxyHandler::ownPropertyKeys(JSContext* cx, HandleObject proxy, - AutoIdVector& props) const -{ - assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE); - RootedObject target(cx, proxy->as().target()); - return GetPropertyKeys(cx, target, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &props); -} - -bool -DirectProxyHandler::delete_(JSContext* cx, HandleObject proxy, HandleId id, - ObjectOpResult& result) const -{ - assertEnteredPolicy(cx, proxy, id, SET); - RootedObject target(cx, proxy->as().target()); - return DeleteProperty(cx, target, id, result); -} - -bool -DirectProxyHandler::enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const -{ - assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE); - MOZ_ASSERT(!hasPrototype()); // Should never be called if there's a prototype. - RootedObject target(cx, proxy->as().target()); - return GetIterator(cx, target, 0, objp); -} - -bool -DirectProxyHandler::call(JSContext* cx, HandleObject proxy, const CallArgs& args) const -{ - assertEnteredPolicy(cx, proxy, JSID_VOID, CALL); - RootedValue target(cx, proxy->as().private_()); - - InvokeArgs iargs(cx); - if (!FillArgumentsFromArraylike(cx, iargs, args)) - return false; - - return js::Call(cx, target, args.thisv(), iargs, args.rval()); -} - -bool -DirectProxyHandler::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const -{ - assertEnteredPolicy(cx, proxy, JSID_VOID, CALL); - - RootedValue target(cx, proxy->as().private_()); - if (!IsConstructor(target)) { - ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, target, nullptr); - return false; - } - - ConstructArgs cargs(cx); - if (!FillArgumentsFromArraylike(cx, cargs, args)) - return false; - - RootedObject obj(cx); - if (!Construct(cx, target, cargs, args.newTarget(), &obj)) - return false; - - args.rval().setObject(*obj); - return true; -} - -bool -DirectProxyHandler::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl, - const CallArgs& args) const -{ - args.setThis(ObjectValue(*args.thisv().toObject().as().target())); - if (!test(args.thisv())) { - ReportIncompatible(cx, args); - return false; - } - - return CallNativeImpl(cx, impl, args); -} - -bool -DirectProxyHandler::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, - bool* bp) const -{ - assertEnteredPolicy(cx, proxy, JSID_VOID, GET); - RootedObject target(cx, proxy->as().target()); - return HasInstance(cx, target, v, bp); -} - -bool -DirectProxyHandler::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const -{ - RootedObject target(cx, proxy->as().target()); - return GetPrototype(cx, target, protop); -} - -bool -DirectProxyHandler::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto, - ObjectOpResult& result) const -{ - RootedObject target(cx, proxy->as().target()); - return SetPrototype(cx, target, proto, result); -} - -bool -DirectProxyHandler::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, - bool* isOrdinary, MutableHandleObject protop) const -{ - RootedObject target(cx, proxy->as().target()); - return GetPrototypeIfOrdinary(cx, target, isOrdinary, protop); -} - -bool -DirectProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const -{ - RootedObject target(cx, proxy->as().target()); - return SetImmutablePrototype(cx, target, succeeded); -} - -bool -DirectProxyHandler::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) const -{ - RootedObject target(cx, proxy->as().target()); - return PreventExtensions(cx, target, result); -} - -bool -DirectProxyHandler::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const -{ - RootedObject target(cx, proxy->as().target()); - return IsExtensible(cx, target, extensible); -} - -bool -DirectProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy, - ESClassValue* classValue) const -{ - RootedObject target(cx, proxy->as().target()); - return GetBuiltinClass(cx, target, classValue); -} - -bool -DirectProxyHandler::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const -{ - RootedObject target(cx, proxy->as().target()); - return IsArray(cx, target, answer); -} - -const char* -DirectProxyHandler::className(JSContext* cx, HandleObject proxy) const -{ - assertEnteredPolicy(cx, proxy, JSID_VOID, GET); - RootedObject target(cx, proxy->as().target()); - return GetObjectClassName(cx, target); -} - -JSString* -DirectProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, - unsigned indent) const -{ - assertEnteredPolicy(cx, proxy, JSID_VOID, GET); - RootedObject target(cx, proxy->as().target()); - return fun_toStringHelper(cx, target, indent); -} - -bool -DirectProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy, - RegExpGuard* g) const -{ - RootedObject target(cx, proxy->as().target()); - return RegExpToShared(cx, target, g); -} - -bool -DirectProxyHandler::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const -{ - RootedObject target(cx, proxy->as().target()); - return Unbox(cx, target, vp); -} - -JSObject* -DirectProxyHandler::weakmapKeyDelegate(JSObject* proxy) const -{ - return UncheckedUnwrap(proxy); -} - -bool -DirectProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const -{ - assertEnteredPolicy(cx, proxy, id, GET); - MOZ_ASSERT(!hasPrototype()); // Should never be called if there's a prototype. - RootedObject target(cx, proxy->as().target()); - return HasProperty(cx, target, id, bp); -} - -bool -DirectProxyHandler::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const -{ - assertEnteredPolicy(cx, proxy, id, GET); - RootedObject target(cx, proxy->as().target()); - return HasOwnProperty(cx, target, id, bp); -} - -bool -DirectProxyHandler::get(JSContext* cx, HandleObject proxy, HandleValue receiver, - HandleId id, MutableHandleValue vp) const -{ - assertEnteredPolicy(cx, proxy, id, GET); - RootedObject target(cx, proxy->as().target()); - return GetProperty(cx, target, receiver, id, vp); -} - -bool -DirectProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, - HandleValue receiver, ObjectOpResult& result) const -{ - assertEnteredPolicy(cx, proxy, id, SET); - RootedObject target(cx, proxy->as().target()); - return SetProperty(cx, target, id, v, receiver, result); -} - -bool -DirectProxyHandler::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy, - AutoIdVector& props) const -{ - assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE); - RootedObject target(cx, proxy->as().target()); - return GetPropertyKeys(cx, target, JSITER_OWNONLY, &props); -} - -bool -DirectProxyHandler::isCallable(JSObject* obj) const -{ - JSObject * target = obj->as().target(); - return target->isCallable(); -} diff --git a/js/src/proxy/Proxy.cpp b/js/src/proxy/Proxy.cpp index 56e1a661f1..71accfaee1 100644 --- a/js/src/proxy/Proxy.cpp +++ b/js/src/proxy/Proxy.cpp @@ -174,8 +174,10 @@ js::AppendUnique(JSContext* cx, AutoIdVector& base, AutoIdVector& others) break; } } - if (unique) - uniqueOthers.append(others[i]); + if (unique) { + if (!uniqueOthers.append(others[i])) + return false; + } } return base.appendAll(uniqueOthers); } diff --git a/js/src/proxy/Wrapper.cpp b/js/src/proxy/Wrapper.cpp index 7c047265b5..0fc5f582f0 100644 --- a/js/src/proxy/Wrapper.cpp +++ b/js/src/proxy/Wrapper.cpp @@ -9,13 +9,300 @@ #include "jsexn.h" #include "jswrapper.h" +#include "js/Proxy.h" #include "vm/ErrorObject.h" +#include "vm/ProxyObject.h" #include "vm/WrapperObject.h" #include "jsobjinlines.h" +#include "vm/NativeObject-inl.h" + using namespace js; +bool +Wrapper::finalizeInBackground(Value priv) const +{ + if (!priv.isObject()) + return true; + + /* + * Make the 'background-finalized-ness' of the wrapper the same as the + * wrapped object, to allow transplanting between them. + * + * If the wrapped object is in the nursery then we know it doesn't have a + * finalizer, and so background finalization is ok. + */ + if (IsInsideNursery(&priv.toObject())) + return true; + return IsBackgroundFinalized(priv.toObject().asTenured().getAllocKind()); +} + +bool +Wrapper::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, + MutableHandle desc) const +{ + assertEnteredPolicy(cx, proxy, id, GET | SET | GET_PROPERTY_DESCRIPTOR); + RootedObject target(cx, proxy->as().target()); + return GetOwnPropertyDescriptor(cx, target, id, desc); +} + +bool +Wrapper::defineProperty(JSContext* cx, HandleObject proxy, HandleId id, + Handle desc, ObjectOpResult& result) const +{ + assertEnteredPolicy(cx, proxy, id, SET); + RootedObject target(cx, proxy->as().target()); + return DefineProperty(cx, target, id, desc, result); +} + +bool +Wrapper::ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) const +{ + assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE); + RootedObject target(cx, proxy->as().target()); + return GetPropertyKeys(cx, target, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &props); +} + +bool +Wrapper::delete_(JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result) const +{ + assertEnteredPolicy(cx, proxy, id, SET); + RootedObject target(cx, proxy->as().target()); + return DeleteProperty(cx, target, id, result); +} + +bool +Wrapper::enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const +{ + assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE); + MOZ_ASSERT(!hasPrototype()); // Should never be called if there's a prototype. + RootedObject target(cx, proxy->as().target()); + return GetIterator(cx, target, 0, objp); +} + +bool +Wrapper::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const +{ + RootedObject target(cx, proxy->as().target()); + return GetPrototype(cx, target, protop); +} + +bool +Wrapper::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto, + ObjectOpResult& result) const +{ + RootedObject target(cx, proxy->as().target()); + return SetPrototype(cx, target, proto, result); +} + +bool +Wrapper::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, + bool* isOrdinary, MutableHandleObject protop) const +{ + RootedObject target(cx, proxy->as().target()); + return GetPrototypeIfOrdinary(cx, target, isOrdinary, protop); +} + +bool +Wrapper::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const +{ + RootedObject target(cx, proxy->as().target()); + return SetImmutablePrototype(cx, target, succeeded); +} + +bool +Wrapper::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) const +{ + RootedObject target(cx, proxy->as().target()); + return PreventExtensions(cx, target, result); +} + +bool +Wrapper::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const +{ + RootedObject target(cx, proxy->as().target()); + return IsExtensible(cx, target, extensible); +} + +bool +Wrapper::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const +{ + assertEnteredPolicy(cx, proxy, id, GET); + MOZ_ASSERT(!hasPrototype()); // Should never be called if there's a prototype. + RootedObject target(cx, proxy->as().target()); + return HasProperty(cx, target, id, bp); +} + +bool +Wrapper::get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id, + MutableHandleValue vp) const +{ + assertEnteredPolicy(cx, proxy, id, GET); + RootedObject target(cx, proxy->as().target()); + return GetProperty(cx, target, receiver, id, vp); +} + +bool +Wrapper::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver, + ObjectOpResult& result) const +{ + assertEnteredPolicy(cx, proxy, id, SET); + RootedObject target(cx, proxy->as().target()); + return SetProperty(cx, target, id, v, receiver, result); +} + +bool +Wrapper::call(JSContext* cx, HandleObject proxy, const CallArgs& args) const +{ + assertEnteredPolicy(cx, proxy, JSID_VOID, CALL); + RootedValue target(cx, proxy->as().private_()); + + InvokeArgs iargs(cx); + if (!FillArgumentsFromArraylike(cx, iargs, args)) + return false; + + return js::Call(cx, target, args.thisv(), iargs, args.rval()); +} + +bool +Wrapper::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const +{ + assertEnteredPolicy(cx, proxy, JSID_VOID, CALL); + + RootedValue target(cx, proxy->as().private_()); + if (!IsConstructor(target)) { + ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, target, nullptr); + return false; + } + + ConstructArgs cargs(cx); + if (!FillArgumentsFromArraylike(cx, cargs, args)) + return false; + + RootedObject obj(cx); + if (!Construct(cx, target, cargs, args.newTarget(), &obj)) + return false; + + args.rval().setObject(*obj); + return true; +} + +bool +Wrapper::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, + MutableHandle desc) const +{ + assertEnteredPolicy(cx, proxy, id, GET | SET | GET_PROPERTY_DESCRIPTOR); + MOZ_ASSERT(!hasPrototype()); // Should never be called if there's a prototype. + RootedObject target(cx, proxy->as().target()); + return GetPropertyDescriptor(cx, target, id, desc); +} + +bool +Wrapper::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const +{ + assertEnteredPolicy(cx, proxy, id, GET); + RootedObject target(cx, proxy->as().target()); + return HasOwnProperty(cx, target, id, bp); +} + +bool +Wrapper::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy, + AutoIdVector& props) const +{ + assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE); + RootedObject target(cx, proxy->as().target()); + return GetPropertyKeys(cx, target, JSITER_OWNONLY, &props); +} + +bool +Wrapper::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl, + const CallArgs& args) const +{ + args.setThis(ObjectValue(*args.thisv().toObject().as().target())); + if (!test(args.thisv())) { + ReportIncompatible(cx, args); + return false; + } + + return CallNativeImpl(cx, impl, args); +} + +bool +Wrapper::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, + bool* bp) const +{ + assertEnteredPolicy(cx, proxy, JSID_VOID, GET); + RootedObject target(cx, proxy->as().target()); + return HasInstance(cx, target, v, bp); +} + +bool +Wrapper::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClassValue* classValue) const +{ + RootedObject target(cx, proxy->as().target()); + return GetBuiltinClass(cx, target, classValue); +} + +bool +Wrapper::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const +{ + RootedObject target(cx, proxy->as().target()); + return IsArray(cx, target, answer); +} + +const char* +Wrapper::className(JSContext* cx, HandleObject proxy) const +{ + assertEnteredPolicy(cx, proxy, JSID_VOID, GET); + RootedObject target(cx, proxy->as().target()); + return GetObjectClassName(cx, target); +} + +JSString* +Wrapper::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const +{ + assertEnteredPolicy(cx, proxy, JSID_VOID, GET); + RootedObject target(cx, proxy->as().target()); + return fun_toStringHelper(cx, target, indent); +} + +bool +Wrapper::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const +{ + RootedObject target(cx, proxy->as().target()); + return RegExpToShared(cx, target, g); +} + +bool +Wrapper::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const +{ + RootedObject target(cx, proxy->as().target()); + return Unbox(cx, target, vp); +} + +bool +Wrapper::isCallable(JSObject* obj) const +{ + JSObject * target = obj->as().target(); + return target->isCallable(); +} + +bool +Wrapper::isConstructor(JSObject* obj) const +{ + // For now, all wrappers are constructable if they are callable. We will want to eventually + // decouple this behavior, but none of the Wrapper infrastructure is currently prepared for + // that. + return isCallable(obj); +} + +JSObject* +Wrapper::weakmapKeyDelegate(JSObject* proxy) const +{ + return UncheckedUnwrap(proxy); +} + JSObject* Wrapper::New(JSContext* cx, JSObject* obj, const Wrapper* handler, const WrapperOptions& options) @@ -48,15 +335,6 @@ Wrapper::wrappedObject(JSObject* wrapper) return target; } -bool -Wrapper::isConstructor(JSObject* obj) const -{ - // For now, all wrappers are constructable if they are callable. We will want to eventually - // decouple this behavior, but none of the Wrapper infrastructure is currently prepared for - // that. - return isCallable(obj); -} - JS_FRIEND_API(JSObject*) js::UncheckedUnwrap(JSObject* wrapped, bool stopAtWindowProxy, unsigned* flagsp) { @@ -70,8 +348,8 @@ js::UncheckedUnwrap(JSObject* wrapped, bool stopAtWindowProxy, unsigned* flagsp) flags |= Wrapper::wrapperHandler(wrapped)->flags(); wrapped = wrapped->as().private_().toObjectOrNull(); - // This can be called from DirectProxyHandler::weakmapKeyDelegate() on a - // wrapper whose referent has been moved while it is still unmarked. + // This can be called from Wrapper::weakmapKeyDelegate() on a wrapper + // whose referent has been moved while it is still unmarked. if (wrapped) wrapped = MaybeForwarded(wrapped); } @@ -111,7 +389,7 @@ JSObject* Wrapper::defaultProto = TaggedProto::LazyProto; /* Compartments. */ -extern JSObject* +JSObject* js::TransparentObjectWrapper(JSContext* cx, HandleObject existing, HandleObject obj) { // Allow wrapping outer window proxies. @@ -140,20 +418,3 @@ ErrorCopier::~ErrorCopier() } } } - -bool Wrapper::finalizeInBackground(Value priv) const -{ - if (!priv.isObject()) - return true; - - /* - * Make the 'background-finalized-ness' of the wrapper the same as the - * wrapped object, to allow transplanting between them. - * - * If the wrapped object is in the nursery then we know it doesn't have a - * finalizer, and so background finalization is ok. - */ - if (IsInsideNursery(&priv.toObject())) - return true; - return IsBackgroundFinalized(priv.toObject().asTenured().getAllocKind()); -} diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index bc951bd2cc..b0181f205a 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -178,7 +178,8 @@ static bool enableNativeRegExp = false; static bool enableUnboxedArrays = false; static bool enableSharedMemory = SHARED_MEMORY_DEFAULT; #ifdef JS_GC_ZEAL -static char gZealStr[128]; +static uint32_t gZealBits = 0; +static uint32_t gZealFrequency = 0; #endif static bool printTiming = false; static const char* jsCacheDir = nullptr; @@ -3843,22 +3844,6 @@ runOffThreadScript(JSContext* cx, unsigned argc, Value* vp) return JS_ExecuteScript(cx, script, args.rval()); } -static bool -CompileOffThreadModule(JSContext* cx, const ReadOnlyCompileOptions& options, - const char16_t* chars, size_t length, - JS::OffThreadCompileCallback callback, void* callbackData) -{ - MOZ_ASSERT(JS::CanCompileOffThread(cx, options, length)); - return StartOffThreadParseModule(cx, options, chars, length, callback, callbackData); -} - -static JSObject* -FinishOffThreadModule(JSContext* maybecx, JSRuntime* rt, void* token) -{ - MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt)); - return HelperThreadState().finishModuleParseTask(maybecx, rt, token); -} - static bool OffThreadCompileModule(JSContext* cx, unsigned argc, Value* vp) { @@ -3912,8 +3897,8 @@ OffThreadCompileModule(JSContext* cx, unsigned argc, Value* vp) return false; } - if (!CompileOffThreadModule(cx, options, chars, length, - OffThreadCompileScriptCallback, nullptr)) + if (!JS::CompileOffThreadModule(cx, options, chars, length, + OffThreadCompileScriptCallback, nullptr)) { offThreadState.abandon(cx); return false; @@ -3938,7 +3923,7 @@ FinishOffThreadModule(JSContext* cx, unsigned argc, Value* vp) return false; } - RootedObject module(cx, FinishOffThreadModule(cx, rt, token)); + RootedObject module(cx, JS::FinishOffThreadModule(cx, rt, token)); if (!module) return false; @@ -6510,9 +6495,6 @@ NewGlobalObject(JSContext* cx, JS::CompartmentOptions& options, /* Initialize FakeDOMObject.prototype */ InitDOMObject(domProto); - - if (!js::InitModuleClasses(cx, glob)) - return nullptr; } JS_FireOnNewGlobalObject(cx, glob); @@ -6906,12 +6888,11 @@ SetRuntimeOptions(JSRuntime* rt, const OptionParser& op) #ifdef JS_GC_ZEAL const char* zealStr = op.getStringOption("gc-zeal"); - gZealStr[0] = 0; if (zealStr) { if (!rt->gc.parseAndSetZeal(zealStr)) return false; - strncpy(gZealStr, zealStr, sizeof(gZealStr)); - gZealStr[sizeof(gZealStr)-1] = 0; + uint32_t nextScheduled; + rt->gc.getZealBits(&gZealBits, &gZealFrequency, &nextScheduled); } #endif @@ -6932,8 +6913,13 @@ SetWorkerRuntimeOptions(JSRuntime* rt) rt->profilingScripts = enableCodeCoverage || enableDisassemblyDumps; #ifdef JS_GC_ZEAL - if (*gZealStr) - rt->gc.parseAndSetZeal(gZealStr); + if (gZealBits && gZealFrequency) { +#define ZEAL_MODE(_, value) \ + if (gZealBits & (1 << value)) \ + rt->gc.setZeal(value, gZealFrequency); + JS_FOR_EACH_ZEAL_MODE(ZEAL_MODE) +#undef ZEAL_MODE + } #endif JS_SetNativeStackQuota(rt, gMaxStackSize); @@ -7235,7 +7221,7 @@ main(int argc, char** argv, char** envp) #endif || !op.addIntOption('\0', "nursery-size", "SIZE-MB", "Set the maximum nursery size in MB", 16) #ifdef JS_GC_ZEAL - || !op.addStringOption('z', "gc-zeal", "LEVEL[,N]", gc::ZealModeHelpText) + || !op.addStringOption('z', "gc-zeal", "LEVEL(;LEVEL)*[,N]", gc::ZealModeHelpText) #endif || !op.addStringOption('\0', "module-load-path", "DIR", "Set directory to load modules from") ) diff --git a/js/src/shell/jsshell.cpp b/js/src/shell/jsshell.cpp index eaa243f882..7f8e53b9a4 100644 --- a/js/src/shell/jsshell.cpp +++ b/js/src/shell/jsshell.cpp @@ -9,6 +9,11 @@ #include "shell/jsshell.h" #include "jsapi.h" +#include "jsfriendapi.h" + +#include "vm/StringBuffer.h" + +using namespace JS; namespace js { namespace shell { diff --git a/js/src/shell/jsshell.h b/js/src/shell/jsshell.h index e1939f8d8c..379b7e2859 100644 --- a/js/src/shell/jsshell.h +++ b/js/src/shell/jsshell.h @@ -27,7 +27,7 @@ void my_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report); bool -GenerateInterfaceHelp(JSContext* cx, HandleObject obj, const char* name); +GenerateInterfaceHelp(JSContext* cx, JS::HandleObject obj, const char* name); JSString* FileAsString(JSContext* cx, const char* pathname); diff --git a/js/src/tests/js1_8_5/regress/regress-610026.js b/js/src/tests/js1_8_5/regress/regress-610026.js index 382879319e..712b6f88c7 100644 --- a/js/src/tests/js1_8_5/regress/regress-610026.js +++ b/js/src/tests/js1_8_5/regress/regress-610026.js @@ -8,7 +8,7 @@ var expect = "pass"; var actual; /* - * We hardcode here that GenerateBlockId limits a program to 2^20 blocks. Start + * We hardcode here that a program is limited to 2^20 blocks. Start * with 2^19 blocks, then test 2^20 - 1 blocks, finally test the limit. */ var s = "{}"; diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 1203592fa3..a5b9fc2c74 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -182,6 +182,8 @@ macro(minute, minute, "minute") \ macro(missingArguments, missingArguments, "missingArguments") \ macro(module, module, "module") \ + macro(ModuleDeclarationInstantiation, ModuleDeclarationInstantiation, "ModuleDeclarationInstantiation") \ + macro(ModuleEvaluation, ModuleEvaluation, "ModuleEvaluation") \ macro(month, month, "month") \ macro(multiline, multiline, "multiline") \ macro(name, name, "name") \ diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 57d62caf18..57efa6f97b 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -948,7 +948,7 @@ Debugger::wrapEnvironment(JSContext* cx, Handle env, MutableHandleValue rv return false; } - CrossCompartmentKey key(CrossCompartmentKey::DebuggerEnvironment, object, env); + CrossCompartmentKey key(object, env, CrossCompartmentKey::DebuggerEnvironment); if (!object->compartment()->putWrapper(cx, key, ObjectValue(*envobj))) { NukeDebuggerWrapper(envobj); environments.remove(env); @@ -995,7 +995,7 @@ Debugger::wrapDebuggeeValue(JSContext* cx, MutableHandleValue vp) } if (obj->compartment() != object->compartment()) { - CrossCompartmentKey key(CrossCompartmentKey::DebuggerObject, object, obj); + CrossCompartmentKey key(object, obj, CrossCompartmentKey::DebuggerObject); if (!object->compartment()->putWrapper(cx, key, ObjectValue(*dobj))) { NukeDebuggerWrapper(dobj); objects.remove(obj); @@ -5065,7 +5065,7 @@ Debugger::newDebuggerScript(JSContext* cx, Handle refere template JSObject* -Debugger::wrapVariantReferent(JSContext* cx, Map& map, CrossCompartmentKey::Kind keyKind, +Debugger::wrapVariantReferent(JSContext* cx, Map& map, Handle key, Handle referent) { assertSameCompartment(cx, object); @@ -5084,7 +5084,6 @@ Debugger::wrapVariantReferent(JSContext* cx, Map& map, CrossCompartmentKey::Kind return nullptr; } - CrossCompartmentKey key(keyKind, object, untaggedReferent); if (!object->compartment()->putWrapper(cx, key, ObjectValue(*wrapper))) { NukeDebuggerWrapper(wrapper); map.remove(untaggedReferent); @@ -5102,11 +5101,16 @@ Debugger::wrapVariantReferent(JSContext* cx, Handle refe { JSObject* obj; if (referent.is()) { + Handle untaggedReferent = referent.template as(); + Rooted key(cx, CrossCompartmentKey(object, untaggedReferent)); obj = wrapVariantReferent( - cx, scripts, CrossCompartmentKey::DebuggerScript, referent); + cx, scripts, key, referent); } else { + Handle untaggedReferent = referent.template as(); + Rooted key(cx, CrossCompartmentKey(object, untaggedReferent, + CrossCompartmentKey::DebuggerObjectKind::DebuggerWasmScript)); obj = wrapVariantReferent( - cx, wasmModuleScripts, CrossCompartmentKey::DebuggerWasmScript, referent); + cx, wasmModuleScripts, key, referent); } MOZ_ASSERT_IF(obj, GetScriptReferent(obj) == referent); return obj; @@ -6344,11 +6348,17 @@ Debugger::wrapVariantReferent(JSContext* cx, Handle refe { JSObject* obj; if (referent.is()) { + Handle untaggedReferent = referent.template as(); + Rooted key(cx, CrossCompartmentKey(object, untaggedReferent, + CrossCompartmentKey::DebuggerObjectKind::DebuggerSource)); obj = wrapVariantReferent( - cx, sources, CrossCompartmentKey::DebuggerSource, referent); + cx, sources, key, referent); } else { + Handle untaggedReferent = referent.template as(); + Rooted key(cx, CrossCompartmentKey(object, untaggedReferent, + CrossCompartmentKey::DebuggerObjectKind::DebuggerWasmSource)); obj = wrapVariantReferent( - cx, wasmModuleSources, CrossCompartmentKey::DebuggerWasmSource, referent); + cx, wasmModuleSources, key, referent); } MOZ_ASSERT_IF(obj, GetSourceReferent(obj) == referent); return obj; diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h index bcc5e6742b..33980ae896 100644 --- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -706,7 +706,7 @@ class Debugger : private mozilla::LinkedListElement * whenever possible. */ template - JSObject* wrapVariantReferent(JSContext* cx, Map& map, CrossCompartmentKey::Kind keyKind, + JSObject* wrapVariantReferent(JSContext* cx, Map& map, Handle key, Handle referent); JSObject* wrapVariantReferent(JSContext* cx, Handle referent); JSObject* wrapVariantReferent(JSContext* cx, Handle referent); diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index bee20ba835..61367ed35f 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -856,14 +856,7 @@ GlobalObject::addIntrinsicValue(JSContext* cx, Handle global, /* static */ bool GlobalObject::ensureModulePrototypesCreated(JSContext *cx, Handle global) { - if (global->getSlot(MODULE_PROTO).isUndefined()) { - MOZ_ASSERT(global->getSlot(IMPORT_ENTRY_PROTO).isUndefined() && - global->getSlot(EXPORT_ENTRY_PROTO).isUndefined()); - if (!js::InitModuleClasses(cx, global)) - return false; - } - MOZ_ASSERT(global->getSlot(MODULE_PROTO).isObject() && - global->getSlot(IMPORT_ENTRY_PROTO).isObject() && - global->getSlot(EXPORT_ENTRY_PROTO).isObject()); - return true; + return global->getOrCreateObject(cx, MODULE_PROTO, initModuleProto) && + global->getOrCreateObject(cx, IMPORT_ENTRY_PROTO, initImportEntryProto) && + global->getOrCreateObject(cx, EXPORT_ENTRY_PROTO, initExportEntryProto); } diff --git a/js/src/vm/ObjectGroup.h b/js/src/vm/ObjectGroup.h index 82befe49c4..9bd31c9c05 100644 --- a/js/src/vm/ObjectGroup.h +++ b/js/src/vm/ObjectGroup.h @@ -424,7 +424,6 @@ class ObjectGroup : public gc::TenuredCell size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; void finalize(FreeOp* fop); - void fixupAfterMovingGC() {} static const JS::TraceKind TraceKind = JS::TraceKind::ObjectGroup; diff --git a/js/src/vm/Printer.h b/js/src/vm/Printer.h index 089f0798bd..c8aa43f268 100644 --- a/js/src/vm/Printer.h +++ b/js/src/vm/Printer.h @@ -7,6 +7,8 @@ #ifndef vm_Printer_h #define vm_Printer_h +#include "mozilla/Attributes.h" + #include #include #include @@ -78,14 +80,14 @@ class Sprinter final : public GenericPrinter size_t size; // size of buffer allocated at base ptrdiff_t offset; // offset of next free char in buffer - bool realloc_(size_t newSize); + MOZ_MUST_USE bool realloc_(size_t newSize); public: explicit Sprinter(ExclusiveContext* cx, bool shouldReportOOM = true); ~Sprinter(); // Initialize this sprinter, returns false on error. - bool init(); + MOZ_MUST_USE bool init(); void checkInvariants() const; @@ -132,7 +134,7 @@ class Fprinter final : public GenericPrinter ~Fprinter(); // Initialize this printer, returns false on error. - bool init(const char* path); + MOZ_MUST_USE bool init(const char* path); void init(FILE* fp); bool isInitialized() const { return file_ != nullptr; diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 6dc765b374..6a2074852f 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -2319,7 +2319,8 @@ class DebugScopeProxy : public BaseProxyHandler if (inScope) props[j++].set(props[i]); } - props.resize(j); + if (!props.resize(j)) + return false; } /* diff --git a/js/src/vm/Shape.h b/js/src/vm/Shape.h index 28b99d334b..a1339d199a 100644 --- a/js/src/vm/Shape.h +++ b/js/src/vm/Shape.h @@ -454,8 +454,6 @@ class BaseShape : public gc::TenuredCell void traceChildren(JSTracer* trc); void traceChildrenSkipShapeTable(JSTracer* trc); - void fixupAfterMovingGC() {} - private: static void staticAsserts() { JS_STATIC_ASSERT(offsetof(BaseShape, clasp_) == offsetof(js::shadow::BaseShape, clasp_)); diff --git a/js/src/vm/String.h b/js/src/vm/String.h index 15a2292239..e618c36f06 100644 --- a/js/src/vm/String.h +++ b/js/src/vm/String.h @@ -474,8 +474,6 @@ class JSString : public js::gc::TenuredCell inline void finalize(js::FreeOp* fop); - void fixupAfterMovingGC() {} - /* Gets the number of bytes that the chars take on the heap. */ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf); diff --git a/js/xpconnect/wrappers/FilteringWrapper.cpp b/js/xpconnect/wrappers/FilteringWrapper.cpp index c2e17ae067..2b95dec2ec 100644 --- a/js/xpconnect/wrappers/FilteringWrapper.cpp +++ b/js/xpconnect/wrappers/FilteringWrapper.cpp @@ -29,7 +29,9 @@ Filter(JSContext* cx, HandleObject wrapper, AutoIdVector& props) else if (JS_IsExceptionPending(cx)) return false; } - props.resize(w); + if (!props.resize(w)) + return false; + return true; } diff --git a/media/libcubeb/README_MOZILLA b/media/libcubeb/README_MOZILLA index 325ac1e2e0..4cbcd2e457 100644 --- a/media/libcubeb/README_MOZILLA +++ b/media/libcubeb/README_MOZILLA @@ -5,4 +5,4 @@ Makefile.in build files for the Mozilla build system. The cubeb git repository is: git://github.com/kinetiknz/cubeb.git -The git commit ID used was 17e3048d0afa1152776fb1867cdb61c49fae69e4. +The git commit ID used was 073c9f011114fe4208b4aa49e99e33cde1deb6f1. diff --git a/media/libcubeb/src/cubeb.c b/media/libcubeb/src/cubeb.c index 9b5ba02b29..2fc3860d7c 100644 --- a/media/libcubeb/src/cubeb.c +++ b/media/libcubeb/src/cubeb.c @@ -66,6 +66,7 @@ int validate_stream_params(cubeb_stream_params * input_stream_params, cubeb_stream_params * output_stream_params) { + XASSERT(input_stream_params || output_stream_params); if (output_stream_params) { if (output_stream_params->rate < 1000 || output_stream_params->rate > 192000 || output_stream_params->channels < 1 || output_stream_params->channels > 8) { diff --git a/media/libcubeb/src/cubeb_alsa.c b/media/libcubeb/src/cubeb_alsa.c index f71dea6afe..24f34d7972 100644 --- a/media/libcubeb/src/cubeb_alsa.c +++ b/media/libcubeb/src/cubeb_alsa.c @@ -801,7 +801,11 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, assert(ctx && stream); - assert(!input_stream_params && "not supported."); + if (input_stream_params) { + /* Capture support not yet implemented. */ + return CUBEB_ERROR_NOT_SUPPORTED; + } + if (input_device || output_device) { /* Device selection not yet implemented. */ return CUBEB_ERROR_DEVICE_UNAVAILABLE; diff --git a/media/libcubeb/src/cubeb_audiounit.c b/media/libcubeb/src/cubeb_audiounit.c index ede865653c..c13fcd591d 100644 --- a/media/libcubeb/src/cubeb_audiounit.c +++ b/media/libcubeb/src/cubeb_audiounit.c @@ -151,6 +151,12 @@ audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream) return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL; } +static void +audiounit_make_silent(AudioBuffer * ioData) +{ + memset(ioData->mData, 0, ioData->mDataByteSize); +} + static OSStatus audiounit_input_callback(void * user_ptr, AudioUnitRenderActionFlags * flags, @@ -193,7 +199,12 @@ audiounit_input_callback(void * user_ptr, bus, input_frames, &input_buffer_list); - assert(r == noErr); + if (r != noErr) { + LOG("Input AudioUnitRender failed with error=%d", r); + audiounit_make_silent(input_buffer); + return r; + } + LOG("- input: buffers %d, size %d, channels %d, frames %d\n", input_buffer_list.mNumberBuffers, input_buffer_list.mBuffers[0].mDataByteSize, @@ -231,12 +242,6 @@ audiounit_input_callback(void * user_ptr, return noErr; } -static void -audiounit_make_silent(AudioBuffer * ioData) -{ - memset(ioData->mData, 0, ioData->mDataByteSize); -} - static OSStatus audiounit_output_callback(void * user_ptr, AudioUnitRenderActionFlags * flags, diff --git a/media/libcubeb/src/cubeb_jack.cpp b/media/libcubeb/src/cubeb_jack.cpp new file mode 100644 index 0000000000..265dc79f5c --- /dev/null +++ b/media/libcubeb/src/cubeb_jack.cpp @@ -0,0 +1,1038 @@ +/* + * Copyright © 2012 David Richards + * Copyright © 2013 Sebastien Alaiwan + * Copyright © 2016 Damien Zammit + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ +#define _DEFAULT_SOURCE +#define _BSD_SOURCE +#define _POSIX_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cubeb/cubeb.h" +#include "cubeb-internal.h" +#include "cubeb_resampler.h" + +#include +#include + +#define JACK_API_VISIT(X) \ + X(jack_activate) \ + X(jack_client_close) \ + X(jack_client_open) \ + X(jack_connect) \ + X(jack_free) \ + X(jack_get_ports) \ + X(jack_get_sample_rate) \ + X(jack_get_xrun_delayed_usecs) \ + X(jack_get_buffer_size) \ + X(jack_port_get_buffer) \ + X(jack_port_name) \ + X(jack_port_register) \ + X(jack_port_unregister) \ + X(jack_port_get_latency_range) \ + X(jack_set_process_callback) \ + X(jack_set_xrun_callback) \ + X(jack_set_graph_order_callback) + +#define IMPORT_FUNC(x) static decltype(x) * api_##x; +JACK_API_VISIT(IMPORT_FUNC); + +static const int MAX_STREAMS = 16; +static const int MAX_CHANNELS = 8; +static const int FIFO_SIZE = 4096 * sizeof(float); + +enum devstream { + NONE = 0, + IN_ONLY, + OUT_ONLY, + DUPLEX, +}; + +static void +s16ne_to_float(float * dst, const int16_t * src, size_t n) +{ + for (size_t i = 0; i < n; i++) + *(dst++) = (float)((float)*(src++) / 32767.0f); +} + +static void +float_to_s16ne(int16_t * dst, float * src, size_t n) +{ + for (size_t i = 0; i < n; i++) { + if (*src > 1.f) *src = 1.f; + if (*src < -1.f) *src = -1.f; + *(dst++) = (int16_t)((int16_t)(*(src++) * 32767)); + } +} + +extern "C" +{ +/*static*/ int jack_init (cubeb ** context, char const * context_name); +} +static char const * cbjack_get_backend_id(cubeb * context); +static int cbjack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels); +static int cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms); +static int cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_ms); +static int cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate); +static void cbjack_destroy(cubeb * context); +static void cbjack_interleave_capture(cubeb_stream * stream, float **in, jack_nframes_t nframes, bool format_mismatch); +static void cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short **bufs_in, float **bufs_out, jack_nframes_t nframes); +static void cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float **bufs_in, float **bufs_out, jack_nframes_t nframes); +static int cbjack_stream_device_destroy(cubeb_stream * stream, + cubeb_device * device); +static int cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device); +static int cbjack_enumerate_devices(cubeb * context, cubeb_device_type type, + cubeb_device_collection ** collection); +static int cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void * user_ptr); +static void cbjack_stream_destroy(cubeb_stream * stream); +static int cbjack_stream_start(cubeb_stream * stream); +static int cbjack_stream_stop(cubeb_stream * stream); +static int cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position); +static int cbjack_stream_set_volume(cubeb_stream * stm, float volume); + +static struct cubeb_ops const cbjack_ops = { + .init = jack_init, + .get_backend_id = cbjack_get_backend_id, + .get_max_channel_count = cbjack_get_max_channel_count, + .get_min_latency = cbjack_get_min_latency, + .get_preferred_sample_rate = cbjack_get_preferred_sample_rate, + .enumerate_devices = cbjack_enumerate_devices, + .destroy = cbjack_destroy, + .stream_init = cbjack_stream_init, + .stream_destroy = cbjack_stream_destroy, + .stream_start = cbjack_stream_start, + .stream_stop = cbjack_stream_stop, + .stream_get_position = cbjack_stream_get_position, + .stream_get_latency = cbjack_get_latency, + .stream_set_volume = cbjack_stream_set_volume, + .stream_set_panning = NULL, + .stream_get_current_device = cbjack_stream_get_current_device, + .stream_device_destroy = cbjack_stream_device_destroy, + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = NULL +}; + +struct cubeb_stream { + cubeb * context; + + /**< Mutex for each stream */ + pthread_mutex_t mutex; + + bool in_use; /**< Set to false iff the stream is free */ + bool ports_ready; /**< Set to true iff the JACK ports are ready */ + + cubeb_data_callback data_callback; + cubeb_state_callback state_callback; + void * user_ptr; + cubeb_stream_params in_params; + cubeb_stream_params out_params; + + cubeb_resampler * resampler; + + uint64_t position; + bool pause; + float ratio; + enum devstream devs; + char stream_name[256]; + jack_port_t * output_ports[MAX_CHANNELS]; + jack_port_t * input_ports[MAX_CHANNELS]; + float volume; +}; + +struct cubeb { + struct cubeb_ops const * ops; + void * libjack; + + /**< Mutex for whole context */ + pthread_mutex_t mutex; + + /**< Audio buffers, converted to float */ + float in_float_interleaved_buffer[FIFO_SIZE * MAX_CHANNELS]; + float out_float_interleaved_buffer[FIFO_SIZE * MAX_CHANNELS]; + + /**< Audio buffer, at the sampling rate of the output */ + float in_resampled_interleaved_buffer_float[FIFO_SIZE * MAX_CHANNELS * 3]; + int16_t in_resampled_interleaved_buffer_s16ne[FIFO_SIZE * MAX_CHANNELS * 3]; + float out_resampled_interleaved_buffer_float[FIFO_SIZE * MAX_CHANNELS * 3]; + int16_t out_resampled_interleaved_buffer_s16ne[FIFO_SIZE * MAX_CHANNELS * 3]; + + cubeb_stream streams[MAX_STREAMS]; + unsigned int active_streams; + + cubeb_device_info * devinfo[2]; + cubeb_device_collection_changed_callback collection_changed_callback; + + bool active; + unsigned int jack_sample_rate; + unsigned int jack_latency; + unsigned int jack_xruns; + unsigned int jack_buffer_size; + unsigned int fragment_size; + unsigned int output_bytes_per_frame; + jack_client_t * jack_client; +}; + +static int +load_jack_lib(cubeb * context) +{ +#ifdef __APPLE__ + context->libjack = dlopen("libjack.0.dylib", RTLD_LAZY); + context->libjack = dlopen("/usr/local/lib/libjack.0.dylib", RTLD_LAZY); +#elif defined(__WIN32__) +# ifdef _WIN64 + context->libjack = LoadLibrary("libjack64.dll"); +# else + context->libjack = LoadLibrary("libjack.dll"); +# endif +#else + context->libjack = dlopen("libjack.so.0", RTLD_LAZY); +#endif + if (!context->libjack) { + return CUBEB_ERROR; + } + +#define LOAD(x) \ + { \ + api_##x = (decltype(x)*)dlsym(context->libjack, #x); \ + if (!api_##x) { \ + dlclose(context->libjack); \ + return CUBEB_ERROR; \ + } \ + } + + JACK_API_VISIT(LOAD); +#undef LOAD + + return CUBEB_OK; +} + +static void +cbjack_connect_ports (cubeb_stream * stream) +{ + const char ** phys_in_ports = api_jack_get_ports (stream->context->jack_client, + NULL, NULL, + JackPortIsInput + | JackPortIsPhysical); + const char ** phys_out_ports = api_jack_get_ports (stream->context->jack_client, + NULL, NULL, + JackPortIsOutput + | JackPortIsPhysical); + + if (*phys_in_ports == NULL) { + goto skipplayback; + } + + // Connect outputs to playback + for (unsigned int c = 0; c < stream->out_params.channels && phys_in_ports[c] != NULL; c++) { + const char *src_port = api_jack_port_name (stream->output_ports[c]); + + api_jack_connect (stream->context->jack_client, src_port, phys_in_ports[c]); + } + +skipplayback: + if (*phys_out_ports == NULL) { + goto end; + } + // Connect inputs to capture + for (unsigned int c = 0; c < stream->in_params.channels && phys_out_ports[c] != NULL; c++) { + const char *src_port = api_jack_port_name (stream->input_ports[c]); + + api_jack_connect (stream->context->jack_client, phys_out_ports[c], src_port); + } +end: + api_jack_free(phys_out_ports); + api_jack_free(phys_in_ports); +} + +static int +cbjack_xrun_callback(void * arg) +{ + cubeb * ctx = (cubeb *)arg; + + float delay = api_jack_get_xrun_delayed_usecs(ctx->jack_client); + int fragments = (int)ceilf( ((delay / 1000000.0) * ctx->jack_sample_rate ) + / (float)(ctx->jack_buffer_size) ); + ctx->jack_xruns += fragments; + return 0; +} + +static int +cbjack_graph_order_callback(void * arg) +{ + cubeb * ctx = (cubeb *)arg; + int i; + uint32_t rate; + + jack_latency_range_t latency_range; + jack_nframes_t port_latency, max_latency = 0; + + for (int j = 0; j < MAX_STREAMS; j++) { + cubeb_stream *stm = &ctx->streams[j]; + + if (!stm->in_use) + continue; + if (!stm->ports_ready) + continue; + + for (i = 0; i < (int)stm->out_params.channels; ++i) { + api_jack_port_get_latency_range(stm->output_ports[i], JackPlaybackLatency, &latency_range); + port_latency = latency_range.max; + if (port_latency > max_latency) + max_latency = port_latency; + } + /* Cap minimum latency to 128 frames */ + if (max_latency < 128) + max_latency = 128; + } + + if (cbjack_get_preferred_sample_rate(ctx, &rate) == CUBEB_ERROR) + ctx->jack_latency = (max_latency * 1000) / 48000; + else + ctx->jack_latency = (max_latency * 1000) / rate; + return 0; +} + +static int +cbjack_process(jack_nframes_t nframes, void * arg) +{ + cubeb * ctx = (cubeb *)arg; + int t_jack_xruns = ctx->jack_xruns; + int i; + + for (int j = 0; j < MAX_STREAMS; j++) { + cubeb_stream *stm = &ctx->streams[j]; + float *bufs_out[stm->out_params.channels]; + float *bufs_in[stm->in_params.channels]; + + if (!stm->in_use) + continue; + + // handle xruns by skipping audio that should have been played + for (i = 0; i < t_jack_xruns; i++) { + stm->position += ctx->fragment_size * stm->ratio; + } + ctx->jack_xruns -= t_jack_xruns; + + if (!stm->ports_ready) + continue; + + if (stm->devs & OUT_ONLY) { + // get jack output buffers + for (i = 0; i < (int)stm->out_params.channels; i++) + bufs_out[i] = (float*)api_jack_port_get_buffer(stm->output_ports[i], nframes); + } + if (stm->devs & IN_ONLY) { + // get jack input buffers + for (i = 0; i < (int)stm->in_params.channels; i++) + bufs_in[i] = (float*)api_jack_port_get_buffer(stm->input_ports[i], nframes); + } + if (stm->pause) { + // paused, play silence on output + if (stm->devs & OUT_ONLY) { + for (unsigned int c = 0; c < stm->out_params.channels; c++) { + float* buffer_out = bufs_out[c]; + for (long f = 0; f < nframes; f++) { + buffer_out[f] = 0.f; + } + } + } + if (stm->devs & IN_ONLY) { + // paused, capture silence + for (unsigned int c = 0; c < stm->in_params.channels; c++) { + float* buffer_in = bufs_in[c]; + for (long f = 0; f < nframes; f++) { + buffer_in[f] = 0.f; + } + } + } + } else { + + // try to lock stream mutex + if (pthread_mutex_trylock(&stm->mutex) == 0) { + + int16_t *in_s16ne = stm->context->in_resampled_interleaved_buffer_s16ne; + float *in_float = stm->context->in_resampled_interleaved_buffer_float; + + // unpaused, play audio + if (stm->devs == DUPLEX) { + if (stm->out_params.format == CUBEB_SAMPLE_S16NE) { + cbjack_interleave_capture(stm, bufs_in, nframes, true); + cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, bufs_out, nframes); + } else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) { + cbjack_interleave_capture(stm, bufs_in, nframes, false); + cbjack_deinterleave_playback_refill_float(stm, &in_float, bufs_out, nframes); + } + } else if (stm->devs == IN_ONLY) { + if (stm->in_params.format == CUBEB_SAMPLE_S16NE) { + cbjack_interleave_capture(stm, bufs_in, nframes, true); + cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, nullptr, nframes); + } else if (stm->in_params.format == CUBEB_SAMPLE_FLOAT32NE) { + cbjack_interleave_capture(stm, bufs_in, nframes, false); + cbjack_deinterleave_playback_refill_float(stm, &in_float, nullptr, nframes); + } + } else if (stm->devs == OUT_ONLY) { + if (stm->out_params.format == CUBEB_SAMPLE_S16NE) { + cbjack_deinterleave_playback_refill_s16ne(stm, nullptr, bufs_out, nframes); + } else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) { + cbjack_deinterleave_playback_refill_float(stm, nullptr, bufs_out, nframes); + } + } + // unlock stream mutex + pthread_mutex_unlock(&stm->mutex); + + } else { + // could not lock mutex + // output silence + if (stm->devs & OUT_ONLY) { + for (unsigned int c = 0; c < stm->out_params.channels; c++) { + float* buffer_out = bufs_out[c]; + for (long f = 0; f < nframes; f++) { + buffer_out[f] = 0.f; + } + } + } + if (stm->devs & IN_ONLY) { + // capture silence + for (unsigned int c = 0; c < stm->in_params.channels; c++) { + float* buffer_in = bufs_in[c]; + for (long f = 0; f < nframes; f++) { + buffer_in[f] = 0.f; + } + } + } + } + } + } + return 0; +} + + +static void +cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, float ** bufs_out, jack_nframes_t nframes) +{ + float * out_interleaved_buffer = nullptr; + + float * inptr = (in != NULL) ? *in : nullptr; + float * outptr = (bufs_out != NULL) ? *bufs_out : nullptr; + + long needed_frames = (bufs_out != NULL) ? nframes : 0; + long done_frames = 0; + long input_frames_count = (in != NULL) ? nframes : 0; + + + done_frames = cubeb_resampler_fill(stream->resampler, + inptr, + &input_frames_count, + (bufs_out != NULL) ? stream->context->out_resampled_interleaved_buffer_float : NULL, + needed_frames); + + out_interleaved_buffer = stream->context->out_resampled_interleaved_buffer_float; + + if (outptr) { + // convert interleaved output buffers to contiguous buffers + for (unsigned int c = 0; c < stream->out_params.channels; c++) { + float* buffer = bufs_out[c]; + for (long f = 0; f < done_frames; f++) { + buffer[f] = out_interleaved_buffer[(f * stream->out_params.channels) + c] * stream->volume; + } + if (done_frames < needed_frames) { + // draining + for (long f = done_frames; f < needed_frames; f++) { + buffer[f] = 0.f; + } + } + if (done_frames == 0) { + // stop, but first zero out the existing buffer + for (long f = 0; f < needed_frames; f++) { + buffer[f] = 0.f; + } + } + } + } + + if (done_frames >= 0 && done_frames < needed_frames) { + // set drained + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED); + // stop stream + cbjack_stream_stop(stream); + } + if (done_frames > 0 && done_frames <= needed_frames) { + // advance stream position + stream->position += done_frames * stream->ratio; + } + if (done_frames < 0 || done_frames > needed_frames) { + // stream error + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_ERROR); + } +} + +static void +cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in, float ** bufs_out, jack_nframes_t nframes) +{ + float * out_interleaved_buffer = nullptr; + + short * inptr = (in != NULL) ? *in : nullptr; + float * outptr = (bufs_out != NULL) ? *bufs_out : nullptr; + + long needed_frames = (bufs_out != NULL) ? nframes : 0; + long done_frames = 0; + long input_frames_count = (in != NULL) ? nframes : 0; + + done_frames = cubeb_resampler_fill(stream->resampler, + inptr, + &input_frames_count, + (bufs_out != NULL) ? stream->context->out_resampled_interleaved_buffer_s16ne : NULL, + needed_frames); + + s16ne_to_float(stream->context->out_resampled_interleaved_buffer_float, stream->context->out_resampled_interleaved_buffer_s16ne, done_frames * stream->out_params.channels); + + out_interleaved_buffer = stream->context->out_resampled_interleaved_buffer_float; + + if (outptr) { + // convert interleaved output buffers to contiguous buffers + for (unsigned int c = 0; c < stream->out_params.channels; c++) { + float* buffer = bufs_out[c]; + for (long f = 0; f < done_frames; f++) { + buffer[f] = out_interleaved_buffer[(f * stream->out_params.channels) + c] * stream->volume; + } + if (done_frames < needed_frames) { + // draining + for (long f = done_frames; f < needed_frames; f++) { + buffer[f] = 0.f; + } + } + if (done_frames == 0) { + // stop, but first zero out the existing buffer + for (long f = 0; f < needed_frames; f++) { + buffer[f] = 0.f; + } + } + } + } + + if (done_frames >= 0 && done_frames < needed_frames) { + // set drained + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED); + // stop stream + cbjack_stream_stop(stream); + } + if (done_frames > 0 && done_frames <= needed_frames) { + // advance stream position + stream->position += done_frames * stream->ratio; + } + if (done_frames < 0 || done_frames > needed_frames) { + // stream error + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_ERROR); + } +} + +static void +cbjack_interleave_capture(cubeb_stream * stream, float **in, jack_nframes_t nframes, bool format_mismatch) +{ + float *in_buffer = stream->context->in_float_interleaved_buffer; + + for (unsigned int c = 0; c < stream->in_params.channels; c++) { + for (long f = 0; f < nframes; f++) { + in_buffer[(f * stream->in_params.channels) + c] = in[c][f] * stream->volume; + } + } + if (format_mismatch) { + float_to_s16ne(stream->context->in_resampled_interleaved_buffer_s16ne, in_buffer, nframes * stream->in_params.channels); + } else { + memset(stream->context->in_resampled_interleaved_buffer_float, 0, (FIFO_SIZE * MAX_CHANNELS * 3) * sizeof(float)); + memcpy(stream->context->in_resampled_interleaved_buffer_float, in_buffer, (FIFO_SIZE * MAX_CHANNELS * 2) * sizeof(float)); + } +} + +/*static*/ int +jack_init (cubeb ** context, char const * context_name) +{ + int r; + + *context = NULL; + + cubeb * ctx = (cubeb *)calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return CUBEB_ERROR; + } + + r = load_jack_lib(ctx); + if (r != 0) { + cbjack_destroy(ctx); + return CUBEB_ERROR; + } + + ctx->ops = &cbjack_ops; + + ctx->mutex = PTHREAD_MUTEX_INITIALIZER; + for (r = 0; r < MAX_STREAMS; r++) { + ctx->streams[r].mutex = PTHREAD_MUTEX_INITIALIZER; + } + + const char * jack_client_name = "cubeb"; + if (context_name) + jack_client_name = context_name; + + ctx->jack_client = api_jack_client_open(jack_client_name, + JackNoStartServer, + NULL); + + if (ctx->jack_client == NULL) { + cbjack_destroy(ctx); + return CUBEB_ERROR; + } + + ctx->jack_xruns = 0; + + api_jack_set_process_callback (ctx->jack_client, cbjack_process, ctx); + api_jack_set_xrun_callback (ctx->jack_client, cbjack_xrun_callback, ctx); + api_jack_set_graph_order_callback (ctx->jack_client, cbjack_graph_order_callback, ctx); + + if (api_jack_activate (ctx->jack_client)) { + cbjack_destroy(ctx); + return CUBEB_ERROR; + } + + ctx->jack_sample_rate = api_jack_get_sample_rate(ctx->jack_client); + ctx->jack_latency = 128 * 1000 / ctx->jack_sample_rate; + + ctx->active = true; + *context = ctx; + + return CUBEB_OK; +} + +static char const * +cbjack_get_backend_id(cubeb * context) +{ + return "jack"; +} + +static int +cbjack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) +{ + *max_channels = MAX_CHANNELS; + return CUBEB_OK; +} + +static int +cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_ms) +{ + *latency_ms = stm->context->jack_latency; + return CUBEB_OK; +} + +static int +cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms) +{ + *latency_ms = ctx->jack_latency; + return CUBEB_OK; +} + +static int +cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) +{ + if (!ctx->jack_client) { + jack_client_t * testclient = api_jack_client_open("test-samplerate", + JackNoStartServer, + NULL); + if (!testclient) { + return CUBEB_ERROR; + } + + *rate = api_jack_get_sample_rate(testclient); + api_jack_client_close(testclient); + + } else { + *rate = api_jack_get_sample_rate(ctx->jack_client); + } + return CUBEB_OK; +} + +static void +cbjack_destroy(cubeb * context) +{ + context->active = false; + + if (context->jack_client != NULL) + api_jack_client_close (context->jack_client); + + if (context->libjack) + dlclose(context->libjack); + + free(context); +} + +static cubeb_stream * +context_alloc_stream(cubeb * context, char const * stream_name) +{ + for (int i = 0; i < MAX_STREAMS; i++) { + if (!context->streams[i].in_use) { + cubeb_stream * stm = &context->streams[i]; + stm->in_use = true; + snprintf(stm->stream_name, 255, "%s_%u", stream_name, i); + return stm; + } + } + return NULL; +} + +static int +cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void * user_ptr) +{ + int stream_actual_rate = 0; + int jack_rate = api_jack_get_sample_rate(context->jack_client); + + if (output_stream_params + && (output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE && + output_stream_params->format != CUBEB_SAMPLE_S16NE) + ) { + return CUBEB_ERROR_INVALID_FORMAT; + } + + if (input_stream_params + && (input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE && + input_stream_params->format != CUBEB_SAMPLE_S16NE) + ) { + return CUBEB_ERROR_INVALID_FORMAT; + } + + *stream = NULL; + + // Find a free stream. + pthread_mutex_lock(&context->mutex); + cubeb_stream * stm = context_alloc_stream(context, stream_name); + + // No free stream? + if (stm == NULL) { + pthread_mutex_unlock(&context->mutex); + return CUBEB_ERROR; + } + + // unlock context mutex + pthread_mutex_unlock(&context->mutex); + + // Lock active stream + pthread_mutex_lock(&stm->mutex); + + stm->ports_ready = false; + stm->user_ptr = user_ptr; + stm->context = context; + stm->devs = NONE; + if (output_stream_params && !input_stream_params) { + stm->out_params = *output_stream_params; + stream_actual_rate = stm->out_params.rate; + stm->out_params.rate = jack_rate; + stm->devs = OUT_ONLY; + if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) { + context->output_bytes_per_frame = sizeof(float); + } else { + context->output_bytes_per_frame = sizeof(short); + } + } + if (input_stream_params && output_stream_params) { + stm->in_params = *input_stream_params; + stm->out_params = *output_stream_params; + stream_actual_rate = stm->out_params.rate; + stm->in_params.rate = jack_rate; + stm->out_params.rate = jack_rate; + stm->devs = DUPLEX; + if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) { + context->output_bytes_per_frame = sizeof(float); + stm->in_params.format = CUBEB_SAMPLE_FLOAT32NE; + } else { + context->output_bytes_per_frame = sizeof(short); + stm->in_params.format = CUBEB_SAMPLE_S16NE; + } + } else if (input_stream_params && !output_stream_params) { + stm->in_params = *input_stream_params; + stream_actual_rate = stm->in_params.rate; + stm->in_params.rate = jack_rate; + stm->devs = IN_ONLY; + if (stm->in_params.format == CUBEB_SAMPLE_FLOAT32NE) { + context->output_bytes_per_frame = sizeof(float); + } else { + context->output_bytes_per_frame = sizeof(short); + } + } + + stm->ratio = (float)stream_actual_rate / (float)jack_rate; + + stm->data_callback = data_callback; + stm->state_callback = state_callback; + stm->position = 0; + stm->volume = 1.0f; + context->jack_buffer_size = api_jack_get_buffer_size(context->jack_client); + context->fragment_size = context->jack_buffer_size; + + if (stm->devs == NONE) { + pthread_mutex_unlock(&stm->mutex); + return CUBEB_ERROR; + } + + stm->resampler = NULL; + + if (stm->devs == DUPLEX) { + stm->resampler = cubeb_resampler_create(stm, + &stm->in_params, + &stm->out_params, + stream_actual_rate, + stm->data_callback, + stm->user_ptr, + CUBEB_RESAMPLER_QUALITY_DESKTOP); + } else if (stm->devs == IN_ONLY) { + stm->resampler = cubeb_resampler_create(stm, + &stm->in_params, + nullptr, + stream_actual_rate, + stm->data_callback, + stm->user_ptr, + CUBEB_RESAMPLER_QUALITY_DESKTOP); + } else if (stm->devs == OUT_ONLY) { + stm->resampler = cubeb_resampler_create(stm, + nullptr, + &stm->out_params, + stream_actual_rate, + stm->data_callback, + stm->user_ptr, + CUBEB_RESAMPLER_QUALITY_DESKTOP); + } + + if (!stm->resampler) { + stm->in_use = false; + pthread_mutex_unlock(&stm->mutex); + return CUBEB_ERROR; + } + + if (stm->devs == DUPLEX || stm->devs == OUT_ONLY) { + for (unsigned int c = 0; c < stm->out_params.channels; c++) { + char portname[256]; + snprintf(portname, 255, "%s_out_%d", stm->stream_name, c); + stm->output_ports[c] = api_jack_port_register(stm->context->jack_client, + portname, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, + 0); + } + } + + if (stm->devs == DUPLEX || stm->devs == IN_ONLY) { + for (unsigned int c = 0; c < stm->in_params.channels; c++) { + char portname[256]; + snprintf(portname, 255, "%s_in_%d", stm->stream_name, c); + stm->input_ports[c] = api_jack_port_register(stm->context->jack_client, + portname, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, + 0); + } + } + + cbjack_connect_ports(stm); + + *stream = stm; + + stm->ports_ready = true; + stm->pause = true; + pthread_mutex_unlock(&stm->mutex); + + return CUBEB_OK; +} + +static void +cbjack_stream_destroy(cubeb_stream * stream) +{ + pthread_mutex_lock(&stream->mutex); + stream->ports_ready = false; + + if (stream->devs == DUPLEX || stream->devs == OUT_ONLY) { + for (unsigned int c = 0; c < stream->out_params.channels; c++) { + if (stream->output_ports[c]) { + api_jack_port_unregister (stream->context->jack_client, stream->output_ports[c]); + stream->output_ports[c] = NULL; + } + } + } + + if (stream->devs == DUPLEX || stream->devs == IN_ONLY) { + for (unsigned int c = 0; c < stream->in_params.channels; c++) { + if (stream->input_ports[c]) { + api_jack_port_unregister (stream->context->jack_client, stream->input_ports[c]); + stream->input_ports[c] = NULL; + } + } + } + + if (stream->resampler) { + cubeb_resampler_destroy(stream->resampler); + stream->resampler = NULL; + } + stream->in_use = false; + pthread_mutex_unlock(&stream->mutex); +} + +static int +cbjack_stream_start(cubeb_stream * stream) +{ + stream->pause = false; + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED); + return CUBEB_OK; +} + +static int +cbjack_stream_stop(cubeb_stream * stream) +{ + stream->pause = true; + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED); + return CUBEB_OK; +} + +static int +cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position) +{ + *position = stream->position; + return CUBEB_OK; +} + +static int +cbjack_stream_set_volume(cubeb_stream * stm, float volume) +{ + stm->volume = volume; + return CUBEB_OK; +} + + +static int +cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device) +{ + *device = (cubeb_device *)calloc(1, sizeof(cubeb_device)); + if (*device == NULL) + return CUBEB_ERROR; + + const char * j_in = "JACK capture"; + const char * j_out = "JACK playback"; + const char * empty = ""; + + if (stm->devs == DUPLEX) { + (*device)->input_name = strdup(j_in); + (*device)->output_name = strdup(j_out); + } else if (stm->devs == IN_ONLY) { + (*device)->input_name = strdup(j_in); + (*device)->output_name = strdup(empty); + } else if (stm->devs == OUT_ONLY) { + (*device)->input_name = strdup(empty); + (*device)->output_name = strdup(j_out); + } + + return CUBEB_OK; +} + +static int +cbjack_stream_device_destroy(cubeb_stream * stream, + cubeb_device * device) +{ + if (device->input_name) + free(device->input_name); + if (device->output_name) + free(device->output_name); + free(device); + return CUBEB_OK; +} + +static int +cbjack_enumerate_devices(cubeb * context, cubeb_device_type type, + cubeb_device_collection ** collection) +{ + if (!context) + return CUBEB_ERROR; + + uint32_t rate; + uint8_t i = 0; + uint8_t j; + cbjack_get_preferred_sample_rate(context, &rate); + const char * j_in = "JACK capture"; + const char * j_out = "JACK playback"; + + if (type & CUBEB_DEVICE_TYPE_OUTPUT) { + context->devinfo[i] = (cubeb_device_info *)malloc(sizeof(cubeb_device_info)); + context->devinfo[i]->device_id = strdup(j_out); + context->devinfo[i]->devid = context->devinfo[i]->device_id; + context->devinfo[i]->friendly_name = strdup(j_out); + context->devinfo[i]->group_id = strdup(j_out); + context->devinfo[i]->vendor_name = strdup(j_out); + context->devinfo[i]->type = CUBEB_DEVICE_TYPE_OUTPUT; + context->devinfo[i]->state = CUBEB_DEVICE_STATE_ENABLED; + context->devinfo[i]->preferred = CUBEB_DEVICE_PREF_ALL; + context->devinfo[i]->format = CUBEB_DEVICE_FMT_F32NE; + context->devinfo[i]->default_format = CUBEB_DEVICE_FMT_F32NE; + context->devinfo[i]->max_channels = MAX_CHANNELS; + context->devinfo[i]->min_rate = rate; + context->devinfo[i]->max_rate = rate; + context->devinfo[i]->default_rate = rate; + context->devinfo[i]->latency_lo_ms = 1; + context->devinfo[i]->latency_hi_ms = 10; + i++; + } + + if (type & CUBEB_DEVICE_TYPE_INPUT) { + context->devinfo[i] = (cubeb_device_info *)malloc(sizeof(cubeb_device_info)); + context->devinfo[i]->device_id = strdup(j_in); + context->devinfo[i]->devid = context->devinfo[i]->device_id; + context->devinfo[i]->friendly_name = strdup(j_in); + context->devinfo[i]->group_id = strdup(j_in); + context->devinfo[i]->vendor_name = strdup(j_in); + context->devinfo[i]->type = CUBEB_DEVICE_TYPE_INPUT; + context->devinfo[i]->state = CUBEB_DEVICE_STATE_ENABLED; + context->devinfo[i]->preferred = CUBEB_DEVICE_PREF_ALL; + context->devinfo[i]->format = CUBEB_DEVICE_FMT_F32NE; + context->devinfo[i]->default_format = CUBEB_DEVICE_FMT_F32NE; + context->devinfo[i]->max_channels = MAX_CHANNELS; + context->devinfo[i]->min_rate = rate; + context->devinfo[i]->max_rate = rate; + context->devinfo[i]->default_rate = rate; + context->devinfo[i]->latency_lo_ms = 1; + context->devinfo[i]->latency_hi_ms = 10; + i++; + } + + *collection = (cubeb_device_collection *) + malloc(sizeof(cubeb_device_collection) + + i * sizeof(cubeb_device_info *)); + + (*collection)->count = i; + + for (j = 0; j < i; j++) { + (*collection)->device[j] = context->devinfo[j]; + } + return CUBEB_OK; +} diff --git a/media/libcubeb/src/cubeb_resampler.cpp b/media/libcubeb/src/cubeb_resampler.cpp index 215749dd35..9bccc78db2 100644 --- a/media/libcubeb/src/cubeb_resampler.cpp +++ b/media/libcubeb/src/cubeb_resampler.cpp @@ -37,12 +37,16 @@ to_speex_quality(cubeb_resampler_quality q) long noop_resampler::fill(void * input_buffer, long * input_frames_count, void * output_buffer, long output_frames) { + if (input_buffer) { + assert(input_frames_count); + } assert((input_buffer && output_buffer && *input_frames_count >= output_frames) || (!input_buffer && (!input_frames_count || *input_frames_count == 0)) || (!output_buffer && output_frames == 0)); if (output_buffer == nullptr) { + assert(input_buffer); output_frames = *input_frames_count; } @@ -131,6 +135,10 @@ cubeb_resampler_speex nullptr, out_unprocessed, output_frames_before_processing); + if (got < 0) { + return got; + } + output_processor->written(got); /* Process the output. If not enough frames have been returned from the @@ -156,8 +164,13 @@ cubeb_resampler_speex input_processor->input(input_buffer, *input_frames_count); resampled_input = input_processor->output(resampled_frame_count); - return data_callback(stream, user_ptr, - resampled_input, nullptr, resampled_frame_count); + long got = data_callback(stream, user_ptr, + resampled_input, nullptr, resampled_frame_count); + + /* Return the number of initial input frames or part of it. + * Since output_frames_needed == 0 in input scenario, the only + * available number outside resampler is the initial number of frames. */ + return (*input_frames_count) * (got / resampled_frame_count); } @@ -206,6 +219,10 @@ cubeb_resampler_speex resampled_input, out_unprocessed, output_frames_before_processing); + if (got < 0) { + return got; + } + output_processor->written(got); /* Process the output. If not enough frames have been returned from the diff --git a/media/libcubeb/src/cubeb_resampler_internal.h b/media/libcubeb/src/cubeb_resampler_internal.h index 8f8d6dcc74..3a6aaca757 100644 --- a/media/libcubeb/src/cubeb_resampler_internal.h +++ b/media/libcubeb/src/cubeb_resampler_internal.h @@ -215,8 +215,8 @@ public: size_t output_for_input(uint32_t input_frames) { - return ceilf(input_frames * resampling_ratio) + 1 - - resampling_in_buffer.length() / channels; + return size_t(ceilf(input_frames / resampling_ratio) + - resampling_in_buffer.length() / channels); } /** Returns a buffer containing exactly `output_frame_count` resampled frames. @@ -263,8 +263,8 @@ public: * number of output frames will be exactly equal. */ uint32_t input_needed_for_output(uint32_t output_frame_count) { - return ceilf(output_frame_count * resampling_ratio) + 1 - - samples_to_frames(resampling_in_buffer.length()); + return uint32_t(ceilf(output_frame_count * resampling_ratio) + 1 + - samples_to_frames(resampling_in_buffer.length())); } /** Returns a pointer to the input buffer, that contains empty space for at diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp index 207cc07b4d..0ddf170f44 100644 --- a/media/libcubeb/src/cubeb_wasapi.cpp +++ b/media/libcubeb/src/cubeb_wasapi.cpp @@ -122,7 +122,7 @@ public: owner = GetCurrentThreadId(); #endif } - + void leave() { #ifdef DEBUG @@ -587,9 +587,7 @@ bool get_input_buffer(cubeb_stream * stm) /* Get input packets until we have captured enough frames, and put them in a * contiguous buffer. */ uint32_t offset = 0; - uint32_t input_channel_count = stm->input_mix_params.channels; - while (offset != total_available_input * input_channel_count && - total_available_input) { + while (offset != total_available_input) { hr = stm->capture_client->GetNextPacketSize(&next); if (FAILED(hr)) { LOG("cannot get next packet size: %x\n", hr); @@ -622,7 +620,7 @@ bool get_input_buffer(cubeb_stream * stm) assert(ok); upmix(reinterpret_cast(input_packet), packet_size, stm->linear_input_buffer.data() + stm->linear_input_buffer.length(), - input_channel_count, + stm->input_mix_params.channels, stm->input_stream_params.channels); stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels); } else if (should_downmix(stm->input_mix_params, stm->input_stream_params)) { @@ -631,7 +629,7 @@ bool get_input_buffer(cubeb_stream * stm) assert(ok); downmix(reinterpret_cast(input_packet), packet_size, stm->linear_input_buffer.data() + stm->linear_input_buffer.length(), - input_channel_count, + stm->input_mix_params.channels, stm->input_stream_params.channels); stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels); } else { @@ -644,7 +642,7 @@ bool get_input_buffer(cubeb_stream * stm) LOG("FAILED to release intput buffer"); return false; } - offset += packet_size * input_channel_count; + offset += packet_size; } assert(stm->linear_input_buffer.length() >= total_available_input && @@ -698,8 +696,8 @@ bool refill_callback_duplex(cubeb_stream * stm) { HRESULT hr; - float * output_buffer; - size_t output_frames; + float * output_buffer = nullptr; + size_t output_frames = 0; size_t input_frames; bool rv; @@ -731,7 +729,7 @@ refill_callback_duplex(cubeb_stream * stm) double output_duration = double(output_frames) / stm->output_mix_params.rate; double input_duration = double(input_frames) / stm->input_mix_params.rate; if (input_duration < output_duration) { - size_t padding = round((output_duration - input_duration) * stm->input_mix_params.rate); + size_t padding = size_t(round((output_duration - input_duration) * stm->input_mix_params.rate)); LOG("padding silence: out=%f in=%f pad=%u\n", output_duration, input_duration, padding); stm->linear_input_buffer.push_front_silence(padding * stm->input_stream_params.channels); } @@ -782,8 +780,8 @@ refill_callback_output(cubeb_stream * stm) { bool rv; HRESULT hr; - float * output_buffer; - size_t output_frames; + float * output_buffer = nullptr; + size_t output_frames = 0; XASSERT(!has_input(stm) && has_output(stm)); @@ -792,11 +790,11 @@ refill_callback_output(cubeb_stream * stm) if (!rv) { return rv; } + if (stm->draining || output_frames == 0) { return true; } - long got = refill(stm, nullptr, 0, @@ -869,6 +867,7 @@ wasapi_stream_render_loop(LPVOID stream) continue; } case WAIT_OBJECT_0 + 1: { /* reconfigure */ + XASSERT(stm->output_client || stm->input_client); /* Close the stream */ if (stm->output_client) { stm->output_client->Stop(); @@ -890,6 +889,7 @@ wasapi_stream_render_loop(LPVOID stream) continue; } } + XASSERT(stm->output_client || stm->input_client); if (stm->output_client) { stm->output_client->Start(); } @@ -1531,7 +1531,7 @@ int setup_wasapi_stream(cubeb_stream * stm) return CUBEB_ERROR; } - XASSERT(!stm->output_client && "WASAPI stream already setup, close it first."); + XASSERT((!stm->output_client || !stm->input_client) && "WASAPI stream already setup, close it first."); if (has_input(stm)) { LOG("Setup capture: device=%x\n", (int)stm->input_device); @@ -1730,10 +1730,12 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream, void close_wasapi_stream(cubeb_stream * stm) { - XASSERT(stm); + XASSERT(stm && !stm->thread && !stm->shutdown_event); stm->stream_reset_lock->assert_current_thread_owns(); + XASSERT(stm->output_client || stm->input_client); + SafeRelease(stm->output_client); stm->output_client = NULL; SafeRelease(stm->input_client); @@ -1809,8 +1811,8 @@ int stream_start_one_side(cubeb_stream * stm, StreamDirection dir) return r; } - HRESULT hr = dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start(); - if (FAILED(hr)) { + HRESULT hr2 = dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start(); + if (FAILED(hr2)) { LOG("could not start the %s stream after reconfig: %x\n", dir == OUTPUT ? "output" : "input", hr); return CUBEB_ERROR; @@ -1826,20 +1828,20 @@ int stream_start_one_side(cubeb_stream * stm, StreamDirection dir) int wasapi_stream_start(cubeb_stream * stm) { - int rv; + XASSERT(stm && !stm->thread && !stm->shutdown_event); + XASSERT(stm->output_client || stm->input_client); + auto_lock lock(stm->stream_reset_lock); - XASSERT(stm && !stm->thread && !stm->shutdown_event); - if (stm->output_client) { - rv = stream_start_one_side(stm, OUTPUT); + int rv = stream_start_one_side(stm, OUTPUT); if (rv != CUBEB_OK) { return rv; } } if (stm->input_client) { - rv = stream_start_one_side(stm, INPUT); + int rv = stream_start_one_side(stm, INPUT); if (rv != CUBEB_OK) { return rv; } @@ -1851,7 +1853,7 @@ int wasapi_stream_start(cubeb_stream * stm) return CUBEB_ERROR; } - stm->thread = (HANDLE) _beginthreadex(NULL, 256 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); + stm->thread = (HANDLE) _beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); if (stm->thread == NULL) { LOG("could not create WASAPI render thread.\n"); return CUBEB_ERROR; @@ -1867,6 +1869,8 @@ int wasapi_stream_stop(cubeb_stream * stm) XASSERT(stm); HRESULT hr; + XASSERT(stm->output_client || stm->input_client); + { auto_lock lock(stm->stream_reset_lock); @@ -2200,6 +2204,9 @@ wasapi_enumerate_devices(cubeb * context, cubeb_device_type type, } *out = (cubeb_device_collection *) malloc(sizeof(cubeb_device_collection) + sizeof(cubeb_device_info*) * (cc > 0 ? cc - 1 : 0)); + if (!*out) { + return CUBEB_ERROR; + } (*out)->count = 0; for (i = 0; i < cc; i++) { hr = collection->Item(i, &dev); diff --git a/media/libcubeb/src/cubeb_winmm.c b/media/libcubeb/src/cubeb_winmm.c index f8d0d0251c..c6a61086b9 100644 --- a/media/libcubeb/src/cubeb_winmm.c +++ b/media/libcubeb/src/cubeb_winmm.c @@ -211,7 +211,7 @@ winmm_refill_stream(cubeb_stream * stm) short * b = (short *) hdr->lpData; uint32_t i; for (i = 0; i < got * stm->params.channels; i++) { - b[i] *= stm->soft_volume; + b[i] = (short) (b[i] * stm->soft_volume); } } } @@ -402,7 +402,11 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n XASSERT(context); XASSERT(stream); - XASSERT(!input_stream_params && "not supported."); + if (input_stream_params) { + /* Capture support not yet implemented. */ + return CUBEB_ERROR_NOT_SUPPORTED; + } + if (input_device || output_device) { /* Device selection not yet implemented. */ return CUBEB_ERROR_DEVICE_UNAVAILABLE; @@ -848,7 +852,10 @@ static char * guid_to_cstr(LPGUID guid) { char * ret = malloc(sizeof(char) * 40); - _snprintf(ret, sizeof(char) * 40, + if (!ret) { + return NULL; + } + _snprintf_s(ret, sizeof(char) * 40, _TRUNCATE, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", guid->Data1, guid->Data2, guid->Data3, guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], @@ -879,7 +886,10 @@ static char * device_id_idx(UINT devid) { char * ret = (char *)malloc(sizeof(char)*16); - _snprintf(ret, 16, "%u", devid); + if (!ret) { + return NULL; + } + _snprintf_s(ret, 16, _TRUNCATE, "%u", devid); return ret; } @@ -889,6 +899,9 @@ winmm_create_device_from_outcaps2(LPWAVEOUTCAPS2A caps, UINT devid) cubeb_device_info * ret; ret = calloc(1, sizeof(cubeb_device_info)); + if (!ret) { + return NULL; + } ret->devid = (cubeb_devid)(size_t)devid; ret->device_id = device_id_idx(devid); ret->friendly_name = _strdup(caps->szPname); @@ -917,6 +930,9 @@ winmm_create_device_from_outcaps(LPWAVEOUTCAPSA caps, UINT devid) cubeb_device_info * ret; ret = calloc(1, sizeof(cubeb_device_info)); + if (!ret) { + return NULL; + } ret->devid = (cubeb_devid)(size_t)devid; ret->device_id = device_id_idx(devid); ret->friendly_name = _strdup(caps->szPname); @@ -964,6 +980,9 @@ winmm_create_device_from_incaps2(LPWAVEINCAPS2A caps, UINT devid) cubeb_device_info * ret; ret = calloc(1, sizeof(cubeb_device_info)); + if (!ret) { + return NULL; + } ret->devid = (cubeb_devid)(size_t)devid; ret->device_id = device_id_idx(devid); ret->friendly_name = _strdup(caps->szPname); @@ -992,6 +1011,9 @@ winmm_create_device_from_incaps(LPWAVEINCAPSA caps, UINT devid) cubeb_device_info * ret; ret = calloc(1, sizeof(cubeb_device_info)); + if (!ret) { + return NULL; + } ret->devid = (cubeb_devid)(size_t)devid; ret->device_id = device_id_idx(devid); ret->friendly_name = _strdup(caps->szPname); diff --git a/media/libcubeb/tests/test_latency.cpp b/media/libcubeb/tests/test_latency.cpp index 5b4da8e7da..5e58f87292 100644 --- a/media/libcubeb/tests/test_latency.cpp +++ b/media/libcubeb/tests/test_latency.cpp @@ -2,7 +2,7 @@ #undef NDEBUG #endif #include -#include +#include "cubeb/cubeb.h" #include #include #ifdef CUBEB_GECKO_BUILD diff --git a/media/libcubeb/tests/test_sanity.cpp b/media/libcubeb/tests/test_sanity.cpp index b09a4f9556..c6bd9de795 100644 --- a/media/libcubeb/tests/test_sanity.cpp +++ b/media/libcubeb/tests/test_sanity.cpp @@ -43,7 +43,6 @@ static long test_data_callback(cubeb_stream * stm, void * user_ptr, const void * inputbuffer, void * outputbuffer, long nframes) { assert(stm && user_ptr == &dummy && outputbuffer && nframes > 0); - memset(outputbuffer, 0, nframes * sizeof(short)); #if (defined(_WIN32) || defined(__WIN32__)) memset(outputbuffer, 0, nframes * sizeof(float)); #else @@ -125,6 +124,9 @@ test_context_variables(void) params.channels = STREAM_CHANNELS; params.format = STREAM_FORMAT; params.rate = STREAM_RATE; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif r = cubeb_get_min_latency(ctx, params, &value); assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED); if (r == CUBEB_OK) { @@ -158,6 +160,9 @@ test_init_destroy_stream(void) params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_data_callback, test_state_callback, &dummy); @@ -186,6 +191,9 @@ test_init_destroy_multiple_streams(void) params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif for (i = 0; i < ARRAY_LENGTH(stream); ++i) { r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, @@ -219,6 +227,9 @@ test_configure_stream(void) params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = 2; // panning +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_data_callback, test_state_callback, &dummy); @@ -252,6 +263,9 @@ test_init_start_stop_destroy_multiple_streams(int early, int delay_ms) params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif for (i = 0; i < ARRAY_LENGTH(stream); ++i) { r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, @@ -312,6 +326,9 @@ test_init_destroy_multiple_contexts_and_streams(void) params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif for (i = 0; i < ARRAY_LENGTH(ctx); ++i) { r = cubeb_init(&ctx[i], "test_sanity"); @@ -352,6 +369,9 @@ test_basic_stream_operations(void) params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_data_callback, test_state_callback, &dummy); @@ -401,6 +421,9 @@ test_stream_position(void) params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_data_callback, test_state_callback, &dummy); @@ -484,7 +507,11 @@ test_drain_data_callback(cubeb_stream * stm, void * user_ptr, const void * input } /* once drain has started, callback must never be called again */ assert(do_drain != 2); +#if (defined(_WIN32) || defined(__WIN32__)) + memset(outputbuffer, 0, nframes * sizeof(float)); +#else memset(outputbuffer, 0, nframes * sizeof(short)); +#endif total_frames_written += nframes; return nframes; } @@ -517,6 +544,9 @@ test_drain(void) params.format = STREAM_FORMAT; params.rate = STREAM_RATE; params.channels = STREAM_CHANNELS; +#if defined(__ANDROID__) + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; +#endif r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, ¶ms, STREAM_LATENCY, test_drain_data_callback, test_drain_state_callback, &dummy); diff --git a/media/libcubeb/update.sh b/media/libcubeb/update.sh index f51f40cc0e..726d806fc5 100755 --- a/media/libcubeb/update.sh +++ b/media/libcubeb/update.sh @@ -1,40 +1,41 @@ # Usage: sh update.sh set -e -cp $1/include/cubeb/cubeb.h include -cp $1/src/cubeb.c src -cp $1/src/cubeb-internal.h src -cp $1/src/cubeb_alsa.c src -cp $1/src/cubeb_winmm.c src -cp $1/src/cubeb_audiounit.c src -cp $1/src/cubeb_pulse.c src -cp $1/src/cubeb_sndio.c src -cp $1/src/cubeb_opensl.c src -cp $1/src/cubeb_audiotrack.c src -cp $1/src/cubeb_wasapi.cpp src -cp $1/src/cubeb_resampler.h src -cp $1/src/cubeb_resampler_internal.h src -cp $1/src/cubeb_resampler.cpp src -cp $1/src/cubeb-speex-resampler.h src -cp $1/src/cubeb_panner.h src -cp $1/src/cubeb_panner.cpp src -cp $1/src/cubeb_utils.h src -cp $1/src/cubeb_ring_array.h src -cp $1/src/android/audiotrack_definitions.h src/android -cp $1/src/android/sles_definitions.h src/android +cp $1/AUTHORS . cp $1/LICENSE . cp $1/README.md . -cp $1/AUTHORS . +cp $1/include/cubeb/cubeb.h include +cp $1/src/android/audiotrack_definitions.h src/android +cp $1/src/android/sles_definitions.h src/android +cp $1/src/cubeb-internal.h src +cp $1/src/cubeb-speex-resampler.h src +cp $1/src/cubeb.c src +cp $1/src/cubeb_alsa.c src +cp $1/src/cubeb_audiotrack.c src +cp $1/src/cubeb_audiounit.c src +cp $1/src/cubeb_jack.cpp src +cp $1/src/cubeb_opensl.c src +cp $1/src/cubeb_panner.cpp src +cp $1/src/cubeb_panner.h src +cp $1/src/cubeb_pulse.c src +cp $1/src/cubeb_resampler.cpp src +cp $1/src/cubeb_resampler.h src +cp $1/src/cubeb_resampler_internal.h src +cp $1/src/cubeb_ring_array.h src +cp $1/src/cubeb_sndio.c src +cp $1/src/cubeb_utils.h src +cp $1/src/cubeb_wasapi.cpp src +cp $1/src/cubeb_winmm.c src cp $1/test/common.h tests/common.h cp $1/test/test_audio.cpp tests/test_audio.cpp -cp $1/test/test_tone.cpp tests/test_tone.cpp -cp $1/test/test_sanity.cpp tests/test_sanity.cpp -cp $1/test/test_latency.cpp tests/test_latency.cpp -cp $1/test/test_resampler.cpp tests/test_resampler.cpp -cp $1/test/test_duplex.cpp tests/test_duplex.cpp -cp $1/test/test_record.cpp tests/test_record.cpp -cp $1/test/test_utils.cpp tests/test_utils.cpp #cp $1/test/test_devices.c tests/test_devices.cpp +cp $1/test/test_duplex.cpp tests/test_duplex.cpp +cp $1/test/test_latency.cpp tests/test_latency.cpp +cp $1/test/test_record.cpp tests/test_record.cpp +cp $1/test/test_resampler.cpp tests/test_resampler.cpp +cp $1/test/test_sanity.cpp tests/test_sanity.cpp +cp $1/test/test_tone.cpp tests/test_tone.cpp +cp $1/test/test_utils.cpp tests/test_utils.cpp if [ -d $1/.git ]; then rev=$(cd $1 && git rev-parse --verify HEAD) diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp index d0e76c601d..686ba0c30a 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp @@ -4,7 +4,6 @@ #include "CSFLog.h" -#include "base/histogram.h" #include "PeerConnectionImpl.h" #include "PeerConnectionCtx.h" #include "runnable_utils.h" @@ -356,9 +355,6 @@ nsresult PeerConnectionCtx::Initialize() { initGMP(); #if !defined(MOZILLA_EXTERNAL_LINKAGE) - mConnectionCounter = 0; - Telemetry::GetHistogramById(Telemetry::WEBRTC_CALL_COUNT)->Add(0); - mTelemetryTimer = do_CreateInstance(NS_TIMER_CONTRACTID); MOZ_ASSERT(mTelemetryTimer); nsresult rv = mTelemetryTimer->SetTarget(gMainThread); diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h index d8be3b837e..3f7d6250b0 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h @@ -80,9 +80,6 @@ class PeerConnectionCtx { EverySecondTelemetryCallback_m(nsITimer* timer, void *); #if !defined(MOZILLA_EXTERNAL_LINKAGE) - // Telemetry Peer conection counter - int mConnectionCounter; - nsCOMPtr mTelemetryTimer; public: diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp index 097057ec75..c9366c8cd5 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp @@ -9,7 +9,6 @@ #include #include -#include "base/histogram.h" #include "CSFLog.h" #include "timecard.h" @@ -3905,13 +3904,8 @@ PeerConnectionImpl::startCallTelem() { mStartTime = TimeStamp::Now(); // Increment session call counter - // If we want to track Loop calls independently here, we need two mConnectionCounters - int &cnt = PeerConnectionCtx::GetInstance()->mConnectionCounter; - if (cnt > 0) { - Telemetry::GetHistogramById(Telemetry::WEBRTC_CALL_COUNT)->Subtract(cnt); - } - cnt++; - Telemetry::GetHistogramById(Telemetry::WEBRTC_CALL_COUNT)->Add(cnt); + // If we want to track Loop calls independently here, we need two histograms. + Telemetry::Accumulate(Telemetry::WEBRTC_CALL_COUNT_2, 1); } #endif diff --git a/mfbt/Attributes.h b/mfbt/Attributes.h index 43a9f674bf..58a5a92f76 100644 --- a/mfbt/Attributes.h +++ b/mfbt/Attributes.h @@ -527,6 +527,12 @@ * declarations where an instance of the template should be considered, for * static analysis purposes, to inherit any type annotations (such as * MOZ_MUST_USE_TYPE and MOZ_STACK_CLASS) from its template arguments. + * MOZ_IGNORE_INITIALIZATION: Applies to class member declarations. Occasionally + * there are class members that are not initialized in the constructor, but logic + * elsewhere in the class ensures they are initialized prior to use. Using this + * attribute on a member disables the check that this member must be initialized + * in constructors via list-initialization, in the constructor body, or via functions + * called from the constructor body. * MOZ_NON_AUTOABLE: Applies to class declarations. Makes it a compile time error to * use `auto` in place of this type in variable declarations. This is intended to * be used with types which are intended to be implicitly constructed into other @@ -560,6 +566,8 @@ # define MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS \ __attribute__((annotate("moz_inherit_type_annotations_from_template_args"))) # define MOZ_NON_AUTOABLE __attribute__((annotate("moz_non_autoable"))) +# define MOZ_INITIALIZED_OUTSIDE_CONSTRUCTOR \ + __attribute__((annotate("moz_ignore_ctor_initialization"))) /* * It turns out that clang doesn't like void func() __attribute__ {} without a * warning, so use pragmas to disable the warning. This code won't work on GCC @@ -591,6 +599,7 @@ # define MOZ_NEEDS_MEMMOVABLE_TYPE /* nothing */ # define MOZ_NEEDS_MEMMOVABLE_MEMBERS /* nothing */ # define MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS /* nothing */ +# define MOZ_INITIALIZED_OUTSIDE_CONSTRUCTOR /* nothing */ # define MOZ_NON_AUTOABLE /* nothing */ #endif /* MOZ_CLANG_PLUGIN */ diff --git a/mfbt/Compiler.h b/mfbt/Compiler.h index 643d559da1..1bd34d329c 100644 --- a/mfbt/Compiler.h +++ b/mfbt/Compiler.h @@ -17,12 +17,15 @@ # undef MOZ_IS_GCC # define MOZ_IS_GCC 1 /* - * This macro should simplify gcc version checking. For example, to check + * These macros should simplify gcc version checking. For example, to check * for gcc 4.7.1 or later, check `#if MOZ_GCC_VERSION_AT_LEAST(4, 7, 1)`. */ # define MOZ_GCC_VERSION_AT_LEAST(major, minor, patchlevel) \ ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) \ >= ((major) * 10000 + (minor) * 100 + (patchlevel))) +# define MOZ_GCC_VERSION_AT_MOST(major, minor, patchlevel) \ + ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) \ + <= ((major) * 10000 + (minor) * 100 + (patchlevel))) # if !MOZ_GCC_VERSION_AT_LEAST(4, 8, 0) # error "mfbt (and Gecko) require at least gcc 4.8 to build." # endif diff --git a/mfbt/EnumeratedArray.h b/mfbt/EnumeratedArray.h index afd06ea805..7468d9e0c5 100644 --- a/mfbt/EnumeratedArray.h +++ b/mfbt/EnumeratedArray.h @@ -10,6 +10,7 @@ #define mozilla_EnumeratedArray_h #include "mozilla/Array.h" +#include "mozilla/Move.h" namespace mozilla { @@ -60,6 +61,13 @@ public: } } + EnumeratedArray(EnumeratedArray&& aOther) + { + for (size_t i = 0; i < kSize; i++) { + mArray[i] = Move(aOther.mArray[i]); + } + } + ValueType& operator[](IndexType aIndex) { return mArray[size_t(aIndex)]; diff --git a/mfbt/RefPtr.h b/mfbt/RefPtr.h index d17266f6c6..607db6045e 100644 --- a/mfbt/RefPtr.h +++ b/mfbt/RefPtr.h @@ -381,10 +381,10 @@ private: struct ConstRemovingRefPtrTraits { static void AddRef(const U* aPtr) { - mozilla::RefPtrTraits::Type>::AddRef(const_cast(aPtr)); + mozilla::RefPtrTraits::AddRef(const_cast(aPtr)); } static void Release(const U* aPtr) { - mozilla::RefPtrTraits::Type>::Release(const_cast(aPtr)); + mozilla::RefPtrTraits::Release(const_cast(aPtr)); } }; }; diff --git a/security/sandbox/chromium/base/win/windows_version.cc b/security/sandbox/chromium/base/win/windows_version.cc index a99cd1840a..fc2def3919 100644 --- a/security/sandbox/chromium/base/win/windows_version.cc +++ b/security/sandbox/chromium/base/win/windows_version.cc @@ -64,6 +64,8 @@ OSInfo::OSInfo() version_ = VERSION_WIN8_1; break; } + } else if (version_number_.major == 10) { + version_ = VERSION_WIN10; } else if (version_number_.major > 6) { NOTREACHED(); version_ = VERSION_WIN_LAST; @@ -84,7 +86,7 @@ OSInfo::OSInfo() GetProductInfoPtr get_product_info; DWORD os_type; - if (version_info.dwMajorVersion == 6) { + if (version_info.dwMajorVersion == 6 || version_info.dwMajorVersion == 10) { // Only present on Vista+. get_product_info = reinterpret_cast( ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "GetProductInfo")); diff --git a/security/sandbox/chromium/base/win/windows_version.h b/security/sandbox/chromium/base/win/windows_version.h index e51840b0db..a52e64e074 100644 --- a/security/sandbox/chromium/base/win/windows_version.h +++ b/security/sandbox/chromium/base/win/windows_version.h @@ -26,7 +26,8 @@ enum Version { VERSION_VISTA, // Also includes Windows Server 2008. VERSION_WIN7, // Also includes Windows Server 2008 R2. VERSION_WIN8, // Also includes Windows Server 2012. - VERSION_WIN8_1, // Code named Windows Blue + VERSION_WIN8_1, // Also includes Windows Server 2012 R2. + VERSION_WIN10, // Also includes Windows 10 Server. VERSION_WIN_LAST, // Indicates error condition. }; diff --git a/security/sandbox/moz-chromium-commit-status.txt b/security/sandbox/moz-chromium-commit-status.txt index 360f78b415..14161eec7a 100644 --- a/security/sandbox/moz-chromium-commit-status.txt +++ b/security/sandbox/moz-chromium-commit-status.txt @@ -9,3 +9,5 @@ de2078cfbbb6770791d32575a1a72a288e6d66a6 chromium/sandbox/win/src/target_servi 0e49d029d5a1a25d971880b9e44d67ac70b31a80 chromium/sandbox/win/src/filesystem_policy.cc 0e49d029d5a1a25d971880b9e44d67ac70b31a80 chromium/sandbox/win/src/win_utils.cc 0e49d029d5a1a25d971880b9e44d67ac70b31a80 chromium/sandbox/win/src/win_utils.h +3181ba39ee787e1b40f4aea4be23f4f666ad0945 chromium/base/win/windows_version.cc +3181ba39ee787e1b40f4aea4be23f4f666ad0945 chromium/base/win/windows_version.h diff --git a/testing/taskcluster/tasks/branches/base_jobs.yml b/testing/taskcluster/tasks/branches/base_jobs.yml index 7d983dc6ee..982288c662 100644 --- a/testing/taskcluster/tasks/branches/base_jobs.yml +++ b/testing/taskcluster/tasks/branches/base_jobs.yml @@ -6,14 +6,6 @@ $inherits: from: tasks/branches/base_job_flags.yml builds: - linux64_gecko: - platforms: - - b2g - types: - opt: - task: tasks/builds/b2g_desktop_opt.yml - debug: - task: tasks/builds/b2g_desktop_debug.yml linux64-mulet: platforms: - Mulet Linux @@ -28,6 +20,14 @@ builds: task: tasks/builds/b2g_emulator_kk_opt.yml debug: task: tasks/builds/b2g_emulator_kk_debug.yml + emulator-x86-kk: + platfoms: + - b2g + types: + opt: + task: tasks/builds/b2g_emulator_x86_kk_opt.yml + debug: + task: tasks/builds/b2g_emulator_x86_kk_debug.yml emulator-l: platfoms: - b2g @@ -36,22 +36,14 @@ builds: task: tasks/builds/b2g_emulator_l_opt.yml debug: task: tasks/builds/b2g_emulator_l_debug.yml - emulator-jb: + linux: platfoms: - - b2g + - Linux types: opt: - task: tasks/builds/b2g_emulator_jb_opt.yml + task: tasks/builds/opt_linux32.yml debug: - task: tasks/builds/b2g_emulator_jb_debug.yml - emulator: - platfoms: - - b2g - types: - opt: - task: tasks/builds/b2g_emulator_ics_opt.yml - debug: - task: tasks/builds/b2g_emulator_ics_debug.yml + task: tasks/builds/dbg_linux32.yml aries: platforms: - b2g diff --git a/testing/taskcluster/tasks/builds/sm_nonunified.yml b/testing/taskcluster/tasks/builds/sm_nonunified.yml new file mode 100644 index 0000000000..f2ee4967ec --- /dev/null +++ b/testing/taskcluster/tasks/builds/sm_nonunified.yml @@ -0,0 +1,17 @@ +$inherits: + from: 'tasks/builds/sm_base.yml' + variables: + build_name: 'sm-nonunified' + build_type: 'debug' +task: + payload: + env: + SPIDERMONKEY_VARIANT: 'nonunified' + metadata: + name: '[TC] Spidermonkey Non-Unified Debug' + description: 'Spidermonkey Non-Unified Debug' + extra: + treeherder: + symbol: nu + collection: + debug: true diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 9a45ca3d13..2115e8e77c 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -6624,12 +6624,19 @@ "description": "The length of time (in seconds) that a call lasted." }, "WEBRTC_CALL_COUNT": { - "expires_in_version": "never", + "expires_in_version": "48", "kind": "exponential", "high": 500, "n_buckets": 50, "description": "The number of calls made during a session." }, + "WEBRTC_CALL_COUNT_2": { + "alert_emails": ["webrtc-telemetry-alerts@mozilla.com"], + "bug_numbers": [1261063], + "expires_in_version": "never", + "kind": "count", + "description": "The number of calls made during a session." + }, "WEBRTC_ICE_ADD_CANDIDATE_ERRORS_GIVEN_SUCCESS": { "alert_emails": ["webrtc-ice-telemetry-alerts@mozilla.com"], "expires_in_version": "53", diff --git a/toolkit/components/telemetry/Telemetry.cpp b/toolkit/components/telemetry/Telemetry.cpp index b5d4012f67..451210405f 100644 --- a/toolkit/components/telemetry/Telemetry.cpp +++ b/toolkit/components/telemetry/Telemetry.cpp @@ -18,7 +18,6 @@ #include "mozilla/MathAlgorithms.h" #include "mozilla/unused.h" -#include "base/histogram.h" #include "base/pickle.h" #include "nsIComponentManager.h" #include "nsIServiceManager.h" @@ -43,6 +42,7 @@ #include "nsISeekableStream.h" #include "Telemetry.h" #include "TelemetryCommon.h" +#include "TelemetryHistogram.h" #include "WebrtcTelemetry.h" #include "nsTHashtable.h" #include "nsHashKeys.h" @@ -75,42 +75,16 @@ #include "shared-libraries.h" #endif -#define EXPIRED_ID "__expired__" - namespace { using namespace mozilla; using namespace mozilla::HangMonitor; -using base::BooleanHistogram; -using base::CountHistogram; -using base::FlagHistogram; -using base::Histogram; -using base::LinearHistogram; -using base::StatisticsRecorder; - // The maximum number of chrome hangs stacks that we're keeping. const size_t kMaxChromeStacksKept = 50; // The maximum depth of a single chrome hang stack. const size_t kMaxChromeStackDepth = 50; -#define KEYED_HISTOGRAM_NAME_SEPARATOR "#" -#define SUBSESSION_HISTOGRAM_PREFIX "sub#" - -enum reflectStatus { - REFLECT_OK, - REFLECT_CORRUPT, - REFLECT_FAILURE -}; - -nsresult -HistogramGet(const char *name, const char *expiration, uint32_t histogramType, - uint32_t min, uint32_t max, uint32_t bucketCount, bool haveOptArgs, - Histogram **result); - -enum reflectStatus -ReflectHistogramSnapshot(JSContext *cx, JS::Handle obj, Histogram *h); - // This class is conceptually a list of ProcessedStack objects, but it represents them // more efficiently by keeping a single global list of modules. class CombinedStacks { @@ -690,8 +664,6 @@ ClearIOReporting() sTelemetryIOObserver = nullptr; } -class KeyedHistogram; - class TelemetryImpl final : public nsITelemetry , public nsIMemoryReporter @@ -703,9 +675,6 @@ class TelemetryImpl final public: void InitMemoryReporter(); - static bool IsInitialized(); - static bool CanRecordBase(); - static bool CanRecordExtended(); static already_AddRefed CreateTelemetryInstance(); static void ShutdownTelemetry(); static void RecordSlowStatement(const nsACString &sql, const nsACString &dbName, @@ -718,7 +687,6 @@ public: HangAnnotationsPtr aAnnotations); #endif static void RecordThreadHangStats(Telemetry::ThreadHangStats& aStats); - static nsresult GetHistogramEnumId(const char *name, Telemetry::ID *id); size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); struct Stat { uint32_t hitCount; @@ -730,8 +698,6 @@ public: }; typedef nsBaseHashtableET SlowSQLEntryType; - static KeyedHistogram* GetKeyedHistogramById(const nsACString &id); - static void RecordIceCandidates(const uint32_t iceCandidateBitmask, const bool success, const bool loop); @@ -758,41 +724,8 @@ private: bool GetSQLStats(JSContext *cx, JS::MutableHandle ret, bool includePrivateSql); - // Like GetHistogramById, but returns the underlying C++ object, not the JS one. - nsresult GetHistogramByName(const nsACString &name, Histogram **ret); - bool ShouldReflectHistogram(Histogram *h); - void IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs); - nsresult CreateHistogramSnapshots(JSContext *cx, - JS::MutableHandle ret, - bool subsession, - bool clearSubsession); - typedef StatisticsRecorder::Histograms::iterator HistogramIterator; - - struct AddonHistogramInfo { - uint32_t min; - uint32_t max; - uint32_t bucketCount; - uint32_t histogramType; - Histogram *h; - }; - typedef nsBaseHashtableET AddonHistogramEntryType; - typedef AutoHashtable AddonHistogramMapType; - typedef nsBaseHashtableET AddonEntryType; - typedef AutoHashtable AddonMapType; - static bool AddonHistogramReflector(AddonHistogramEntryType *entry, - JSContext *cx, JS::Handle obj); - static bool AddonReflector(AddonEntryType *entry, JSContext *cx, JS::Handle obj); - static bool CreateHistogramForAddon(const nsACString &name, - AddonHistogramInfo &info); void ReadLateWritesStacks(nsIFile* aProfileDir); - AddonMapType mAddonMap; - // This is used for speedy string->Telemetry::ID conversions - typedef nsBaseHashtableET CharPtrEntryType; - typedef AutoHashtable HistogramMapType; - HistogramMapType mHistogramMap; - bool mCanRecordBase; - bool mCanRecordExtended; static TelemetryImpl *sTelemetry; AutoHashtable mPrivateSQL; AutoHashtable mSanitizedSQL; @@ -811,9 +744,6 @@ private: friend class nsFetchTelemetryData; WebrtcTelemetry mWebrtcTelemetry; - - typedef nsClassHashtable KeyedHistogramMapType; - KeyedHistogramMapType mKeyedHistograms; }; TelemetryImpl* TelemetryImpl::sTelemetry = nullptr; @@ -830,845 +760,10 @@ TelemetryImpl::CollectReports(nsIHandleReportCallback* aHandleReport, "Memory used by the telemetry system."); } -class KeyedHistogram { -public: - KeyedHistogram(const nsACString &name, const nsACString &expiration, - uint32_t histogramType, uint32_t min, uint32_t max, - uint32_t bucketCount, uint32_t dataset); - nsresult GetHistogram(const nsCString& name, Histogram** histogram, bool subsession); - Histogram* GetHistogram(const nsCString& name, bool subsession); - uint32_t GetHistogramType() const { return mHistogramType; } - nsresult GetDataset(uint32_t* dataset) const; - nsresult GetJSKeys(JSContext* cx, JS::CallArgs& args); - nsresult GetJSSnapshot(JSContext* cx, JS::Handle obj, - bool subsession, bool clearSubsession); - - void SetRecordingEnabled(bool aEnabled) { mRecordingEnabled = aEnabled; }; - bool IsRecordingEnabled() const { return mRecordingEnabled; }; - - nsresult Add(const nsCString& key, uint32_t aSample); - void Clear(bool subsession); - -private: - typedef nsBaseHashtableET KeyedHistogramEntry; - typedef AutoHashtable KeyedHistogramMapType; - KeyedHistogramMapType mHistogramMap; -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - KeyedHistogramMapType mSubsessionMap; -#endif - - static bool ReflectKeyedHistogram(KeyedHistogramEntry* entry, - JSContext* cx, - JS::Handle obj); - - const nsCString mName; - const nsCString mExpiration; - const uint32_t mHistogramType; - const uint32_t mMin; - const uint32_t mMax; - const uint32_t mBucketCount; - const uint32_t mDataset; - mozilla::Atomic mRecordingEnabled; -}; - -// Hardcoded probes -struct TelemetryHistogram { - uint32_t min; - uint32_t max; - uint32_t bucketCount; - uint32_t histogramType; - uint32_t id_offset; - uint32_t expiration_offset; - uint32_t dataset; - bool keyed; - - const char *id() const; - const char *expiration() const; -}; - -#include "TelemetryHistogramData.inc" -bool gCorruptHistograms[Telemetry::HistogramCount]; - -const char * -TelemetryHistogram::id() const -{ - return &gHistogramStringTable[this->id_offset]; -} - -const char * -TelemetryHistogram::expiration() const -{ - return &gHistogramStringTable[this->expiration_offset]; -} - -bool -IsHistogramEnumId(Telemetry::ID aID) -{ - static_assert(((Telemetry::ID)-1 > 0), "ID should be unsigned."); - return aID < Telemetry::HistogramCount; -} - -// List of histogram IDs which should have recording disabled initially. -const Telemetry::ID kRecordingInitiallyDisabledIDs[] = { - Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, - - // The array must not be empty. Leave these item here. - Telemetry::TELEMETRY_TEST_COUNT_INIT_NO_RECORD, - Telemetry::TELEMETRY_TEST_KEYED_COUNT_INIT_NO_RECORD -}; - void InitHistogramRecordingEnabled() { - const size_t length = mozilla::ArrayLength(kRecordingInitiallyDisabledIDs); - for (size_t i = 0; i < length; i++) { - SetHistogramRecordingEnabled(kRecordingInitiallyDisabledIDs[i], false); - } -} - -bool -IsExpired(const char *expiration){ - static Version current_version = Version(MOZ_APP_VERSION); - MOZ_ASSERT(expiration); - return strcmp(expiration, "never") && strcmp(expiration, "default") && - (mozilla::Version(expiration) <= current_version); -} - -bool -IsExpired(const Histogram *histogram){ - return histogram->histogram_name() == EXPIRED_ID; -} - -bool -IsValidHistogramName(const nsACString& name) -{ - return !FindInReadable(NS_LITERAL_CSTRING(KEYED_HISTOGRAM_NAME_SEPARATOR), name); -} - -bool -IsInDataset(uint32_t dataset, uint32_t containingDataset) -{ - if (dataset == containingDataset) { - return true; - } - - // The "optin on release channel" dataset is a superset of the - // "optout on release channel one". - if (containingDataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN - && dataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT) { - return true; - } - - return false; -} - -bool -CanRecordDataset(uint32_t dataset) -{ - // If we are extended telemetry is enabled, we are allowed to record regardless of - // the dataset. - if (TelemetryImpl::CanRecordExtended()) { - return true; - } - - // If base telemetry data is enabled and we're trying to record base telemetry, allow it. - if (TelemetryImpl::CanRecordBase() && - IsInDataset(dataset, nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT)) { - return true; - } - - // We're not recording extended telemetry or this is not the base dataset. Bail out. - return false; -} - -nsresult -CheckHistogramArguments(uint32_t histogramType, uint32_t min, uint32_t max, - uint32_t bucketCount, bool haveOptArgs) -{ - if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN - && histogramType != nsITelemetry::HISTOGRAM_FLAG - && histogramType != nsITelemetry::HISTOGRAM_COUNT) { - // The min, max & bucketCount arguments are not optional for this type. - if (!haveOptArgs) - return NS_ERROR_ILLEGAL_VALUE; - - // Sanity checks for histogram parameters. - if (min >= max) - return NS_ERROR_ILLEGAL_VALUE; - - if (bucketCount <= 2) - return NS_ERROR_ILLEGAL_VALUE; - - if (min < 1) - return NS_ERROR_ILLEGAL_VALUE; - } - - return NS_OK; -} - -/* - * min, max & bucketCount are optional for boolean, flag & count histograms. - * haveOptArgs has to be set if the caller provides them. - */ -nsresult -HistogramGet(const char *name, const char *expiration, uint32_t histogramType, - uint32_t min, uint32_t max, uint32_t bucketCount, bool haveOptArgs, - Histogram **result) -{ - nsresult rv = CheckHistogramArguments(histogramType, min, max, bucketCount, haveOptArgs); - if (NS_FAILED(rv)) { - return rv; - } - - if (IsExpired(expiration)) { - name = EXPIRED_ID; - min = 1; - max = 2; - bucketCount = 3; - histogramType = nsITelemetry::HISTOGRAM_LINEAR; - } - - switch (histogramType) { - case nsITelemetry::HISTOGRAM_EXPONENTIAL: - *result = Histogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag); - break; - case nsITelemetry::HISTOGRAM_LINEAR: - *result = LinearHistogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag); - break; - case nsITelemetry::HISTOGRAM_BOOLEAN: - *result = BooleanHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag); - break; - case nsITelemetry::HISTOGRAM_FLAG: - *result = FlagHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag); - break; - case nsITelemetry::HISTOGRAM_COUNT: - *result = CountHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag); - break; - default: - NS_ASSERTION(false, "Invalid histogram type"); - return NS_ERROR_INVALID_ARG; - } - return NS_OK; -} - -// O(1) histogram lookup by numeric id -nsresult -GetHistogramByEnumId(Telemetry::ID id, Histogram **ret) -{ - static Histogram* knownHistograms[Telemetry::HistogramCount] = {0}; - Histogram *h = knownHistograms[id]; - if (h) { - *ret = h; - return NS_OK; - } - - const TelemetryHistogram &p = gHistograms[id]; - if (p.keyed) { - return NS_ERROR_FAILURE; - } - - nsresult rv = HistogramGet(p.id(), p.expiration(), p.histogramType, - p.min, p.max, p.bucketCount, true, &h); - if (NS_FAILED(rv)) - return rv; - -#ifdef DEBUG - // Check that the C++ Histogram code computes the same ranges as the - // Python histogram code. - if (!IsExpired(p.expiration())) { - const struct bounds &b = gBucketLowerBoundIndex[id]; - if (b.length != 0) { - MOZ_ASSERT(size_t(b.length) == h->bucket_count(), - "C++/Python bucket # mismatch"); - for (int i = 0; i < b.length; ++i) { - MOZ_ASSERT(gBucketLowerBounds[b.offset + i] == h->ranges(i), - "C++/Python bucket mismatch"); - } - } - } -#endif - - *ret = knownHistograms[id] = h; - return NS_OK; -} - -/** - * This clones a histogram |existing| with the id |existingId| to a - * new histogram with the name |newName|. - * For simplicity this is limited to registered histograms. - */ -Histogram* -CloneHistogram(const nsACString& newName, Telemetry::ID existingId, - Histogram& existing) -{ - const TelemetryHistogram &info = gHistograms[existingId]; - Histogram *clone = nullptr; - nsresult rv; - - rv = HistogramGet(PromiseFlatCString(newName).get(), info.expiration(), - info.histogramType, existing.declared_min(), - existing.declared_max(), existing.bucket_count(), - true, &clone); - if (NS_FAILED(rv)) { - return nullptr; - } - - Histogram::SampleSet ss; - existing.SnapshotSample(&ss); - clone->AddSampleSet(ss); - - return clone; -} - -/** - * This clones a histogram with the id |existingId| to a new histogram - * with the name |newName|. - * For simplicity this is limited to registered histograms. - */ -Histogram* -CloneHistogram(const nsACString& newName, Telemetry::ID existingId) -{ - Histogram *existing = nullptr; - nsresult rv = GetHistogramByEnumId(existingId, &existing); - if (NS_FAILED(rv)) { - return nullptr; - } - - return CloneHistogram(newName, existingId, *existing); -} - -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) -Histogram* -GetSubsessionHistogram(Histogram& existing) -{ - Telemetry::ID id; - nsresult rv = TelemetryImpl::GetHistogramEnumId(existing.histogram_name().c_str(), &id); - if (NS_FAILED(rv) || gHistograms[id].keyed) { - return nullptr; - } - - static Histogram* subsession[Telemetry::HistogramCount] = {}; - if (subsession[id]) { - return subsession[id]; - } - - NS_NAMED_LITERAL_CSTRING(prefix, SUBSESSION_HISTOGRAM_PREFIX); - nsDependentCString existingName(gHistograms[id].id()); - if (StringBeginsWith(existingName, prefix)) { - return nullptr; - } - - nsCString subsessionName(prefix); - subsessionName.Append(existingName); - - subsession[id] = CloneHistogram(subsessionName, id, existing); - return subsession[id]; -} -#endif - -nsresult -HistogramAdd(Histogram& histogram, int32_t value, uint32_t dataset) -{ - // Check if we are allowed to record the data. - if (!CanRecordDataset(dataset) || !histogram.IsRecordingEnabled()) { - return NS_OK; - } - -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - if (Histogram* subsession = GetSubsessionHistogram(histogram)) { - subsession->Add(value); - } -#endif - - // It is safe to add to the histogram now: the subsession histogram was already - // cloned from this so we won't add the sample twice. - histogram.Add(value); - - return NS_OK; -} - -nsresult -HistogramAdd(Histogram& histogram, int32_t value) -{ - uint32_t dataset = nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN; - // We only really care about the dataset of the histogram if we are not recording - // extended telemetry. Otherwise, we always record histogram data. - if (!TelemetryImpl::CanRecordExtended()) { - Telemetry::ID id; - nsresult rv = TelemetryImpl::GetHistogramEnumId(histogram.histogram_name().c_str(), &id); - if (NS_FAILED(rv)) { - // If we can't look up the dataset, it might be because the histogram was added - // at runtime. Since we're not recording extended telemetry, bail out. - return NS_OK; - } - dataset = gHistograms[id].dataset; - } - - return HistogramAdd(histogram, value, dataset); -} - -bool -FillRanges(JSContext *cx, JS::Handle array, Histogram *h) -{ - JS::Rooted range(cx); - for (size_t i = 0; i < h->bucket_count(); i++) { - range.setInt32(h->ranges(i)); - if (!JS_DefineElement(cx, array, i, range, JSPROP_ENUMERATE)) - return false; - } - return true; -} - -enum reflectStatus -ReflectHistogramAndSamples(JSContext *cx, JS::Handle obj, Histogram *h, - const Histogram::SampleSet &ss) -{ - mozilla::OffTheBooksMutexAutoLock locker(ss.mutex()); - - // We don't want to reflect corrupt histograms. - if (h->FindCorruption(ss, locker) != Histogram::NO_INCONSISTENCIES) { - return REFLECT_CORRUPT; - } - - if (!(JS_DefineProperty(cx, obj, "min", - h->declared_min(), JSPROP_ENUMERATE) - && JS_DefineProperty(cx, obj, "max", - h->declared_max(), JSPROP_ENUMERATE) - && JS_DefineProperty(cx, obj, "histogram_type", - h->histogram_type(), JSPROP_ENUMERATE) - && JS_DefineProperty(cx, obj, "sum", - double(ss.sum(locker)), JSPROP_ENUMERATE))) { - return REFLECT_FAILURE; - } - - const size_t count = h->bucket_count(); - JS::Rooted rarray(cx, JS_NewArrayObject(cx, count)); - if (!rarray) { - return REFLECT_FAILURE; - } - if (!(FillRanges(cx, rarray, h) - && JS_DefineProperty(cx, obj, "ranges", rarray, JSPROP_ENUMERATE))) { - return REFLECT_FAILURE; - } - - JS::Rooted counts_array(cx, JS_NewArrayObject(cx, count)); - if (!counts_array) { - return REFLECT_FAILURE; - } - if (!JS_DefineProperty(cx, obj, "counts", counts_array, JSPROP_ENUMERATE)) { - return REFLECT_FAILURE; - } - for (size_t i = 0; i < count; i++) { - if (!JS_DefineElement(cx, counts_array, i, - ss.counts(locker, i), JSPROP_ENUMERATE)) { - return REFLECT_FAILURE; - } - } - - return REFLECT_OK; -} - -enum reflectStatus -ReflectHistogramSnapshot(JSContext *cx, JS::Handle obj, Histogram *h) -{ - Histogram::SampleSet ss; - h->SnapshotSample(&ss); - return ReflectHistogramAndSamples(cx, obj, h, ss); -} - -bool -IsEmpty(const Histogram *h) -{ - Histogram::SampleSet ss; - h->SnapshotSample(&ss); - - mozilla::OffTheBooksMutexAutoLock locker(ss.mutex()); - return ss.counts(locker, 0) == 0 && ss.sum(locker) == 0; -} - -bool -JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp) -{ - JSObject *obj = JS_THIS_OBJECT(cx, vp); - if (!obj) { - return false; - } - - Histogram *h = static_cast(JS_GetPrivate(obj)); - MOZ_ASSERT(h); - Histogram::ClassType type = h->histogram_type(); - - int32_t value = 1; - if (type != base::CountHistogram::COUNT_HISTOGRAM) { - JS::CallArgs args = CallArgsFromVp(argc, vp); - if (!args.length()) { - JS_ReportError(cx, "Expected one argument"); - return false; - } - - if (!(args[0].isNumber() || args[0].isBoolean())) { - JS_ReportError(cx, "Not a number"); - return false; - } - - if (!JS::ToInt32(cx, args[0], &value)) { - return false; - } - } - - if (TelemetryImpl::CanRecordBase()) { - HistogramAdd(*h, value); - } - - return true; -} - -bool -JSHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp) -{ - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - JSObject *obj = JS_THIS_OBJECT(cx, vp); - if (!obj) { - return false; - } - - Histogram *h = static_cast(JS_GetPrivate(obj)); - JS::Rooted snapshot(cx, JS_NewPlainObject(cx)); - if (!snapshot) - return false; - - switch (ReflectHistogramSnapshot(cx, snapshot, h)) { - case REFLECT_FAILURE: - return false; - case REFLECT_CORRUPT: - JS_ReportError(cx, "Histogram is corrupt"); - return false; - case REFLECT_OK: - args.rval().setObject(*snapshot); - return true; - default: - MOZ_CRASH("unhandled reflection status"); - } -} - -bool -JSHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp) -{ - JSObject *obj = JS_THIS_OBJECT(cx, vp); - if (!obj) { - return false; - } - - bool onlySubsession = false; -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - if (args.length() >= 1) { - if (!args[0].isBoolean()) { - JS_ReportError(cx, "Not a boolean"); - return false; - } - - onlySubsession = JS::ToBoolean(args[0]); - } -#endif - - Histogram *h = static_cast(JS_GetPrivate(obj)); - MOZ_ASSERT(h); - if(!onlySubsession) { - h->Clear(); - } - -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - if (Histogram* subsession = GetSubsessionHistogram(*h)) { - subsession->Clear(); - } -#endif - - return true; -} - -bool -JSHistogram_Dataset(JSContext *cx, unsigned argc, JS::Value *vp) -{ - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - JSObject *obj = JS_THIS_OBJECT(cx, vp); - if (!obj) { - return false; - } - - Histogram *h = static_cast(JS_GetPrivate(obj)); - Telemetry::ID id; - nsresult rv = TelemetryImpl::GetHistogramEnumId(h->histogram_name().c_str(), &id); - if (NS_SUCCEEDED(rv)) { - args.rval().setNumber(gHistograms[id].dataset); - return true; - } - - return false; -} - -nsresult -WrapAndReturnHistogram(Histogram *h, JSContext *cx, JS::MutableHandle ret) -{ - static const JSClass JSHistogram_class = { - "JSHistogram", /* name */ - JSCLASS_HAS_PRIVATE /* flags */ - }; - - JS::Rooted obj(cx, JS_NewObject(cx, &JSHistogram_class)); - if (!obj) - return NS_ERROR_FAILURE; - if (!(JS_DefineFunction(cx, obj, "add", JSHistogram_Add, 1, 0) - && JS_DefineFunction(cx, obj, "snapshot", JSHistogram_Snapshot, 0, 0) - && JS_DefineFunction(cx, obj, "clear", JSHistogram_Clear, 0, 0) - && JS_DefineFunction(cx, obj, "dataset", JSHistogram_Dataset, 0, 0))) { - return NS_ERROR_FAILURE; - } - JS_SetPrivate(obj, h); - ret.setObject(*obj); - return NS_OK; -} - -bool -JSKeyedHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp) -{ - JSObject *obj = JS_THIS_OBJECT(cx, vp); - if (!obj) { - return false; - } - - KeyedHistogram* keyed = static_cast(JS_GetPrivate(obj)); - if (!keyed) { - return false; - } - - JS::CallArgs args = CallArgsFromVp(argc, vp); - if (args.length() < 1) { - JS_ReportError(cx, "Expected one argument"); - return false; - } - - nsAutoJSString key; - if (!args[0].isString() || !key.init(cx, args[0])) { - JS_ReportError(cx, "Not a string"); - return false; - } - - const uint32_t type = keyed->GetHistogramType(); - int32_t value = 1; - - if (type != base::CountHistogram::COUNT_HISTOGRAM) { - if (args.length() < 2) { - JS_ReportError(cx, "Expected two arguments for this histogram type"); - return false; - } - - if (!(args[1].isNumber() || args[1].isBoolean())) { - JS_ReportError(cx, "Not a number"); - return false; - } - - if (!JS::ToInt32(cx, args[1], &value)) { - return false; - } - } - - keyed->Add(NS_ConvertUTF16toUTF8(key), value); - return true; -} - -bool -JSKeyedHistogram_Keys(JSContext *cx, unsigned argc, JS::Value *vp) -{ - JSObject *obj = JS_THIS_OBJECT(cx, vp); - if (!obj) { - return false; - } - - KeyedHistogram* keyed = static_cast(JS_GetPrivate(obj)); - if (!keyed) { - return false; - } - - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - return NS_SUCCEEDED(keyed->GetJSKeys(cx, args)); -} - -bool -KeyedHistogram_SnapshotImpl(JSContext *cx, unsigned argc, JS::Value *vp, - bool subsession, bool clearSubsession) -{ - JSObject *obj = JS_THIS_OBJECT(cx, vp); - if (!obj) { - return false; - } - - KeyedHistogram* keyed = static_cast(JS_GetPrivate(obj)); - if (!keyed) { - return false; - } - - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - if (args.length() == 0) { - JS::RootedObject snapshot(cx, JS_NewPlainObject(cx)); - if (!snapshot) { - JS_ReportError(cx, "Failed to create object"); - return false; - } - - if (!NS_SUCCEEDED(keyed->GetJSSnapshot(cx, snapshot, subsession, clearSubsession))) { - JS_ReportError(cx, "Failed to reflect keyed histograms"); - return false; - } - - args.rval().setObject(*snapshot); - return true; - } - - nsAutoJSString key; - if (!args[0].isString() || !key.init(cx, args[0])) { - JS_ReportError(cx, "Not a string"); - return false; - } - - Histogram* h = nullptr; - nsresult rv = keyed->GetHistogram(NS_ConvertUTF16toUTF8(key), &h, subsession); - if (NS_FAILED(rv)) { - JS_ReportError(cx, "Failed to get histogram"); - return false; - } - - JS::RootedObject snapshot(cx, JS_NewPlainObject(cx)); - if (!snapshot) { - return false; - } - - switch (ReflectHistogramSnapshot(cx, snapshot, h)) { - case REFLECT_FAILURE: - return false; - case REFLECT_CORRUPT: - JS_ReportError(cx, "Histogram is corrupt"); - return false; - case REFLECT_OK: - args.rval().setObject(*snapshot); - return true; - default: - MOZ_CRASH("unhandled reflection status"); - } -} - -bool -JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp) -{ - return KeyedHistogram_SnapshotImpl(cx, argc, vp, false, false); -} - -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) -bool -JSKeyedHistogram_SubsessionSnapshot(JSContext *cx, unsigned argc, JS::Value *vp) -{ - return KeyedHistogram_SnapshotImpl(cx, argc, vp, true, false); -} -#endif - -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) -bool -JSKeyedHistogram_SnapshotSubsessionAndClear(JSContext *cx, unsigned argc, JS::Value *vp) -{ - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - if (args.length() != 0) { - JS_ReportError(cx, "No key arguments supported for snapshotSubsessionAndClear"); - } - - return KeyedHistogram_SnapshotImpl(cx, argc, vp, true, true); -} -#endif - -bool -JSKeyedHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp) -{ - JSObject *obj = JS_THIS_OBJECT(cx, vp); - if (!obj) { - return false; - } - - KeyedHistogram* keyed = static_cast(JS_GetPrivate(obj)); - if (!keyed) { - return false; - } - -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - bool onlySubsession = false; - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - if (args.length() >= 1) { - if (!(args[0].isNumber() || args[0].isBoolean())) { - JS_ReportError(cx, "Not a boolean"); - return false; - } - - onlySubsession = JS::ToBoolean(args[0]); - } - - keyed->Clear(onlySubsession); -#else - keyed->Clear(false); -#endif - return true; -} - -bool -JSKeyedHistogram_Dataset(JSContext *cx, unsigned argc, JS::Value *vp) -{ - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - JSObject *obj = JS_THIS_OBJECT(cx, vp); - if (!obj) { - return false; - } - - KeyedHistogram* keyed = static_cast(JS_GetPrivate(obj)); - if (!keyed) { - return false; - } - - uint32_t dataset = nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN; - nsresult rv = keyed->GetDataset(&dataset);; - if (NS_FAILED(rv)) { - return false; - } - - args.rval().setNumber(dataset); - return true; -} - -nsresult -WrapAndReturnKeyedHistogram(KeyedHistogram *h, JSContext *cx, JS::MutableHandle ret) -{ - static const JSClass JSHistogram_class = { - "JSKeyedHistogram", /* name */ - JSCLASS_HAS_PRIVATE /* flags */ - }; - - JS::Rooted obj(cx, JS_NewObject(cx, &JSHistogram_class)); - if (!obj) - return NS_ERROR_FAILURE; - if (!(JS_DefineFunction(cx, obj, "add", JSKeyedHistogram_Add, 2, 0) - && JS_DefineFunction(cx, obj, "snapshot", JSKeyedHistogram_Snapshot, 1, 0) -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - && JS_DefineFunction(cx, obj, "subsessionSnapshot", JSKeyedHistogram_SubsessionSnapshot, 1, 0) - && JS_DefineFunction(cx, obj, "snapshotSubsessionAndClear", JSKeyedHistogram_SnapshotSubsessionAndClear, 0, 0) -#endif - && JS_DefineFunction(cx, obj, "keys", JSKeyedHistogram_Keys, 0, 0) - && JS_DefineFunction(cx, obj, "clear", JSKeyedHistogram_Clear, 0, 0) - && JS_DefineFunction(cx, obj, "dataset", JSKeyedHistogram_Dataset, 0, 0))) { - return NS_ERROR_FAILURE; - } - - JS_SetPrivate(obj, h); - ret.setObject(*obj); - return NS_OK; + TelemetryHistogram::InitHistogramRecordingEnabled(); } static uint32_t @@ -1909,62 +1004,17 @@ TelemetryImpl::AsyncFetchTelemetryData(nsIFetchTelemetryDataCallback *aCallback) return NS_OK; } -TelemetryImpl::TelemetryImpl(): -mHistogramMap(Telemetry::HistogramCount), -mCanRecordBase(XRE_IsParentProcess() || XRE_IsContentProcess()), -mCanRecordExtended(XRE_IsParentProcess() || XRE_IsContentProcess()), -mHashMutex("Telemetry::mHashMutex"), -mHangReportsMutex("Telemetry::mHangReportsMutex"), -mThreadHangStatsMutex("Telemetry::mThreadHangStatsMutex"), -mCachedTelemetryData(false), -mLastShutdownTime(0), -mFailedLockCount(0) +TelemetryImpl::TelemetryImpl() + : mHashMutex("Telemetry::mHashMutex") + , mHangReportsMutex("Telemetry::mHangReportsMutex") + , mThreadHangStatsMutex("Telemetry::mThreadHangStatsMutex") + , mCachedTelemetryData(false) + , mLastShutdownTime(0) + , mFailedLockCount(0) { - // Populate the static histogram name->id cache. - // Note that the histogram names are statically allocated. - for (uint32_t i = 0; i < Telemetry::HistogramCount; i++) { - CharPtrEntryType *entry = mHistogramMap.PutEntry(gHistograms[i].id()); - entry->mData = (Telemetry::ID) i; - } - -#ifdef DEBUG - mHistogramMap.MarkImmutable(); -#endif - - // Create registered keyed histograms - for (size_t i = 0; i < ArrayLength(gHistograms); ++i) { - const TelemetryHistogram& h = gHistograms[i]; - if (!h.keyed) { - continue; - } - - const nsDependentCString id(h.id()); - const nsDependentCString expiration(h.expiration()); - mKeyedHistograms.Put(id, new KeyedHistogram(id, expiration, h.histogramType, - h.min, h.max, h.bucketCount, h.dataset)); - } - - // Setup of the initial recording-enabled state (for not-recording-by-default - // histograms) using InitHistogramRecordingEnabled() will happen after instantiating - // sTelemetry since it depends on the static GetKeyedHistogramById(...) - which - // uses the singleton instance at sTelemetry. - - // Some Telemetry histograms depend on the value of C++ constants and hardcode - // their values in Histograms.json. - // We add static asserts here for those values to match so that future changes - // don't go unnoticed. - // TODO: Compare explicitly with gHistograms[].bucketCount here - // once we can make gHistograms constexpr (requires VS2015). - /*static_assert((JS::gcreason::NUM_TELEMETRY_REASONS == 100), - "NUM_TELEMETRY_REASONS is assumed to be a fixed value in Histograms.json." - " If this was an intentional change, update this assert with its value " - "and update the n_values for the following in Histograms.json: " - "GC_MINOR_REASON, GC_MINOR_REASON_LONG, GC_REASON_2");*/ - static_assert((mozilla::StartupTimeline::MAX_EVENT_ID == 16), - "MAX_EVENT_ID is assumed to be a fixed value in Histograms.json. If this" - " was an intentional change, update this assert with its value and update" - " the n_values for the following in Histograms.json:" - " STARTUP_MEASUREMENT_ERRORS"); + // We expect TelemetryHistogram::InitializeGlobalState() to have been + // called before we get to this point. + MOZ_ASSERT(TelemetryHistogram::GlobalStateHasBeenInitialized()); } TelemetryImpl::~TelemetryImpl() { @@ -1981,17 +1031,9 @@ TelemetryImpl::NewHistogram(const nsACString &name, const nsACString &expiration uint32_t min, uint32_t max, uint32_t bucketCount, JSContext *cx, uint8_t optArgCount, JS::MutableHandle ret) { - if (!IsValidHistogramName(name)) { - return NS_ERROR_INVALID_ARG; - } - - Histogram *h; - nsresult rv = HistogramGet(PromiseFlatCString(name).get(), PromiseFlatCString(expiration).get(), - histogramType, min, max, bucketCount, optArgCount == 3, &h); - if (NS_FAILED(rv)) - return rv; - h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); - return WrapAndReturnHistogram(h, cx, ret); + return TelemetryHistogram::NewHistogram(name, expiration, histogramType, + min, max, bucketCount, + cx, optArgCount, ret); } NS_IMETHODIMP @@ -1999,24 +1041,9 @@ TelemetryImpl::NewKeyedHistogram(const nsACString &name, const nsACString &expir uint32_t min, uint32_t max, uint32_t bucketCount, JSContext *cx, uint8_t optArgCount, JS::MutableHandle ret) { - if (!IsValidHistogramName(name)) { - return NS_ERROR_INVALID_ARG; - } - - nsresult rv = CheckHistogramArguments(histogramType, min, max, bucketCount, optArgCount == 3); - if (NS_FAILED(rv)) { - return rv; - } - - KeyedHistogram* keyed = new KeyedHistogram(name, expiration, histogramType, - min, max, bucketCount, - nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN); - if (MOZ_UNLIKELY(!mKeyedHistograms.Put(name, keyed, fallible))) { - delete keyed; - return NS_ERROR_OUT_OF_MEMORY; - } - - return WrapAndReturnKeyedHistogram(keyed, cx, ret); + return TelemetryHistogram::NewKeyedHistogram(name, expiration, histogramType, + min, max, bucketCount, + cx, optArgCount, ret); } @@ -2076,136 +1103,11 @@ TelemetryImpl::AddSQLInfo(JSContext *cx, JS::Handle rootObj, bool mai statsObj, JSPROP_ENUMERATE); } -nsresult -TelemetryImpl::GetHistogramEnumId(const char *name, Telemetry::ID *id) -{ - if (!sTelemetry) { - return NS_ERROR_FAILURE; - } - - const TelemetryImpl::HistogramMapType& map = sTelemetry->mHistogramMap; - CharPtrEntryType *entry = map.GetEntry(name); - if (!entry) { - return NS_ERROR_INVALID_ARG; - } - *id = entry->mData; - return NS_OK; -} - -nsresult -TelemetryImpl::GetHistogramByName(const nsACString &name, Histogram **ret) -{ - Telemetry::ID id; - nsresult rv = GetHistogramEnumId(PromiseFlatCString(name).get(), &id); - if (NS_FAILED(rv)) { - return rv; - } - - rv = GetHistogramByEnumId(id, ret); - if (NS_FAILED(rv)) - return rv; - - return NS_OK; -} - NS_IMETHODIMP TelemetryImpl::HistogramFrom(const nsACString &name, const nsACString &existing_name, JSContext *cx, JS::MutableHandle ret) { - Telemetry::ID id; - nsresult rv = GetHistogramEnumId(PromiseFlatCString(existing_name).get(), &id); - if (NS_FAILED(rv)) { - return rv; - } - - Histogram* clone = CloneHistogram(name, id); - if (!clone) { - return NS_ERROR_FAILURE; - } - - return WrapAndReturnHistogram(clone, cx, ret); -} - -void -TelemetryImpl::IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs) -{ - for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { - Histogram *h = *it; - - Telemetry::ID id; - nsresult rv = GetHistogramEnumId(h->histogram_name().c_str(), &id); - // This histogram isn't a static histogram, just ignore it. - if (NS_FAILED(rv)) { - continue; - } - - if (gCorruptHistograms[id]) { - continue; - } - - Histogram::SampleSet ss; - h->SnapshotSample(&ss); - - Histogram::Inconsistencies check; - { - mozilla::OffTheBooksMutexAutoLock locker(ss.mutex()); - check = h->FindCorruption(ss, locker); - } - - bool corrupt = (check != Histogram::NO_INCONSISTENCIES); - - if (corrupt) { - Telemetry::ID corruptID = Telemetry::HistogramCount; - if (check & Histogram::RANGE_CHECKSUM_ERROR) { - corruptID = Telemetry::RANGE_CHECKSUM_ERRORS; - } else if (check & Histogram::BUCKET_ORDER_ERROR) { - corruptID = Telemetry::BUCKET_ORDER_ERRORS; - } else if (check & Histogram::COUNT_HIGH_ERROR) { - corruptID = Telemetry::TOTAL_COUNT_HIGH_ERRORS; - } else if (check & Histogram::COUNT_LOW_ERROR) { - corruptID = Telemetry::TOTAL_COUNT_LOW_ERRORS; - } - Telemetry::Accumulate(corruptID, 1); - } - - gCorruptHistograms[id] = corrupt; - } -} - -bool -TelemetryImpl::ShouldReflectHistogram(Histogram *h) -{ - const char *name = h->histogram_name().c_str(); - Telemetry::ID id; - nsresult rv = GetHistogramEnumId(name, &id); - if (NS_FAILED(rv)) { - // GetHistogramEnumId generally should not fail. But a lookup - // failure shouldn't prevent us from reflecting histograms into JS. - // - // However, these two histograms are created by Histogram itself for - // tracking corruption. We have our own histograms for that, so - // ignore these two. - if (strcmp(name, "Histogram.InconsistentCountHigh") == 0 - || strcmp(name, "Histogram.InconsistentCountLow") == 0) { - return false; - } - return true; - } else { - return !gCorruptHistograms[id]; - } -} - -// Compute the name to pass into Histogram for the addon histogram -// 'name' from the addon 'id'. We can't use 'name' directly because it -// might conflict with other histograms in other addons or even with our -// own. -void -AddonHistogramName(const nsACString &id, const nsACString &name, - nsACString &ret) -{ - ret.Append(id); - ret.Append(':'); - ret.Append(name); + return TelemetryHistogram::HistogramFrom(name, existing_name, cx, ret); } NS_IMETHODIMP @@ -2216,207 +1118,33 @@ TelemetryImpl::RegisterAddonHistogram(const nsACString &id, uint32_t bucketCount, uint8_t optArgCount) { - if (histogramType == HISTOGRAM_EXPONENTIAL || - histogramType == HISTOGRAM_LINEAR) { - if (optArgCount != 3) { - return NS_ERROR_INVALID_ARG; - } - - // Sanity checks for histogram parameters. - if (min >= max) - return NS_ERROR_ILLEGAL_VALUE; - - if (bucketCount <= 2) - return NS_ERROR_ILLEGAL_VALUE; - - if (min < 1) - return NS_ERROR_ILLEGAL_VALUE; - } else { - min = 1; - max = 2; - bucketCount = 3; - } - - AddonEntryType *addonEntry = mAddonMap.GetEntry(id); - if (!addonEntry) { - addonEntry = mAddonMap.PutEntry(id); - if (MOZ_UNLIKELY(!addonEntry)) { - return NS_ERROR_OUT_OF_MEMORY; - } - addonEntry->mData = new AddonHistogramMapType(); - } - - AddonHistogramMapType *histogramMap = addonEntry->mData; - AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name); - // Can't re-register the same histogram. - if (histogramEntry) { - return NS_ERROR_FAILURE; - } - - histogramEntry = histogramMap->PutEntry(name); - if (MOZ_UNLIKELY(!histogramEntry)) { - return NS_ERROR_OUT_OF_MEMORY; - } - - AddonHistogramInfo &info = histogramEntry->mData; - info.min = min; - info.max = max; - info.bucketCount = bucketCount; - info.histogramType = histogramType; - - return NS_OK; + return TelemetryHistogram::RegisterAddonHistogram + (id, name, histogramType, min, max, bucketCount, optArgCount); } NS_IMETHODIMP TelemetryImpl::GetAddonHistogram(const nsACString &id, const nsACString &name, JSContext *cx, JS::MutableHandle ret) { - AddonEntryType *addonEntry = mAddonMap.GetEntry(id); - // The given id has not been registered. - if (!addonEntry) { - return NS_ERROR_INVALID_ARG; - } - - AddonHistogramMapType *histogramMap = addonEntry->mData; - AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name); - // The given histogram name has not been registered. - if (!histogramEntry) { - return NS_ERROR_INVALID_ARG; - } - - AddonHistogramInfo &info = histogramEntry->mData; - if (!info.h) { - nsAutoCString actualName; - AddonHistogramName(id, name, actualName); - if (!CreateHistogramForAddon(actualName, info)) { - return NS_ERROR_FAILURE; - } - } - return WrapAndReturnHistogram(info.h, cx, ret); + return TelemetryHistogram::GetAddonHistogram(id, name, cx, ret); } NS_IMETHODIMP TelemetryImpl::UnregisterAddonHistograms(const nsACString &id) { - AddonEntryType *addonEntry = mAddonMap.GetEntry(id); - if (addonEntry) { - // Histogram's destructor is private, so this is the best we can do. - // The histograms the addon created *will* stick around, but they - // will be deleted if and when the addon registers histograms with - // the same names. - delete addonEntry->mData; - mAddonMap.RemoveEntry(addonEntry); - } - - return NS_OK; + return TelemetryHistogram::UnregisterAddonHistograms(id); } NS_IMETHODIMP TelemetryImpl::SetHistogramRecordingEnabled(const nsACString &id, bool aEnabled) { - Histogram *h; - nsresult rv = GetHistogramByName(id, &h); - if (NS_SUCCEEDED(rv)) { - h->SetRecordingEnabled(aEnabled); - return NS_OK; - } - - KeyedHistogram* keyed = GetKeyedHistogramById(id); - if (keyed) { - keyed->SetRecordingEnabled(aEnabled); - return NS_OK; - } - - return NS_ERROR_FAILURE; -} - -nsresult -TelemetryImpl::CreateHistogramSnapshots(JSContext *cx, - JS::MutableHandle ret, - bool subsession, - bool clearSubsession) -{ - JS::Rooted root_obj(cx, JS_NewPlainObject(cx)); - if (!root_obj) - return NS_ERROR_FAILURE; - ret.setObject(*root_obj); - - // Ensure that all the HISTOGRAM_FLAG & HISTOGRAM_COUNT histograms have - // been created, so that their values are snapshotted. - for (size_t i = 0; i < Telemetry::HistogramCount; ++i) { - if (gHistograms[i].keyed) { - continue; - } - const uint32_t type = gHistograms[i].histogramType; - if (type == nsITelemetry::HISTOGRAM_FLAG || - type == nsITelemetry::HISTOGRAM_COUNT) { - Histogram *h; - DebugOnly rv = GetHistogramByEnumId(Telemetry::ID(i), &h); - MOZ_ASSERT(NS_SUCCEEDED(rv)); - } - } - - StatisticsRecorder::Histograms hs; - StatisticsRecorder::GetHistograms(&hs); - - // We identify corrupt histograms first, rather than interspersing it - // in the loop below, to ensure that our corruption statistics don't - // depend on histogram enumeration order. - // - // Of course, we hope that all of these corruption-statistics - // histograms are not themselves corrupt... - IdentifyCorruptHistograms(hs); - - // OK, now we can actually reflect things. - JS::Rooted hobj(cx); - for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { - Histogram *h = *it; - if (!ShouldReflectHistogram(h) || IsEmpty(h) || IsExpired(h)) { - continue; - } - - Histogram* original = h; -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - if (subsession) { - h = GetSubsessionHistogram(*h); - if (!h) { - continue; - } - } -#endif - - hobj = JS_NewPlainObject(cx); - if (!hobj) { - return NS_ERROR_FAILURE; - } - switch (ReflectHistogramSnapshot(cx, hobj, h)) { - case REFLECT_CORRUPT: - // We can still hit this case even if ShouldReflectHistograms - // returns true. The histogram lies outside of our control - // somehow; just skip it. - continue; - case REFLECT_FAILURE: - return NS_ERROR_FAILURE; - case REFLECT_OK: - if (!JS_DefineProperty(cx, root_obj, original->histogram_name().c_str(), - hobj, JSPROP_ENUMERATE)) { - return NS_ERROR_FAILURE; - } - } - -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - if (subsession && clearSubsession) { - h->Clear(); - } -#endif - } - return NS_OK; + return TelemetryHistogram::SetHistogramRecordingEnabled(id, aEnabled); } NS_IMETHODIMP TelemetryImpl::GetHistogramSnapshots(JSContext *cx, JS::MutableHandle ret) { - return CreateHistogramSnapshots(cx, ret, false, false); + return TelemetryHistogram::CreateHistogramSnapshots(cx, ret, false, false); } NS_IMETHODIMP @@ -2425,132 +1153,23 @@ TelemetryImpl::SnapshotSubsessionHistograms(bool clearSubsession, JS::MutableHandle ret) { #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - return CreateHistogramSnapshots(cx, ret, true, clearSubsession); + return TelemetryHistogram::CreateHistogramSnapshots(cx, ret, true, + clearSubsession); #else return NS_OK; #endif } -bool -TelemetryImpl::CreateHistogramForAddon(const nsACString &name, - AddonHistogramInfo &info) -{ - Histogram *h; - nsresult rv = HistogramGet(PromiseFlatCString(name).get(), "never", - info.histogramType, info.min, info.max, - info.bucketCount, true, &h); - if (NS_FAILED(rv)) { - return false; - } - // Don't let this histogram be reported via the normal means - // (e.g. Telemetry.registeredHistograms); we'll make it available in - // other ways. - h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); - info.h = h; - return true; -} - -bool -TelemetryImpl::AddonHistogramReflector(AddonHistogramEntryType *entry, - JSContext *cx, JS::Handle obj) -{ - AddonHistogramInfo &info = entry->mData; - - // Never even accessed the histogram. - if (!info.h) { - // Have to force creation of HISTOGRAM_FLAG histograms. - if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG) - return true; - - if (!CreateHistogramForAddon(entry->GetKey(), info)) { - return false; - } - } - - if (IsEmpty(info.h)) { - return true; - } - - JS::Rooted snapshot(cx, JS_NewPlainObject(cx)); - if (!snapshot) { - // Just consider this to be skippable. - return true; - } - switch (ReflectHistogramSnapshot(cx, snapshot, info.h)) { - case REFLECT_FAILURE: - case REFLECT_CORRUPT: - return false; - case REFLECT_OK: - const nsACString &histogramName = entry->GetKey(); - if (!JS_DefineProperty(cx, obj, PromiseFlatCString(histogramName).get(), - snapshot, JSPROP_ENUMERATE)) { - return false; - } - break; - } - return true; -} - -bool -TelemetryImpl::AddonReflector(AddonEntryType *entry, - JSContext *cx, JS::Handle obj) -{ - const nsACString &addonId = entry->GetKey(); - JS::Rooted subobj(cx, JS_NewPlainObject(cx)); - if (!subobj) { - return false; - } - - AddonHistogramMapType *map = entry->mData; - if (!(map->ReflectIntoJS(AddonHistogramReflector, cx, subobj) - && JS_DefineProperty(cx, obj, PromiseFlatCString(addonId).get(), - subobj, JSPROP_ENUMERATE))) { - return false; - } - return true; -} - NS_IMETHODIMP TelemetryImpl::GetAddonHistogramSnapshots(JSContext *cx, JS::MutableHandle ret) { - JS::Rooted obj(cx, JS_NewPlainObject(cx)); - if (!obj) { - return NS_ERROR_FAILURE; - } - - if (!mAddonMap.ReflectIntoJS(AddonReflector, cx, obj)) { - return NS_ERROR_FAILURE; - } - ret.setObject(*obj); - return NS_OK; + return TelemetryHistogram::GetAddonHistogramSnapshots(cx, ret); } NS_IMETHODIMP TelemetryImpl::GetKeyedHistogramSnapshots(JSContext *cx, JS::MutableHandle ret) { - JS::Rooted obj(cx, JS_NewPlainObject(cx)); - if (!obj) { - return NS_ERROR_FAILURE; - } - - for (auto iter = mKeyedHistograms.Iter(); !iter.Done(); iter.Next()) { - JS::RootedObject snapshot(cx, JS_NewPlainObject(cx)); - if (!snapshot) { - return NS_ERROR_FAILURE; - } - - if (!NS_SUCCEEDED(iter.Data()->GetJSSnapshot(cx, snapshot, false, false))) { - return NS_ERROR_FAILURE; - } - - if (!JS_DefineProperty(cx, obj, PromiseFlatCString(iter.Key()).get(), - snapshot, JSPROP_ENUMERATE)) { - return NS_ERROR_FAILURE; - } - } - - ret.setObject(*obj); - return NS_OK; + return TelemetryHistogram::GetKeyedHistogramSnapshots(cx, ret); } bool @@ -3220,100 +1839,35 @@ TelemetryImpl::GetLateWrites(JSContext *cx, JS::MutableHandle ret) return NS_OK; } -nsresult -GetRegisteredHistogramIds(bool keyed, uint32_t dataset, uint32_t *aCount, - char*** aHistograms) -{ - nsTArray collection; - - for (size_t i = 0; i < ArrayLength(gHistograms); ++i) { - const TelemetryHistogram& h = gHistograms[i]; - if (IsExpired(h.expiration()) || h.keyed != keyed || - !IsInDataset(h.dataset, dataset)) { - continue; - } - - const char* id = h.id(); - const size_t len = strlen(id); - collection.AppendElement(static_cast(nsMemory::Clone(id, len+1))); - } - - const size_t bytes = collection.Length() * sizeof(char*); - char** histograms = static_cast(moz_xmalloc(bytes)); - memcpy(histograms, collection.Elements(), bytes); - *aHistograms = histograms; - *aCount = collection.Length(); - - return NS_OK; -} - NS_IMETHODIMP TelemetryImpl::RegisteredHistograms(uint32_t aDataset, uint32_t *aCount, char*** aHistograms) { - return GetRegisteredHistogramIds(false, aDataset, aCount, aHistograms); + return + TelemetryHistogram::RegisteredHistograms(aDataset, aCount, aHistograms); } NS_IMETHODIMP TelemetryImpl::RegisteredKeyedHistograms(uint32_t aDataset, uint32_t *aCount, char*** aHistograms) { - return GetRegisteredHistogramIds(true, aDataset, aCount, aHistograms); + return + TelemetryHistogram::RegisteredKeyedHistograms(aDataset, aCount, + aHistograms); } NS_IMETHODIMP TelemetryImpl::GetHistogramById(const nsACString &name, JSContext *cx, JS::MutableHandle ret) { - Histogram *h; - nsresult rv = GetHistogramByName(name, &h); - if (NS_FAILED(rv)) - return rv; - - return WrapAndReturnHistogram(h, cx, ret); + return TelemetryHistogram::GetHistogramById(name, cx, ret); } NS_IMETHODIMP TelemetryImpl::GetKeyedHistogramById(const nsACString &name, JSContext *cx, JS::MutableHandle ret) { - KeyedHistogram* keyed = nullptr; - if (!mKeyedHistograms.Get(name, &keyed)) { - return NS_ERROR_FAILURE; - } - - return WrapAndReturnKeyedHistogram(keyed, cx, ret); -} - -/* static */ -KeyedHistogram* -TelemetryImpl::GetKeyedHistogramById(const nsACString &name) -{ - if (!sTelemetry) { - return nullptr; - } - - KeyedHistogram* keyed = nullptr; - sTelemetry->mKeyedHistograms.Get(name, &keyed); - return keyed; -} - -NS_IMETHODIMP -TelemetryImpl::GetCanRecordBase(bool *ret) { - *ret = mCanRecordBase; - return NS_OK; -} - -NS_IMETHODIMP -TelemetryImpl::SetCanRecordBase(bool canRecord) { - mCanRecordBase = canRecord; - return NS_OK; -} - -/* static */ bool -TelemetryImpl::IsInitialized() -{ - return sTelemetry; + return TelemetryHistogram::GetKeyedHistogramById(name, cx, ret); } /** @@ -3324,20 +1878,15 @@ TelemetryImpl::IsInitialized() * collection wiki at https://wiki.mozilla.org/Firefox/Data_Collection and talk to the * Telemetry team. */ -bool -TelemetryImpl::CanRecordBase() { - return !sTelemetry || sTelemetry->mCanRecordBase; -} - NS_IMETHODIMP -TelemetryImpl::GetCanRecordExtended(bool *ret) { - *ret = mCanRecordExtended; +TelemetryImpl::GetCanRecordBase(bool *ret) { + *ret = TelemetryHistogram::CanRecordBase(); return NS_OK; } NS_IMETHODIMP -TelemetryImpl::SetCanRecordExtended(bool canRecord) { - mCanRecordExtended = canRecord; +TelemetryImpl::SetCanRecordBase(bool canRecord) { + TelemetryHistogram::SetCanRecordBase(canRecord); return NS_OK; } @@ -3348,11 +1897,19 @@ TelemetryImpl::SetCanRecordExtended(bool canRecord) { * during tests. * If the returned value is false, gathering of extended telemetry statistics is disabled. */ -bool -TelemetryImpl::CanRecordExtended() { - return !sTelemetry || sTelemetry->mCanRecordExtended; +NS_IMETHODIMP +TelemetryImpl::GetCanRecordExtended(bool *ret) { + *ret = TelemetryHistogram::CanRecordExtended(); + return NS_OK; } +NS_IMETHODIMP +TelemetryImpl::SetCanRecordExtended(bool canRecord) { + TelemetryHistogram::SetCanRecordExtended(canRecord); + return NS_OK; +} + + NS_IMETHODIMP TelemetryImpl::GetIsOfficialTelemetry(bool *ret) { #if defined(MOZILLA_OFFICIAL) && defined(MOZ_TELEMETRY_REPORTING) && !defined(DEBUG) @@ -3367,7 +1924,15 @@ already_AddRefed TelemetryImpl::CreateTelemetryInstance() { MOZ_ASSERT(sTelemetry == nullptr, "CreateTelemetryInstance may only be called once, via GetService()"); + + // First, initialize the TelemetryHistogram global state. + TelemetryHistogram::InitializeGlobalState( + XRE_IsParentProcess() || XRE_IsContentProcess(), + XRE_IsParentProcess() || XRE_IsContentProcess()); + + // Now, create and initialize the Telemetry global state. sTelemetry = new TelemetryImpl(); + // AddRef for the local reference NS_ADDREF(sTelemetry); // AddRef for the caller @@ -3385,6 +1950,10 @@ TelemetryImpl::ShutdownTelemetry() // No point in collecting IO beyond this point ClearIOReporting(); NS_IF_RELEASE(sTelemetry); + + // Lastly, de-initialise the TelemetryHistogram global state, so as to + // release any heap storage that would otherwise be kept alive by it. + TelemetryHistogram::DeInitializeGlobalState(); } void @@ -3603,7 +2172,7 @@ TelemetryImpl::RecordSlowStatement(const nsACString &sql, MOZ_ASSERT(!sql.IsEmpty()); MOZ_ASSERT(!dbName.IsEmpty()); - if (!sTelemetry || !sTelemetry->mCanRecordExtended) + if (!sTelemetry || !TelemetryHistogram::CanRecordExtended()) return; bool recordStatement = false; @@ -3656,7 +2225,7 @@ void TelemetryImpl::RecordIceCandidates(const uint32_t iceCandidateBitmask, const bool success, const bool loop) { - if (!sTelemetry || !sTelemetry->mCanRecordExtended) + if (!sTelemetry || !TelemetryHistogram::CanRecordExtended()) return; sTelemetry->mWebrtcTelemetry.RecordIceCandidateMask(iceCandidateBitmask, success, loop); @@ -3670,7 +2239,7 @@ TelemetryImpl::RecordChromeHang(uint32_t aDuration, int32_t aFirefoxUptime, HangAnnotationsPtr aAnnotations) { - if (!sTelemetry || !sTelemetry->mCanRecordExtended) + if (!sTelemetry || !TelemetryHistogram::CanRecordExtended()) return; HangAnnotationsPtr annotations; @@ -3690,7 +2259,7 @@ TelemetryImpl::RecordChromeHang(uint32_t aDuration, void TelemetryImpl::RecordThreadHangStats(Telemetry::ThreadHangStats& aStats) { - if (!sTelemetry || !sTelemetry->mCanRecordExtended) + if (!sTelemetry || !TelemetryHistogram::CanRecordExtended()) return; MutexAutoLock autoLock(sTelemetry->mThreadHangStatsMutex); @@ -3763,8 +2332,7 @@ TelemetryImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) size_t n = aMallocSizeOf(this); // Ignore the hashtables in mAddonMap; they are not significant. - n += mAddonMap.ShallowSizeOfExcludingThis(aMallocSizeOf); - n += mHistogramMap.ShallowSizeOfExcludingThis(aMallocSizeOf); + n += TelemetryHistogram::GetMapShallowSizesOfExcludingThis(aMallocSizeOf); n += mWebrtcTelemetry.SizeOfExcludingThis(aMallocSizeOf); { // Scope for mHashMutex lock MutexAutoLock lock(mHashMutex); @@ -3785,12 +2353,8 @@ TelemetryImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) if (sTelemetryIOObserver) { n += sTelemetryIOObserver->SizeOfIncludingThis(aMallocSizeOf); } - StatisticsRecorder::Histograms hs; - StatisticsRecorder::GetHistograms(&hs); - for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { - Histogram *h = *it; - n += h->SizeOfIncludingThis(aMallocSizeOf); - } + + n += TelemetryHistogram::GetHistogramSizesofIncludingThis(aMallocSizeOf); return n; } @@ -3869,85 +2433,31 @@ namespace Telemetry { void SetHistogramRecordingEnabled(ID aID, bool aEnabled) { - if (!IsHistogramEnumId(aID)) { - MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) must be used with an enum id"); - return; - } - - if (gHistograms[aID].keyed) { - const nsDependentCString id(gHistograms[aID].id()); - KeyedHistogram* keyed = TelemetryImpl::GetKeyedHistogramById(id); - if (keyed) { - keyed->SetRecordingEnabled(aEnabled); - return; - } - } else { - Histogram *h; - nsresult rv = GetHistogramByEnumId(aID, &h); - if (NS_SUCCEEDED(rv)) { - h->SetRecordingEnabled(aEnabled); - return; - } - } - - MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) id not found"); + TelemetryHistogram::SetHistogramRecordingEnabled(aID, aEnabled); } void Accumulate(ID aHistogram, uint32_t aSample) { - if (!TelemetryImpl::CanRecordBase()) { - return; - } - Histogram *h; - nsresult rv = GetHistogramByEnumId(aHistogram, &h); - if (NS_SUCCEEDED(rv)) { - HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset); - } + TelemetryHistogram::Accumulate(aHistogram, aSample); } void Accumulate(ID aID, const nsCString& aKey, uint32_t aSample) { - if (!TelemetryImpl::IsInitialized() || !TelemetryImpl::CanRecordBase()) { - return; - } - const TelemetryHistogram& th = gHistograms[aID]; - KeyedHistogram* keyed = TelemetryImpl::GetKeyedHistogramById(nsDependentCString(th.id())); - MOZ_ASSERT(keyed); - keyed->Add(aKey, aSample); + TelemetryHistogram::Accumulate(aID, aKey, aSample); } void Accumulate(const char* name, uint32_t sample) { - if (!TelemetryImpl::CanRecordBase()) { - return; - } - ID id; - nsresult rv = TelemetryImpl::GetHistogramEnumId(name, &id); - if (NS_FAILED(rv)) { - return; - } - - Histogram *h; - rv = GetHistogramByEnumId(id, &h); - if (NS_SUCCEEDED(rv)) { - HistogramAdd(*h, sample, gHistograms[id].dataset); - } + TelemetryHistogram::Accumulate(name, sample); } void Accumulate(const char *name, const nsCString& key, uint32_t sample) { - if (!TelemetryImpl::CanRecordBase()) { - return; - } - ID id; - nsresult rv = TelemetryImpl::GetHistogramEnumId(name, &id); - if (NS_SUCCEEDED(rv)) { - Accumulate(id, key, sample); - } + TelemetryHistogram::Accumulate(name, key, sample); } void @@ -3957,31 +2467,28 @@ AccumulateTimeDelta(ID aHistogram, TimeStamp start, TimeStamp end) static_cast((end - start).ToMilliseconds())); } +void +ClearHistogram(ID aId) +{ + TelemetryHistogram::ClearHistogram(aId); +} + +const char* +GetHistogramName(ID id) +{ + return TelemetryHistogram::GetHistogramName(id); +} + bool CanRecordBase() { - return TelemetryImpl::CanRecordBase(); + return TelemetryHistogram::CanRecordBase(); } bool CanRecordExtended() { - return TelemetryImpl::CanRecordExtended(); -} - -base::Histogram* -GetHistogramById(ID id) -{ - Histogram *h = nullptr; - GetHistogramByEnumId(id, &h); - return h; -} - -const char* -GetHistogramName(Telemetry::ID id) -{ - const TelemetryHistogram& h = gHistograms[id]; - return h.id(); + return TelemetryHistogram::CanRecordExtended(); } void @@ -4364,202 +2871,3 @@ XRE_TelemetryAccumulate(int aID, uint32_t aSample) { mozilla::Telemetry::Accumulate((mozilla::Telemetry::ID) aID, aSample); } - -KeyedHistogram::KeyedHistogram(const nsACString &name, const nsACString &expiration, - uint32_t histogramType, uint32_t min, uint32_t max, - uint32_t bucketCount, uint32_t dataset) - : mHistogramMap() -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - , mSubsessionMap() -#endif - , mName(name) - , mExpiration(expiration) - , mHistogramType(histogramType) - , mMin(min) - , mMax(max) - , mBucketCount(bucketCount) - , mDataset(dataset) - , mRecordingEnabled(true) -{ -} - -nsresult -KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram, - bool subsession) -{ -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - KeyedHistogramMapType& map = subsession ? mSubsessionMap : mHistogramMap; -#else - KeyedHistogramMapType& map = mHistogramMap; -#endif - KeyedHistogramEntry* entry = map.GetEntry(key); - if (entry) { - *histogram = entry->mData; - return NS_OK; - } - - nsCString histogramName; -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - if (subsession) { - histogramName.AppendLiteral(SUBSESSION_HISTOGRAM_PREFIX); - } -#endif - histogramName.Append(mName); - histogramName.AppendLiteral(KEYED_HISTOGRAM_NAME_SEPARATOR); - histogramName.Append(key); - - Histogram* h; - nsresult rv = HistogramGet(histogramName.get(), mExpiration.get(), - mHistogramType, mMin, mMax, mBucketCount, - true, &h); - if (NS_FAILED(rv)) { - return rv; - } - - h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); - *histogram = h; - - entry = map.PutEntry(key); - if (MOZ_UNLIKELY(!entry)) { - return NS_ERROR_OUT_OF_MEMORY; - } - - entry->mData = h; - return NS_OK; -} - -Histogram* -KeyedHistogram::GetHistogram(const nsCString& key, bool subsession) -{ - Histogram* h = nullptr; - if (NS_FAILED(GetHistogram(key, &h, subsession))) { - return nullptr; - } - return h; -} - -nsresult -KeyedHistogram::GetDataset(uint32_t* dataset) const -{ - MOZ_ASSERT(dataset); - *dataset = mDataset; - return NS_OK; -} - -nsresult -KeyedHistogram::Add(const nsCString& key, uint32_t sample) -{ - if (!CanRecordDataset(mDataset)) { - return NS_OK; - } - - Histogram* histogram = GetHistogram(key, false); - MOZ_ASSERT(histogram); - if (!histogram) { - return NS_ERROR_FAILURE; - } -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - Histogram* subsession = GetHistogram(key, true); - MOZ_ASSERT(subsession); - if (!subsession) { - return NS_ERROR_FAILURE; - } -#endif - - if (!IsRecordingEnabled()) { - return NS_OK; - } - - histogram->Add(sample); -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - subsession->Add(sample); -#endif - return NS_OK; -} - -void -KeyedHistogram::Clear(bool onlySubsession) -{ -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - for (auto iter = mSubsessionMap.Iter(); !iter.Done(); iter.Next()) { - iter.Get()->mData->Clear(); - } - mSubsessionMap.Clear(); - if (onlySubsession) { - return; - } -#endif - - for (auto iter = mHistogramMap.Iter(); !iter.Done(); iter.Next()) { - iter.Get()->mData->Clear(); - } - mHistogramMap.Clear(); -} - -nsresult -KeyedHistogram::GetJSKeys(JSContext* cx, JS::CallArgs& args) -{ - JS::AutoValueVector keys(cx); - if (!keys.reserve(mHistogramMap.Count())) { - return NS_ERROR_OUT_OF_MEMORY; - } - - for (auto iter = mHistogramMap.Iter(); !iter.Done(); iter.Next()) { - JS::RootedValue jsKey(cx); - const NS_ConvertUTF8toUTF16 key(iter.Get()->GetKey()); - jsKey.setString(JS_NewUCStringCopyN(cx, key.Data(), key.Length())); - keys.append(jsKey); - } - - JS::RootedObject jsKeys(cx, JS_NewArrayObject(cx, keys)); - if (!jsKeys) { - return NS_ERROR_FAILURE; - } - - args.rval().setObject(*jsKeys); - return NS_OK; -} - -/* static */ -bool -KeyedHistogram::ReflectKeyedHistogram(KeyedHistogramEntry* entry, JSContext* cx, JS::Handle obj) -{ - JS::RootedObject histogramSnapshot(cx, JS_NewPlainObject(cx)); - if (!histogramSnapshot) { - return false; - } - - if (ReflectHistogramSnapshot(cx, histogramSnapshot, entry->mData) != REFLECT_OK) { - return false; - } - - const NS_ConvertUTF8toUTF16 key(entry->GetKey()); - if (!JS_DefineUCProperty(cx, obj, key.Data(), key.Length(), - histogramSnapshot, JSPROP_ENUMERATE)) { - return false; - } - - return true; -} - -nsresult -KeyedHistogram::GetJSSnapshot(JSContext* cx, JS::Handle obj, - bool subsession, bool clearSubsession) -{ -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - KeyedHistogramMapType& map = subsession ? mSubsessionMap : mHistogramMap; -#else - KeyedHistogramMapType& map = mHistogramMap; -#endif - if (!map.ReflectIntoJS(&KeyedHistogram::ReflectKeyedHistogram, cx, obj)) { - return NS_ERROR_FAILURE; - } - -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) - if (subsession && clearSubsession) { - Clear(true); - } -#endif - - return NS_OK; -} diff --git a/toolkit/components/telemetry/Telemetry.h b/toolkit/components/telemetry/Telemetry.h index 1b16d0ed74..512fad9aa7 100644 --- a/toolkit/components/telemetry/Telemetry.h +++ b/toolkit/components/telemetry/Telemetry.h @@ -12,9 +12,19 @@ #include "nsTArray.h" #include "nsStringGlue.h" -namespace base { - class Histogram; -} // namespace base +#include "mozilla/TelemetryHistogramEnums.h" + +/****************************************************************************** + * This implements the Telemetry system. + * It allows recording into histograms as well some more specialized data + * points and gives access to the data. + * + * For documentation on how to add and use new Telemetry probes, see: + * https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Adding_a_new_Telemetry_probe + * + * For more general information on Telemetry see: + * https://wiki.mozilla.org/Telemetry + *****************************************************************************/ namespace mozilla { namespace HangMonitor { @@ -22,8 +32,6 @@ namespace HangMonitor { } // namespace HangMonitor namespace Telemetry { -#include "mozilla/TelemetryHistogramEnums.h" - enum TimerResolution { Millisecond, Microsecond @@ -83,6 +91,13 @@ void Accumulate(const char *name, const nsCString& key, uint32_t sample = 1); */ void AccumulateTimeDelta(ID id, TimeStamp start, TimeStamp end = TimeStamp::Now()); +/** + * This clears the data for a histogram in TelemetryHistograms.h. + * + * @param id - histogram id + */ +void ClearHistogram(ID id); + /** * Enable/disable recording for this histogram at runtime. * Recording is enabled by default, unless listed at kRecordingInitiallyDisabledIDs[]. @@ -93,18 +108,8 @@ void AccumulateTimeDelta(ID id, TimeStamp start, TimeStamp end = TimeStamp::Now( */ void SetHistogramRecordingEnabled(ID id, bool enabled); -/** - * Return a raw Histogram for direct manipulation for users who can not use Accumulate(). - */ -base::Histogram* GetHistogramById(ID id); - const char* GetHistogramName(ID id); -/** - * Return a raw histogram for keyed histograms. - */ -base::Histogram* GetKeyedHistogramById(ID id, const nsAString&); - /** * Those wrappers are needed because the VS versions we use do not support free * functions with default template arguments. diff --git a/toolkit/components/telemetry/TelemetryHistogram.cpp b/toolkit/components/telemetry/TelemetryHistogram.cpp new file mode 100644 index 0000000000..cfc088b6af --- /dev/null +++ b/toolkit/components/telemetry/TelemetryHistogram.cpp @@ -0,0 +1,2077 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/GCAPI.h" +#include "nsString.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "nsBaseHashtable.h" +#include "nsClassHashtable.h" +#include "nsITelemetry.h" +#include "nsVersionComparator.h" + +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/StartupTimeline.h" + +#include "TelemetryCommon.h" +#include "TelemetryHistogram.h" + +#include "base/histogram.h" + +using base::Histogram; +using base::StatisticsRecorder; +using base::BooleanHistogram; +using base::CountHistogram; +using base::FlagHistogram; +using base::LinearHistogram; + + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE TYPES + +#define EXPIRED_ID "__expired__" +#define SUBSESSION_HISTOGRAM_PREFIX "sub#" +#define KEYED_HISTOGRAM_NAME_SEPARATOR "#" + +namespace { + +class KeyedHistogram; + +typedef nsBaseHashtableET + CharPtrEntryType; + +typedef AutoHashtable HistogramMapType; + +typedef nsClassHashtable + KeyedHistogramMapType; + +// Hardcoded probes +struct HistogramInfo { + uint32_t min; + uint32_t max; + uint32_t bucketCount; + uint32_t histogramType; + uint32_t id_offset; + uint32_t expiration_offset; + uint32_t dataset; + bool keyed; + + const char *id() const; + const char *expiration() const; +}; + +struct AddonHistogramInfo { + uint32_t min; + uint32_t max; + uint32_t bucketCount; + uint32_t histogramType; + Histogram *h; +}; + +enum reflectStatus { + REFLECT_OK, + REFLECT_CORRUPT, + REFLECT_FAILURE +}; + +typedef StatisticsRecorder::Histograms::iterator HistogramIterator; + +typedef nsBaseHashtableET + AddonHistogramEntryType; + +typedef AutoHashtable AddonHistogramMapType; + +typedef nsBaseHashtableET + AddonEntryType; + +typedef AutoHashtable AddonMapType; + +} // namespace + + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE STATE, SHARED BY ALL THREADS + +namespace { + +// Set to true once this global state has been initialized +bool gInitDone = false; + +bool gCanRecordBase; +bool gCanRecordExtended; + +HistogramMapType gHistogramMap(mozilla::Telemetry::HistogramCount); + +KeyedHistogramMapType gKeyedHistograms; + +bool gCorruptHistograms[mozilla::Telemetry::HistogramCount]; + +// This is for gHistograms, gHistogramStringTable +#include "TelemetryHistogramData.inc" + +AddonMapType gAddonMap; + +} // namespace + + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE CONSTANTS + +// List of histogram IDs which should have recording disabled initially. +const mozilla::Telemetry::ID kRecordingInitiallyDisabledIDs[] = { + mozilla::Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, + + // The array must not be empty. Leave these item here. + mozilla::Telemetry::TELEMETRY_TEST_COUNT_INIT_NO_RECORD, + mozilla::Telemetry::TELEMETRY_TEST_KEYED_COUNT_INIT_NO_RECORD +}; + + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: Misc small helpers + +namespace { + +bool +IsHistogramEnumId(mozilla::Telemetry::ID aID) +{ + static_assert(((mozilla::Telemetry::ID)-1 > 0), "ID should be unsigned."); + return aID < mozilla::Telemetry::HistogramCount; +} + +bool +IsExpired(const char *expiration) +{ + static mozilla::Version current_version = mozilla::Version(MOZ_APP_VERSION); + MOZ_ASSERT(expiration); + return strcmp(expiration, "never") && strcmp(expiration, "default") && + (mozilla::Version(expiration) <= current_version); +} + +bool +IsInDataset(uint32_t dataset, uint32_t containingDataset) +{ + if (dataset == containingDataset) { + return true; + } + + // The "optin on release channel" dataset is a superset of the + // "optout on release channel one". + if (containingDataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN + && dataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT) { + return true; + } + + return false; +} + +bool +CanRecordDataset(uint32_t dataset) +{ + // If we are extended telemetry is enabled, we are allowed to record + // regardless of the dataset. + if (TelemetryHistogram::CanRecordExtended()) { + return true; + } + + // If base telemetry data is enabled and we're trying to record base + // telemetry, allow it. + if (TelemetryHistogram::CanRecordBase() && + IsInDataset(dataset, nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT)) { + return true; + } + + // We're not recording extended telemetry or this is not the base + // dataset. Bail out. + return false; +} + +bool +IsValidHistogramName(const nsACString& name) +{ + return !FindInReadable(NS_LITERAL_CSTRING(KEYED_HISTOGRAM_NAME_SEPARATOR), name); +} + +// Note: this is completely unrelated to mozilla::IsEmpty. +bool +IsEmpty(const Histogram *h) +{ + Histogram::SampleSet ss; + h->SnapshotSample(&ss); + + mozilla::OffTheBooksMutexAutoLock locker(ss.mutex()); + return ss.counts(locker, 0) == 0 && ss.sum(locker) == 0; +} + +bool +IsExpired(const Histogram *histogram) +{ + return histogram->histogram_name() == EXPIRED_ID; +} + +nsresult +GetRegisteredHistogramIds(bool keyed, uint32_t dataset, uint32_t *aCount, + char*** aHistograms) +{ + nsTArray collection; + + for (size_t i = 0; i < mozilla::ArrayLength(gHistograms); ++i) { + const HistogramInfo& h = gHistograms[i]; + if (IsExpired(h.expiration()) || h.keyed != keyed || + !IsInDataset(h.dataset, dataset)) { + continue; + } + + const char* id = h.id(); + const size_t len = strlen(id); + collection.AppendElement(static_cast(nsMemory::Clone(id, len+1))); + } + + const size_t bytes = collection.Length() * sizeof(char*); + char** histograms = static_cast(moz_xmalloc(bytes)); + memcpy(histograms, collection.Elements(), bytes); + *aHistograms = histograms; + *aCount = collection.Length(); + + return NS_OK; +} + +const char * +HistogramInfo::id() const +{ + return &gHistogramStringTable[this->id_offset]; +} + +const char * +HistogramInfo::expiration() const +{ + return &gHistogramStringTable[this->expiration_offset]; +} + +} // namespace + + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: Histogram Get, Add, Clone, Clear functions + +namespace { + +nsresult +CheckHistogramArguments(uint32_t histogramType, uint32_t min, uint32_t max, + uint32_t bucketCount, bool haveOptArgs) +{ + if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN + && histogramType != nsITelemetry::HISTOGRAM_FLAG + && histogramType != nsITelemetry::HISTOGRAM_COUNT) { + // The min, max & bucketCount arguments are not optional for this type. + if (!haveOptArgs) + return NS_ERROR_ILLEGAL_VALUE; + + // Sanity checks for histogram parameters. + if (min >= max) + return NS_ERROR_ILLEGAL_VALUE; + + if (bucketCount <= 2) + return NS_ERROR_ILLEGAL_VALUE; + + if (min < 1) + return NS_ERROR_ILLEGAL_VALUE; + } + + return NS_OK; +} + +/* + * min, max & bucketCount are optional for boolean, flag & count histograms. + * haveOptArgs has to be set if the caller provides them. + */ +nsresult +HistogramGet(const char *name, const char *expiration, uint32_t histogramType, + uint32_t min, uint32_t max, uint32_t bucketCount, bool haveOptArgs, + Histogram **result) +{ + nsresult rv = CheckHistogramArguments(histogramType, min, max, bucketCount, haveOptArgs); + if (NS_FAILED(rv)) { + return rv; + } + + if (IsExpired(expiration)) { + name = EXPIRED_ID; + min = 1; + max = 2; + bucketCount = 3; + histogramType = nsITelemetry::HISTOGRAM_LINEAR; + } + + switch (histogramType) { + case nsITelemetry::HISTOGRAM_EXPONENTIAL: + *result = Histogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag); + break; + case nsITelemetry::HISTOGRAM_LINEAR: + *result = LinearHistogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag); + break; + case nsITelemetry::HISTOGRAM_BOOLEAN: + *result = BooleanHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag); + break; + case nsITelemetry::HISTOGRAM_FLAG: + *result = FlagHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag); + break; + case nsITelemetry::HISTOGRAM_COUNT: + *result = CountHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag); + break; + default: + NS_ASSERTION(false, "Invalid histogram type"); + return NS_ERROR_INVALID_ARG; + } + return NS_OK; +} + +nsresult +GetHistogramEnumId(const char *name, mozilla::Telemetry::ID *id) +{ + if (!gInitDone) { + return NS_ERROR_FAILURE; + } + + CharPtrEntryType *entry = gHistogramMap.GetEntry(name); + if (!entry) { + return NS_ERROR_INVALID_ARG; + } + *id = entry->mData; + return NS_OK; +} + +// O(1) histogram lookup by numeric id +nsresult +GetHistogramByEnumId(mozilla::Telemetry::ID id, Histogram **ret) +{ + static Histogram* knownHistograms[mozilla::Telemetry::HistogramCount] = {0}; + Histogram *h = knownHistograms[id]; + if (h) { + *ret = h; + return NS_OK; + } + + const HistogramInfo &p = gHistograms[id]; + if (p.keyed) { + return NS_ERROR_FAILURE; + } + + nsresult rv = HistogramGet(p.id(), p.expiration(), p.histogramType, + p.min, p.max, p.bucketCount, true, &h); + if (NS_FAILED(rv)) + return rv; + +#ifdef DEBUG + // Check that the C++ Histogram code computes the same ranges as the + // Python histogram code. + if (!IsExpired(p.expiration())) { + const struct bounds &b = gBucketLowerBoundIndex[id]; + if (b.length != 0) { + MOZ_ASSERT(size_t(b.length) == h->bucket_count(), + "C++/Python bucket # mismatch"); + for (int i = 0; i < b.length; ++i) { + MOZ_ASSERT(gBucketLowerBounds[b.offset + i] == h->ranges(i), + "C++/Python bucket mismatch"); + } + } + } +#endif + + *ret = knownHistograms[id] = h; + return NS_OK; +} + +nsresult +GetHistogramByName(const nsACString &name, Histogram **ret) +{ + mozilla::Telemetry::ID id; + nsresult rv = GetHistogramEnumId(PromiseFlatCString(name).get(), &id); + if (NS_FAILED(rv)) { + return rv; + } + + rv = GetHistogramByEnumId(id, ret); + if (NS_FAILED(rv)) + return rv; + + return NS_OK; +} + +/** + * This clones a histogram |existing| with the id |existingId| to a + * new histogram with the name |newName|. + * For simplicity this is limited to registered histograms. + */ +Histogram* +CloneHistogram(const nsACString& newName, mozilla::Telemetry::ID existingId, + Histogram& existing) +{ + const HistogramInfo &info = gHistograms[existingId]; + Histogram *clone = nullptr; + nsresult rv; + + rv = HistogramGet(PromiseFlatCString(newName).get(), info.expiration(), + info.histogramType, existing.declared_min(), + existing.declared_max(), existing.bucket_count(), + true, &clone); + if (NS_FAILED(rv)) { + return nullptr; + } + + Histogram::SampleSet ss; + existing.SnapshotSample(&ss); + clone->AddSampleSet(ss); + + return clone; +} + +/** + * This clones a histogram with the id |existingId| to a new histogram + * with the name |newName|. + * For simplicity this is limited to registered histograms. + */ +Histogram* +CloneHistogram(const nsACString& newName, mozilla::Telemetry::ID existingId) +{ + Histogram *existing = nullptr; + nsresult rv = GetHistogramByEnumId(existingId, &existing); + if (NS_FAILED(rv)) { + return nullptr; + } + + return CloneHistogram(newName, existingId, *existing); +} + +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) +Histogram* +GetSubsessionHistogram(Histogram& existing) +{ + mozilla::Telemetry::ID id; + nsresult rv = GetHistogramEnumId(existing.histogram_name().c_str(), &id); + if (NS_FAILED(rv) || gHistograms[id].keyed) { + return nullptr; + } + + static Histogram* subsession[mozilla::Telemetry::HistogramCount] = {}; + if (subsession[id]) { + return subsession[id]; + } + + NS_NAMED_LITERAL_CSTRING(prefix, SUBSESSION_HISTOGRAM_PREFIX); + nsDependentCString existingName(gHistograms[id].id()); + if (StringBeginsWith(existingName, prefix)) { + return nullptr; + } + + nsCString subsessionName(prefix); + subsessionName.Append(existingName); + + subsession[id] = CloneHistogram(subsessionName, id, existing); + return subsession[id]; +} +#endif + +nsresult +HistogramAdd(Histogram& histogram, int32_t value, uint32_t dataset) +{ + // Check if we are allowed to record the data. + if (!CanRecordDataset(dataset) || !histogram.IsRecordingEnabled()) { + return NS_OK; + } + +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + if (Histogram* subsession = GetSubsessionHistogram(histogram)) { + subsession->Add(value); + } +#endif + + // It is safe to add to the histogram now: the subsession histogram was already + // cloned from this so we won't add the sample twice. + histogram.Add(value); + + return NS_OK; +} + +nsresult +HistogramAdd(Histogram& histogram, int32_t value) +{ + uint32_t dataset = nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN; + // We only really care about the dataset of the histogram if we are not recording + // extended telemetry. Otherwise, we always record histogram data. + if (!TelemetryHistogram::CanRecordExtended()) { + mozilla::Telemetry::ID id; + nsresult rv = GetHistogramEnumId(histogram.histogram_name().c_str(), &id); + if (NS_FAILED(rv)) { + // If we can't look up the dataset, it might be because the histogram was added + // at runtime. Since we're not recording extended telemetry, bail out. + return NS_OK; + } + dataset = gHistograms[id].dataset; + } + + return HistogramAdd(histogram, value, dataset); +} + +void +HistogramClear(Histogram& aHistogram, bool onlySubsession) +{ + if (!onlySubsession) { + aHistogram.Clear(); + } + +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + if (Histogram* subsession = GetSubsessionHistogram(aHistogram)) { + subsession->Clear(); + } +#endif +} + +} // namespace + + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: Histogram corruption helpers + +namespace { + +void +IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs) +{ + for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { + Histogram *h = *it; + + mozilla::Telemetry::ID id; + nsresult rv = ::GetHistogramEnumId(h->histogram_name().c_str(), &id); + // This histogram isn't a static histogram, just ignore it. + if (NS_FAILED(rv)) { + continue; + } + + if (gCorruptHistograms[id]) { + continue; + } + + Histogram::SampleSet ss; + h->SnapshotSample(&ss); + + Histogram::Inconsistencies check; + { + mozilla::OffTheBooksMutexAutoLock locker(ss.mutex()); + check = h->FindCorruption(ss, locker); + } + + bool corrupt = (check != Histogram::NO_INCONSISTENCIES); + + if (corrupt) { + mozilla::Telemetry::ID corruptID = mozilla::Telemetry::HistogramCount; + if (check & Histogram::RANGE_CHECKSUM_ERROR) { + corruptID = mozilla::Telemetry::RANGE_CHECKSUM_ERRORS; + } else if (check & Histogram::BUCKET_ORDER_ERROR) { + corruptID = mozilla::Telemetry::BUCKET_ORDER_ERRORS; + } else if (check & Histogram::COUNT_HIGH_ERROR) { + corruptID = mozilla::Telemetry::TOTAL_COUNT_HIGH_ERRORS; + } else if (check & Histogram::COUNT_LOW_ERROR) { + corruptID = mozilla::Telemetry::TOTAL_COUNT_LOW_ERRORS; + } + TelemetryHistogram::Accumulate(corruptID, 1); + } + + gCorruptHistograms[id] = corrupt; + } +} + +} // namespace + + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: Histogram reflection helpers + +namespace { + +bool +FillRanges(JSContext *cx, JS::Handle array, Histogram *h) +{ + JS::Rooted range(cx); + for (size_t i = 0; i < h->bucket_count(); i++) { + range.setInt32(h->ranges(i)); + if (!JS_DefineElement(cx, array, i, range, JSPROP_ENUMERATE)) + return false; + } + return true; +} + +enum reflectStatus +ReflectHistogramAndSamples(JSContext *cx, JS::Handle obj, Histogram *h, + const Histogram::SampleSet &ss) +{ + mozilla::OffTheBooksMutexAutoLock locker(ss.mutex()); + + // We don't want to reflect corrupt histograms. + if (h->FindCorruption(ss, locker) != Histogram::NO_INCONSISTENCIES) { + return REFLECT_CORRUPT; + } + + if (!(JS_DefineProperty(cx, obj, "min", + h->declared_min(), JSPROP_ENUMERATE) + && JS_DefineProperty(cx, obj, "max", + h->declared_max(), JSPROP_ENUMERATE) + && JS_DefineProperty(cx, obj, "histogram_type", + h->histogram_type(), JSPROP_ENUMERATE) + && JS_DefineProperty(cx, obj, "sum", + double(ss.sum(locker)), JSPROP_ENUMERATE))) { + return REFLECT_FAILURE; + } + + const size_t count = h->bucket_count(); + JS::Rooted rarray(cx, JS_NewArrayObject(cx, count)); + if (!rarray) { + return REFLECT_FAILURE; + } + if (!(FillRanges(cx, rarray, h) + && JS_DefineProperty(cx, obj, "ranges", rarray, JSPROP_ENUMERATE))) { + return REFLECT_FAILURE; + } + + JS::Rooted counts_array(cx, JS_NewArrayObject(cx, count)); + if (!counts_array) { + return REFLECT_FAILURE; + } + if (!JS_DefineProperty(cx, obj, "counts", counts_array, JSPROP_ENUMERATE)) { + return REFLECT_FAILURE; + } + for (size_t i = 0; i < count; i++) { + if (!JS_DefineElement(cx, counts_array, i, + ss.counts(locker, i), JSPROP_ENUMERATE)) { + return REFLECT_FAILURE; + } + } + + return REFLECT_OK; +} + +enum reflectStatus +ReflectHistogramSnapshot(JSContext *cx, JS::Handle obj, Histogram *h) +{ + Histogram::SampleSet ss; + h->SnapshotSample(&ss); + return ReflectHistogramAndSamples(cx, obj, h, ss); +} + +bool +ShouldReflectHistogram(Histogram *h) +{ + const char *name = h->histogram_name().c_str(); + mozilla::Telemetry::ID id; + nsresult rv = ::GetHistogramEnumId(name, &id); + if (NS_FAILED(rv)) { + // GetHistogramEnumId generally should not fail. But a lookup + // failure shouldn't prevent us from reflecting histograms into JS. + // + // However, these two histograms are created by Histogram itself for + // tracking corruption. We have our own histograms for that, so + // ignore these two. + if (strcmp(name, "Histogram.InconsistentCountHigh") == 0 + || strcmp(name, "Histogram.InconsistentCountLow") == 0) { + return false; + } + return true; + } else { + return !gCorruptHistograms[id]; + } +} + +} + + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: class KeyedHistogram + +namespace { + +class KeyedHistogram { +public: + KeyedHistogram(const nsACString &name, const nsACString &expiration, + uint32_t histogramType, uint32_t min, uint32_t max, + uint32_t bucketCount, uint32_t dataset); + nsresult GetHistogram(const nsCString& name, Histogram** histogram, bool subsession); + Histogram* GetHistogram(const nsCString& name, bool subsession); + uint32_t GetHistogramType() const { return mHistogramType; } + nsresult GetDataset(uint32_t* dataset) const; + nsresult GetJSKeys(JSContext* cx, JS::CallArgs& args); + nsresult GetJSSnapshot(JSContext* cx, JS::Handle obj, + bool subsession, bool clearSubsession); + + void SetRecordingEnabled(bool aEnabled) { mRecordingEnabled = aEnabled; }; + bool IsRecordingEnabled() const { return mRecordingEnabled; }; + + nsresult Add(const nsCString& key, uint32_t aSample); + void Clear(bool subsession); + +private: + typedef nsBaseHashtableET KeyedHistogramEntry; + typedef AutoHashtable KeyedHistogramMapType; + KeyedHistogramMapType mHistogramMap; +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + KeyedHistogramMapType mSubsessionMap; +#endif + + static bool ReflectKeyedHistogram(KeyedHistogramEntry* entry, + JSContext* cx, + JS::Handle obj); + + const nsCString mName; + const nsCString mExpiration; + const uint32_t mHistogramType; + const uint32_t mMin; + const uint32_t mMax; + const uint32_t mBucketCount; + const uint32_t mDataset; + mozilla::Atomic mRecordingEnabled; +}; + +KeyedHistogram::KeyedHistogram(const nsACString &name, + const nsACString &expiration, + uint32_t histogramType, + uint32_t min, uint32_t max, + uint32_t bucketCount, uint32_t dataset) + : mHistogramMap() +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + , mSubsessionMap() +#endif + , mName(name) + , mExpiration(expiration) + , mHistogramType(histogramType) + , mMin(min) + , mMax(max) + , mBucketCount(bucketCount) + , mDataset(dataset) + , mRecordingEnabled(true) +{ +} + +nsresult +KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram, + bool subsession) +{ +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + KeyedHistogramMapType& map = subsession ? mSubsessionMap : mHistogramMap; +#else + KeyedHistogramMapType& map = mHistogramMap; +#endif + KeyedHistogramEntry* entry = map.GetEntry(key); + if (entry) { + *histogram = entry->mData; + return NS_OK; + } + + nsCString histogramName; +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + if (subsession) { + histogramName.AppendLiteral(SUBSESSION_HISTOGRAM_PREFIX); + } +#endif + histogramName.Append(mName); + histogramName.AppendLiteral(KEYED_HISTOGRAM_NAME_SEPARATOR); + histogramName.Append(key); + + Histogram* h; + nsresult rv = HistogramGet(histogramName.get(), mExpiration.get(), + mHistogramType, mMin, mMax, mBucketCount, + true, &h); + if (NS_FAILED(rv)) { + return rv; + } + + h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); + *histogram = h; + + entry = map.PutEntry(key); + if (MOZ_UNLIKELY(!entry)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + entry->mData = h; + return NS_OK; +} + +Histogram* +KeyedHistogram::GetHistogram(const nsCString& key, bool subsession) +{ + Histogram* h = nullptr; + if (NS_FAILED(GetHistogram(key, &h, subsession))) { + return nullptr; + } + return h; +} + +nsresult +KeyedHistogram::GetDataset(uint32_t* dataset) const +{ + MOZ_ASSERT(dataset); + *dataset = mDataset; + return NS_OK; +} + +nsresult +KeyedHistogram::Add(const nsCString& key, uint32_t sample) +{ + if (!CanRecordDataset(mDataset)) { + return NS_OK; + } + + Histogram* histogram = GetHistogram(key, false); + MOZ_ASSERT(histogram); + if (!histogram) { + return NS_ERROR_FAILURE; + } +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + Histogram* subsession = GetHistogram(key, true); + MOZ_ASSERT(subsession); + if (!subsession) { + return NS_ERROR_FAILURE; + } +#endif + + if (!IsRecordingEnabled()) { + return NS_OK; + } + + histogram->Add(sample); +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + subsession->Add(sample); +#endif + return NS_OK; +} + +void +KeyedHistogram::Clear(bool onlySubsession) +{ +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + for (auto iter = mSubsessionMap.Iter(); !iter.Done(); iter.Next()) { + iter.Get()->mData->Clear(); + } + mSubsessionMap.Clear(); + if (onlySubsession) { + return; + } +#endif + + for (auto iter = mHistogramMap.Iter(); !iter.Done(); iter.Next()) { + iter.Get()->mData->Clear(); + } + mHistogramMap.Clear(); +} + +nsresult +KeyedHistogram::GetJSKeys(JSContext* cx, JS::CallArgs& args) +{ + JS::AutoValueVector keys(cx); + if (!keys.reserve(mHistogramMap.Count())) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (auto iter = mHistogramMap.Iter(); !iter.Done(); iter.Next()) { + JS::RootedValue jsKey(cx); + const NS_ConvertUTF8toUTF16 key(iter.Get()->GetKey()); + jsKey.setString(JS_NewUCStringCopyN(cx, key.Data(), key.Length())); + if (!keys.append(jsKey)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + JS::RootedObject jsKeys(cx, JS_NewArrayObject(cx, keys)); + if (!jsKeys) { + return NS_ERROR_FAILURE; + } + + args.rval().setObject(*jsKeys); + return NS_OK; +} + +bool +KeyedHistogram::ReflectKeyedHistogram(KeyedHistogramEntry* entry, + JSContext* cx, JS::Handle obj) +{ + JS::RootedObject histogramSnapshot(cx, JS_NewPlainObject(cx)); + if (!histogramSnapshot) { + return false; + } + + if (ReflectHistogramSnapshot(cx, histogramSnapshot, entry->mData) != REFLECT_OK) { + return false; + } + + const NS_ConvertUTF8toUTF16 key(entry->GetKey()); + if (!JS_DefineUCProperty(cx, obj, key.Data(), key.Length(), + histogramSnapshot, JSPROP_ENUMERATE)) { + return false; + } + + return true; +} + +nsresult +KeyedHistogram::GetJSSnapshot(JSContext* cx, JS::Handle obj, + bool subsession, bool clearSubsession) +{ +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + KeyedHistogramMapType& map = subsession ? mSubsessionMap : mHistogramMap; +#else + KeyedHistogramMapType& map = mHistogramMap; +#endif + if (!map.ReflectIntoJS(&KeyedHistogram::ReflectKeyedHistogram, cx, obj)) { + return NS_ERROR_FAILURE; + } + +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + if (subsession && clearSubsession) { + Clear(true); + } +#endif + + return NS_OK; +} + +} // namespace + + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: KeyedHistogram helpers + +namespace { + +KeyedHistogram* +GetKeyedHistogramById(const nsACString &name) +{ + if (!gInitDone) { + return nullptr; + } + + KeyedHistogram* keyed = nullptr; + gKeyedHistograms.Get(name, &keyed); + return keyed; +} + +} // namespace + + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: JSHistogram_* functions + +namespace { + +bool +JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JSObject *obj = JS_THIS_OBJECT(cx, vp); + if (!obj) { + return false; + } + + Histogram *h = static_cast(JS_GetPrivate(obj)); + MOZ_ASSERT(h); + Histogram::ClassType type = h->histogram_type(); + + JS::CallArgs args = CallArgsFromVp(argc, vp); + + // If we don't have an argument for the count histogram, assume an increment of 1. + // Otherwise, make sure to run some sanity checks on the argument. + int32_t value = 1; + if ((type != base::CountHistogram::COUNT_HISTOGRAM) || args.length()) { + if (!args.length()) { + JS_ReportError(cx, "Expected one argument"); + return false; + } + + if (!(args[0].isNumber() || args[0].isBoolean())) { + JS_ReportError(cx, "Not a number"); + return false; + } + + if (!JS::ToInt32(cx, args[0], &value)) { + return false; + } + } + + if (TelemetryHistogram::CanRecordBase()) { + HistogramAdd(*h, value); + } + + return true; +} + +bool +JSHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JSObject *obj = JS_THIS_OBJECT(cx, vp); + if (!obj) { + return false; + } + + Histogram *h = static_cast(JS_GetPrivate(obj)); + JS::Rooted snapshot(cx, JS_NewPlainObject(cx)); + if (!snapshot) + return false; + + switch (ReflectHistogramSnapshot(cx, snapshot, h)) { + case REFLECT_FAILURE: + return false; + case REFLECT_CORRUPT: + JS_ReportError(cx, "Histogram is corrupt"); + return false; + case REFLECT_OK: + args.rval().setObject(*snapshot); + return true; + default: + MOZ_CRASH("unhandled reflection status"); + } +} + +bool +JSHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JSObject *obj = JS_THIS_OBJECT(cx, vp); + if (!obj) { + return false; + } + + bool onlySubsession = false; +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (args.length() >= 1) { + if (!args[0].isBoolean()) { + JS_ReportError(cx, "Not a boolean"); + return false; + } + + onlySubsession = JS::ToBoolean(args[0]); + } +#endif + + Histogram *h = static_cast(JS_GetPrivate(obj)); + MOZ_ASSERT(h); + if (h) { + HistogramClear(*h, onlySubsession); + } + + return true; +} + +bool +JSHistogram_Dataset(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JSObject *obj = JS_THIS_OBJECT(cx, vp); + if (!obj) { + return false; + } + + Histogram *h = static_cast(JS_GetPrivate(obj)); + mozilla::Telemetry::ID id; + nsresult rv = ::GetHistogramEnumId(h->histogram_name().c_str(), &id); + if (NS_SUCCEEDED(rv)) { + args.rval().setNumber(gHistograms[id].dataset); + return true; + } + + return false; +} + +nsresult +WrapAndReturnHistogram(Histogram *h, JSContext *cx, JS::MutableHandle ret) +{ + static const JSClass JSHistogram_class = { + "JSHistogram", /* name */ + JSCLASS_HAS_PRIVATE /* flags */ + }; + + JS::Rooted obj(cx, JS_NewObject(cx, &JSHistogram_class)); + if (!obj) + return NS_ERROR_FAILURE; + if (!(JS_DefineFunction(cx, obj, "add", JSHistogram_Add, 1, 0) + && JS_DefineFunction(cx, obj, "snapshot", JSHistogram_Snapshot, 0, 0) + && JS_DefineFunction(cx, obj, "clear", JSHistogram_Clear, 0, 0) + && JS_DefineFunction(cx, obj, "dataset", JSHistogram_Dataset, 0, 0))) { + return NS_ERROR_FAILURE; + } + JS_SetPrivate(obj, h); + ret.setObject(*obj); + return NS_OK; +} + +} // namespace + + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: JSKeyedHistogram_* functions + +namespace { + +bool +KeyedHistogram_SnapshotImpl(JSContext *cx, unsigned argc, JS::Value *vp, + bool subsession, bool clearSubsession) +{ + JSObject *obj = JS_THIS_OBJECT(cx, vp); + if (!obj) { + return false; + } + + KeyedHistogram* keyed = static_cast(JS_GetPrivate(obj)); + if (!keyed) { + return false; + } + + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (args.length() == 0) { + JS::RootedObject snapshot(cx, JS_NewPlainObject(cx)); + if (!snapshot) { + JS_ReportError(cx, "Failed to create object"); + return false; + } + + if (!NS_SUCCEEDED(keyed->GetJSSnapshot(cx, snapshot, subsession, clearSubsession))) { + JS_ReportError(cx, "Failed to reflect keyed histograms"); + return false; + } + + args.rval().setObject(*snapshot); + return true; + } + + nsAutoJSString key; + if (!args[0].isString() || !key.init(cx, args[0])) { + JS_ReportError(cx, "Not a string"); + return false; + } + + Histogram* h = nullptr; + nsresult rv = keyed->GetHistogram(NS_ConvertUTF16toUTF8(key), &h, subsession); + if (NS_FAILED(rv)) { + JS_ReportError(cx, "Failed to get histogram"); + return false; + } + + JS::RootedObject snapshot(cx, JS_NewPlainObject(cx)); + if (!snapshot) { + return false; + } + + switch (ReflectHistogramSnapshot(cx, snapshot, h)) { + case REFLECT_FAILURE: + return false; + case REFLECT_CORRUPT: + JS_ReportError(cx, "Histogram is corrupt"); + return false; + case REFLECT_OK: + args.rval().setObject(*snapshot); + return true; + default: + MOZ_CRASH("unhandled reflection status"); + } +} + +bool +JSKeyedHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JSObject *obj = JS_THIS_OBJECT(cx, vp); + if (!obj) { + return false; + } + + KeyedHistogram* keyed = static_cast(JS_GetPrivate(obj)); + if (!keyed) { + return false; + } + + JS::CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 1) { + JS_ReportError(cx, "Expected one argument"); + return false; + } + + nsAutoJSString key; + if (!args[0].isString() || !key.init(cx, args[0])) { + JS_ReportError(cx, "Not a string"); + return false; + } + + const uint32_t type = keyed->GetHistogramType(); + + // If we don't have an argument for the count histogram, assume an increment of 1. + // Otherwise, make sure to run some sanity checks on the argument. + int32_t value = 1; + if ((type != base::CountHistogram::COUNT_HISTOGRAM) || (args.length() == 2)) { + if (args.length() < 2) { + JS_ReportError(cx, "Expected two arguments for this histogram type"); + return false; + } + + if (!(args[1].isNumber() || args[1].isBoolean())) { + JS_ReportError(cx, "Not a number"); + return false; + } + + if (!JS::ToInt32(cx, args[1], &value)) { + return false; + } + } + + keyed->Add(NS_ConvertUTF16toUTF8(key), value); + return true; +} + +bool +JSKeyedHistogram_Keys(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JSObject *obj = JS_THIS_OBJECT(cx, vp); + if (!obj) { + return false; + } + + KeyedHistogram* keyed = static_cast(JS_GetPrivate(obj)); + if (!keyed) { + return false; + } + + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + return NS_SUCCEEDED(keyed->GetJSKeys(cx, args)); +} + +bool +JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp) +{ + return KeyedHistogram_SnapshotImpl(cx, argc, vp, false, false); +} + +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) +bool +JSKeyedHistogram_SubsessionSnapshot(JSContext *cx, unsigned argc, JS::Value *vp) +{ + return KeyedHistogram_SnapshotImpl(cx, argc, vp, true, false); +} +#endif + +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) +bool +JSKeyedHistogram_SnapshotSubsessionAndClear(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (args.length() != 0) { + JS_ReportError(cx, "No key arguments supported for snapshotSubsessionAndClear"); + } + + return KeyedHistogram_SnapshotImpl(cx, argc, vp, true, true); +} +#endif + +bool +JSKeyedHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JSObject *obj = JS_THIS_OBJECT(cx, vp); + if (!obj) { + return false; + } + + KeyedHistogram* keyed = static_cast(JS_GetPrivate(obj)); + if (!keyed) { + return false; + } + +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + bool onlySubsession = false; + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (args.length() >= 1) { + if (!(args[0].isNumber() || args[0].isBoolean())) { + JS_ReportError(cx, "Not a boolean"); + return false; + } + + onlySubsession = JS::ToBoolean(args[0]); + } + + keyed->Clear(onlySubsession); +#else + keyed->Clear(false); +#endif + return true; +} + +bool +JSKeyedHistogram_Dataset(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JSObject *obj = JS_THIS_OBJECT(cx, vp); + if (!obj) { + return false; + } + + KeyedHistogram* keyed = static_cast(JS_GetPrivate(obj)); + if (!keyed) { + return false; + } + + uint32_t dataset = nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN; + nsresult rv = keyed->GetDataset(&dataset);; + if (NS_FAILED(rv)) { + return false; + } + + args.rval().setNumber(dataset); + return true; +} + +nsresult +WrapAndReturnKeyedHistogram(KeyedHistogram *h, JSContext *cx, JS::MutableHandle ret) +{ + static const JSClass JSHistogram_class = { + "JSKeyedHistogram", /* name */ + JSCLASS_HAS_PRIVATE /* flags */ + }; + + JS::Rooted obj(cx, JS_NewObject(cx, &JSHistogram_class)); + if (!obj) + return NS_ERROR_FAILURE; + if (!(JS_DefineFunction(cx, obj, "add", JSKeyedHistogram_Add, 2, 0) + && JS_DefineFunction(cx, obj, "snapshot", JSKeyedHistogram_Snapshot, 1, 0) +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + && JS_DefineFunction(cx, obj, "subsessionSnapshot", JSKeyedHistogram_SubsessionSnapshot, 1, 0) + && JS_DefineFunction(cx, obj, "snapshotSubsessionAndClear", JSKeyedHistogram_SnapshotSubsessionAndClear, 0, 0) +#endif + && JS_DefineFunction(cx, obj, "keys", JSKeyedHistogram_Keys, 0, 0) + && JS_DefineFunction(cx, obj, "clear", JSKeyedHistogram_Clear, 0, 0) + && JS_DefineFunction(cx, obj, "dataset", JSKeyedHistogram_Dataset, 0, 0))) { + return NS_ERROR_FAILURE; + } + + JS_SetPrivate(obj, h); + ret.setObject(*obj); + return NS_OK; +} + +} // namespace + + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: functions related to addon histograms + +namespace { + +// Compute the name to pass into Histogram for the addon histogram +// 'name' from the addon 'id'. We can't use 'name' directly because it +// might conflict with other histograms in other addons or even with our +// own. +void +AddonHistogramName(const nsACString &id, const nsACString &name, + nsACString &ret) +{ + ret.Append(id); + ret.Append(':'); + ret.Append(name); +} + +bool +CreateHistogramForAddon(const nsACString &name, AddonHistogramInfo &info) +{ + Histogram *h; + nsresult rv = HistogramGet(PromiseFlatCString(name).get(), "never", + info.histogramType, info.min, info.max, + info.bucketCount, true, &h); + if (NS_FAILED(rv)) { + return false; + } + // Don't let this histogram be reported via the normal means + // (e.g. Telemetry.registeredHistograms); we'll make it available in + // other ways. + h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); + info.h = h; + return true; +} + +bool +AddonHistogramReflector(AddonHistogramEntryType *entry, + JSContext *cx, JS::Handle obj) +{ + AddonHistogramInfo &info = entry->mData; + + // Never even accessed the histogram. + if (!info.h) { + // Have to force creation of HISTOGRAM_FLAG histograms. + if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG) + return true; + + if (!CreateHistogramForAddon(entry->GetKey(), info)) { + return false; + } + } + + if (IsEmpty(info.h)) { + return true; + } + + JS::Rooted snapshot(cx, JS_NewPlainObject(cx)); + if (!snapshot) { + // Just consider this to be skippable. + return true; + } + switch (ReflectHistogramSnapshot(cx, snapshot, info.h)) { + case REFLECT_FAILURE: + case REFLECT_CORRUPT: + return false; + case REFLECT_OK: + const nsACString &histogramName = entry->GetKey(); + if (!JS_DefineProperty(cx, obj, PromiseFlatCString(histogramName).get(), + snapshot, JSPROP_ENUMERATE)) { + return false; + } + break; + } + return true; +} + +bool +AddonReflector(AddonEntryType *entry, JSContext *cx, JS::Handle obj) +{ + const nsACString &addonId = entry->GetKey(); + JS::Rooted subobj(cx, JS_NewPlainObject(cx)); + if (!subobj) { + return false; + } + + AddonHistogramMapType *map = entry->mData; + if (!(map->ReflectIntoJS(AddonHistogramReflector, cx, subobj) + && JS_DefineProperty(cx, obj, PromiseFlatCString(addonId).get(), + subobj, JSPROP_ENUMERATE))) { + return false; + } + return true; +} + +} // namespace + + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// EXTERNALLY VISIBLE FUNCTIONS + +namespace TelemetryHistogram { + +void InitializeGlobalState(bool canRecordBase, bool canRecordExtended) +{ + MOZ_ASSERT(!gInitDone, "TelemetryHistogram::InitializeGlobalState " + "may only be called once"); + + gCanRecordBase = canRecordBase; + gCanRecordExtended = canRecordExtended; + + // gHistogramMap should have been pre-sized correctly at the + // declaration point further up in this file. + + // Populate the static histogram name->id cache. + // Note that the histogram names are statically allocated. + for (uint32_t i = 0; i < mozilla::Telemetry::HistogramCount; i++) { + CharPtrEntryType *entry = gHistogramMap.PutEntry(gHistograms[i].id()); + entry->mData = (mozilla::Telemetry::ID) i; + } + +#ifdef DEBUG + gHistogramMap.MarkImmutable(); +#endif + + mozilla::PodArrayZero(gCorruptHistograms); + + // Create registered keyed histograms + for (size_t i = 0; i < mozilla::ArrayLength(gHistograms); ++i) { + const HistogramInfo& h = gHistograms[i]; + if (!h.keyed) { + continue; + } + + const nsDependentCString id(h.id()); + const nsDependentCString expiration(h.expiration()); + gKeyedHistograms.Put(id, new KeyedHistogram(id, expiration, h.histogramType, + h.min, h.max, h.bucketCount, h.dataset)); + } + + // Some Telemetry histograms depend on the value of C++ constants and hardcode + // their values in Histograms.json. + // We add static asserts here for those values to match so that future changes + // don't go unnoticed. + // TODO: Compare explicitly with gHistograms[].bucketCount here + // once we can make gHistograms constexpr (requires VS2015). + /*static_assert((JS::gcreason::NUM_TELEMETRY_REASONS == 100), + "NUM_TELEMETRY_REASONS is assumed to be a fixed value in Histograms.json." + " If this was an intentional change, update this assert with its value " + "and update the n_values for the following in Histograms.json: " + "GC_MINOR_REASON, GC_MINOR_REASON_LONG, GC_REASON_2");*/ + static_assert((mozilla::StartupTimeline::MAX_EVENT_ID == 16), + "MAX_EVENT_ID is assumed to be a fixed value in Histograms.json. If this" + " was an intentional change, update this assert with its value and update" + " the n_values for the following in Histograms.json:" + " STARTUP_MEASUREMENT_ERRORS"); + + gInitDone = true; +} + +void DeInitializeGlobalState() +{ + gCanRecordBase = false; + gCanRecordExtended = false; + gHistogramMap.Clear(); + gKeyedHistograms.Clear(); + gAddonMap.Clear(); + gInitDone = false; +} + +#ifdef DEBUG +bool GlobalStateHasBeenInitialized() { + return gInitDone; +} +#endif + +bool +CanRecordBase() { + return gCanRecordBase; +} + +void +SetCanRecordBase(bool b) { + gCanRecordBase = b; +} + +bool +CanRecordExtended() { + return gCanRecordExtended; +} + +void +SetCanRecordExtended(bool b) { + gCanRecordExtended = b; +} + + +void +InitHistogramRecordingEnabled() +{ + const size_t length = mozilla::ArrayLength(kRecordingInitiallyDisabledIDs); + for (size_t i = 0; i < length; i++) { + SetHistogramRecordingEnabled(kRecordingInitiallyDisabledIDs[i], false); + } +} + +void +SetHistogramRecordingEnabled(mozilla::Telemetry::ID aID, bool aEnabled) +{ + if (!IsHistogramEnumId(aID)) { + MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) must be used with an enum id"); + return; + } + + if (gHistograms[aID].keyed) { + const nsDependentCString id(gHistograms[aID].id()); + KeyedHistogram* keyed = ::GetKeyedHistogramById(id); + if (keyed) { + keyed->SetRecordingEnabled(aEnabled); + return; + } + } else { + Histogram *h; + nsresult rv = GetHistogramByEnumId(aID, &h); + if (NS_SUCCEEDED(rv)) { + h->SetRecordingEnabled(aEnabled); + return; + } + } + + MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) id not found"); +} + + +nsresult +SetHistogramRecordingEnabled(const nsACString &id, bool aEnabled) +{ + Histogram *h; + nsresult rv = GetHistogramByName(id, &h); + if (NS_SUCCEEDED(rv)) { + h->SetRecordingEnabled(aEnabled); + return NS_OK; + } + + KeyedHistogram* keyed = ::GetKeyedHistogramById(id); + if (keyed) { + keyed->SetRecordingEnabled(aEnabled); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + + +void +Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample) +{ + if (!CanRecordBase()) { + return; + } + Histogram *h; + nsresult rv = GetHistogramByEnumId(aHistogram, &h); + if (NS_SUCCEEDED(rv)) { + HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset); + } +} + +void +Accumulate(mozilla::Telemetry::ID aID, const nsCString& aKey, uint32_t aSample) +{ + if (!gInitDone || !CanRecordBase()) { + return; + } + const HistogramInfo& th = gHistograms[aID]; + KeyedHistogram* keyed + = ::GetKeyedHistogramById(nsDependentCString(th.id())); + MOZ_ASSERT(keyed); + keyed->Add(aKey, aSample); + +} + +void +Accumulate(const char* name, uint32_t sample) +{ + if (!CanRecordBase()) { + return; + } + mozilla::Telemetry::ID id; + nsresult rv = ::GetHistogramEnumId(name, &id); + if (NS_FAILED(rv)) { + return; + } + + Histogram *h; + rv = GetHistogramByEnumId(id, &h); + if (NS_SUCCEEDED(rv)) { + HistogramAdd(*h, sample, gHistograms[id].dataset); + } +} + +void +Accumulate(const char* name, const nsCString& key, uint32_t sample) +{ + if (!CanRecordBase()) { + return; + } + mozilla::Telemetry::ID id; + nsresult rv = ::GetHistogramEnumId(name, &id); + if (NS_SUCCEEDED(rv)) { + Accumulate(id, key, sample); + } +} + +void +ClearHistogram(mozilla::Telemetry::ID aId) +{ + if (!TelemetryHistogram::CanRecordBase()) { + return; + } + + Histogram *h; + nsresult rv = ::GetHistogramByEnumId(aId, &h); + if (NS_SUCCEEDED(rv) && h) { + ::HistogramClear(*h, false); + } +} + +nsresult +GetHistogramById(const nsACString &name, JSContext *cx, + JS::MutableHandle ret) +{ + Histogram *h; + nsresult rv = GetHistogramByName(name, &h); + if (NS_FAILED(rv)) + return rv; + + return WrapAndReturnHistogram(h, cx, ret); +} + +nsresult +GetKeyedHistogramById(const nsACString &name, JSContext *cx, + JS::MutableHandle ret) +{ + KeyedHistogram* keyed = nullptr; + if (!gKeyedHistograms.Get(name, &keyed)) { + return NS_ERROR_FAILURE; + } + + return WrapAndReturnKeyedHistogram(keyed, cx, ret); + +} + +const char* +GetHistogramName(mozilla::Telemetry::ID id) +{ + const HistogramInfo& h = gHistograms[id]; + return h.id(); +} + +nsresult +NewHistogram(const nsACString &name, const nsACString &expiration, + uint32_t histogramType, uint32_t min, uint32_t max, + uint32_t bucketCount, JSContext *cx, + uint8_t optArgCount, JS::MutableHandle ret) +{ + if (!IsValidHistogramName(name)) { + return NS_ERROR_INVALID_ARG; + } + + Histogram *h; + nsresult rv = HistogramGet(PromiseFlatCString(name).get(), + PromiseFlatCString(expiration).get(), + histogramType, min, max, bucketCount, + optArgCount == 3, &h); + if (NS_FAILED(rv)) + return rv; + h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); + return WrapAndReturnHistogram(h, cx, ret); + +} + +nsresult +NewKeyedHistogram(const nsACString &name, const nsACString &expiration, + uint32_t histogramType, uint32_t min, uint32_t max, + uint32_t bucketCount, JSContext *cx, + uint8_t optArgCount, JS::MutableHandle ret) +{ + if (!IsValidHistogramName(name)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = CheckHistogramArguments(histogramType, min, max, bucketCount, optArgCount == 3); + if (NS_FAILED(rv)) { + return rv; + } + + KeyedHistogram* keyed = new KeyedHistogram(name, expiration, histogramType, + min, max, bucketCount, + nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN); + if (MOZ_UNLIKELY(!gKeyedHistograms.Put(name, keyed, mozilla::fallible))) { + delete keyed; + return NS_ERROR_OUT_OF_MEMORY; + } + + return WrapAndReturnKeyedHistogram(keyed, cx, ret); + +} + +nsresult +HistogramFrom(const nsACString &name, const nsACString &existing_name, + JSContext *cx, JS::MutableHandle ret) +{ + mozilla::Telemetry::ID id; + nsresult rv = ::GetHistogramEnumId(PromiseFlatCString(existing_name).get(), &id); + if (NS_FAILED(rv)) { + return rv; + } + + Histogram* clone = CloneHistogram(name, id); + if (!clone) { + return NS_ERROR_FAILURE; + } + + return WrapAndReturnHistogram(clone, cx, ret); +} + +nsresult +CreateHistogramSnapshots(JSContext *cx, + JS::MutableHandle ret, + bool subsession, + bool clearSubsession) +{ + JS::Rooted root_obj(cx, JS_NewPlainObject(cx)); + if (!root_obj) + return NS_ERROR_FAILURE; + ret.setObject(*root_obj); + + // Ensure that all the HISTOGRAM_FLAG & HISTOGRAM_COUNT histograms have + // been created, so that their values are snapshotted. + for (size_t i = 0; i < mozilla::Telemetry::HistogramCount; ++i) { + if (gHistograms[i].keyed) { + continue; + } + const uint32_t type = gHistograms[i].histogramType; + if (type == nsITelemetry::HISTOGRAM_FLAG || + type == nsITelemetry::HISTOGRAM_COUNT) { + Histogram *h; + mozilla::DebugOnly rv + = GetHistogramByEnumId(mozilla::Telemetry::ID(i), &h); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + } + + StatisticsRecorder::Histograms hs; + StatisticsRecorder::GetHistograms(&hs); + + // We identify corrupt histograms first, rather than interspersing it + // in the loop below, to ensure that our corruption statistics don't + // depend on histogram enumeration order. + // + // Of course, we hope that all of these corruption-statistics + // histograms are not themselves corrupt... + IdentifyCorruptHistograms(hs); + + // OK, now we can actually reflect things. + JS::Rooted hobj(cx); + for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { + Histogram *h = *it; + if (!ShouldReflectHistogram(h) || IsEmpty(h) || IsExpired(h)) { + continue; + } + + Histogram* original = h; +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + if (subsession) { + h = GetSubsessionHistogram(*h); + if (!h) { + continue; + } + } +#endif + + hobj = JS_NewPlainObject(cx); + if (!hobj) { + return NS_ERROR_FAILURE; + } + switch (ReflectHistogramSnapshot(cx, hobj, h)) { + case REFLECT_CORRUPT: + // We can still hit this case even if ShouldReflectHistograms + // returns true. The histogram lies outside of our control + // somehow; just skip it. + continue; + case REFLECT_FAILURE: + return NS_ERROR_FAILURE; + case REFLECT_OK: + if (!JS_DefineProperty(cx, root_obj, original->histogram_name().c_str(), + hobj, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + +#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + if (subsession && clearSubsession) { + h->Clear(); + } +#endif + } + return NS_OK; +} + +nsresult +RegisteredHistograms(uint32_t aDataset, uint32_t *aCount, + char*** aHistograms) +{ + return GetRegisteredHistogramIds(false, aDataset, aCount, aHistograms); +} + +nsresult +RegisteredKeyedHistograms(uint32_t aDataset, uint32_t *aCount, + char*** aHistograms) +{ + return GetRegisteredHistogramIds(true, aDataset, aCount, aHistograms); +} + +nsresult +GetKeyedHistogramSnapshots(JSContext *cx, JS::MutableHandle ret) +{ + JS::Rooted obj(cx, JS_NewPlainObject(cx)); + if (!obj) { + return NS_ERROR_FAILURE; + } + + for (auto iter = gKeyedHistograms.Iter(); !iter.Done(); iter.Next()) { + JS::RootedObject snapshot(cx, JS_NewPlainObject(cx)); + if (!snapshot) { + return NS_ERROR_FAILURE; + } + + if (!NS_SUCCEEDED(iter.Data()->GetJSSnapshot(cx, snapshot, false, false))) { + return NS_ERROR_FAILURE; + } + + if (!JS_DefineProperty(cx, obj, PromiseFlatCString(iter.Key()).get(), + snapshot, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + + ret.setObject(*obj); + return NS_OK; +} + +nsresult +RegisterAddonHistogram(const nsACString &id, const nsACString &name, + uint32_t histogramType, uint32_t min, uint32_t max, + uint32_t bucketCount, uint8_t optArgCount) +{ + if (histogramType == nsITelemetry::HISTOGRAM_EXPONENTIAL || + histogramType == nsITelemetry::HISTOGRAM_LINEAR) { + if (optArgCount != 3) { + return NS_ERROR_INVALID_ARG; + } + + // Sanity checks for histogram parameters. + if (min >= max) + return NS_ERROR_ILLEGAL_VALUE; + + if (bucketCount <= 2) + return NS_ERROR_ILLEGAL_VALUE; + + if (min < 1) + return NS_ERROR_ILLEGAL_VALUE; + } else { + min = 1; + max = 2; + bucketCount = 3; + } + + AddonEntryType *addonEntry = gAddonMap.GetEntry(id); + if (!addonEntry) { + addonEntry = gAddonMap.PutEntry(id); + if (MOZ_UNLIKELY(!addonEntry)) { + return NS_ERROR_OUT_OF_MEMORY; + } + addonEntry->mData = new AddonHistogramMapType(); + } + + AddonHistogramMapType *histogramMap = addonEntry->mData; + AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name); + // Can't re-register the same histogram. + if (histogramEntry) { + return NS_ERROR_FAILURE; + } + + histogramEntry = histogramMap->PutEntry(name); + if (MOZ_UNLIKELY(!histogramEntry)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + AddonHistogramInfo &info = histogramEntry->mData; + info.min = min; + info.max = max; + info.bucketCount = bucketCount; + info.histogramType = histogramType; + + return NS_OK; +} + +nsresult +GetAddonHistogram(const nsACString &id, const nsACString &name, + JSContext *cx, JS::MutableHandle ret) +{ + AddonEntryType *addonEntry = gAddonMap.GetEntry(id); + // The given id has not been registered. + if (!addonEntry) { + return NS_ERROR_INVALID_ARG; + } + + AddonHistogramMapType *histogramMap = addonEntry->mData; + AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name); + // The given histogram name has not been registered. + if (!histogramEntry) { + return NS_ERROR_INVALID_ARG; + } + + AddonHistogramInfo &info = histogramEntry->mData; + if (!info.h) { + nsAutoCString actualName; + AddonHistogramName(id, name, actualName); + if (!::CreateHistogramForAddon(actualName, info)) { + return NS_ERROR_FAILURE; + } + } + return WrapAndReturnHistogram(info.h, cx, ret); +} + +nsresult +UnregisterAddonHistograms(const nsACString &id) +{ + AddonEntryType *addonEntry = gAddonMap.GetEntry(id); + if (addonEntry) { + // Histogram's destructor is private, so this is the best we can do. + // The histograms the addon created *will* stick around, but they + // will be deleted if and when the addon registers histograms with + // the same names. + delete addonEntry->mData; + gAddonMap.RemoveEntry(addonEntry); + } + + return NS_OK; +} + +nsresult +GetAddonHistogramSnapshots(JSContext *cx, JS::MutableHandle ret) +{ + JS::Rooted obj(cx, JS_NewPlainObject(cx)); + if (!obj) { + return NS_ERROR_FAILURE; + } + + if (!gAddonMap.ReflectIntoJS(AddonReflector, cx, obj)) { + return NS_ERROR_FAILURE; + } + ret.setObject(*obj); + return NS_OK; +} + +size_t +GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) +{ + return gAddonMap.ShallowSizeOfExcludingThis(aMallocSizeOf) + + gHistogramMap.ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +size_t +GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) +{ + StatisticsRecorder::Histograms hs; + StatisticsRecorder::GetHistograms(&hs); + size_t n = 0; + for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { + Histogram *h = *it; + n += h->SizeOfIncludingThis(aMallocSizeOf); + } + return n; +} + +} // namespace TelemetryHistogram diff --git a/toolkit/components/telemetry/TelemetryHistogram.h b/toolkit/components/telemetry/TelemetryHistogram.h new file mode 100644 index 0000000000..fdaee5b8a0 --- /dev/null +++ b/toolkit/components/telemetry/TelemetryHistogram.h @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TelemetryHistogram_h__ +#define TelemetryHistogram_h__ + +#include "mozilla/TelemetryHistogramEnums.h" + +// This module is internal to Telemetry. It encapsulates Telemetry's +// histogram accumulation and storage logic. It should only be used by +// Telemetry.cpp. These functions should not be used anywhere else. +// For the public interface to Telemetry functionality, see Telemetry.h. + +namespace TelemetryHistogram { + +void InitializeGlobalState(bool canRecordBase, bool canRecordExtended); +void DeInitializeGlobalState(); +#ifdef DEBUG +bool GlobalStateHasBeenInitialized(); +#endif + +bool CanRecordBase(); +void SetCanRecordBase(bool b); +bool CanRecordExtended(); +void SetCanRecordExtended(bool b); + +void InitHistogramRecordingEnabled(); +void SetHistogramRecordingEnabled(mozilla::Telemetry::ID aID, bool aEnabled); + +nsresult SetHistogramRecordingEnabled(const nsACString &id, bool aEnabled); + +void Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample); +void Accumulate(mozilla::Telemetry::ID aID, const nsCString& aKey, + uint32_t aSample); +void Accumulate(const char* name, uint32_t sample); +void Accumulate(const char* name, const nsCString& key, uint32_t sample); + +void +ClearHistogram(mozilla::Telemetry::ID aId); + +nsresult +GetHistogramById(const nsACString &name, JSContext *cx, + JS::MutableHandle ret); + +nsresult +GetKeyedHistogramById(const nsACString &name, JSContext *cx, + JS::MutableHandle ret); + +const char* +GetHistogramName(mozilla::Telemetry::ID id); + +nsresult +NewHistogram(const nsACString &name, const nsACString &expiration, + uint32_t histogramType, uint32_t min, uint32_t max, + uint32_t bucketCount, JSContext *cx, + uint8_t optArgCount, JS::MutableHandle ret); + +nsresult +NewKeyedHistogram(const nsACString &name, const nsACString &expiration, + uint32_t histogramType, uint32_t min, uint32_t max, + uint32_t bucketCount, JSContext *cx, + uint8_t optArgCount, JS::MutableHandle ret); + +nsresult +HistogramFrom(const nsACString &name, const nsACString &existing_name, + JSContext *cx, JS::MutableHandle ret); + +nsresult +CreateHistogramSnapshots(JSContext *cx, JS::MutableHandle ret, + bool subsession, bool clearSubsession); + +nsresult +RegisteredHistograms(uint32_t aDataset, uint32_t *aCount, + char*** aHistograms); + +nsresult +RegisteredKeyedHistograms(uint32_t aDataset, uint32_t *aCount, + char*** aHistograms); + +nsresult +GetKeyedHistogramSnapshots(JSContext *cx, JS::MutableHandle ret); + +nsresult +RegisterAddonHistogram(const nsACString &id, const nsACString &name, + uint32_t histogramType, uint32_t min, uint32_t max, + uint32_t bucketCount, uint8_t optArgCount); + +nsresult +GetAddonHistogram(const nsACString &id, const nsACString &name, + JSContext *cx, JS::MutableHandle ret); + +nsresult +UnregisterAddonHistograms(const nsACString &id); + +nsresult +GetAddonHistogramSnapshots(JSContext *cx, JS::MutableHandle ret); + +size_t +GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf); + +size_t +GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + +} // namespace TelemetryHistogram + +#endif // TelemetryHistogram_h__ diff --git a/toolkit/components/telemetry/gen-histogram-data.py b/toolkit/components/telemetry/gen-histogram-data.py index f160501d4d..d6f9faf3f2 100644 --- a/toolkit/components/telemetry/gen-histogram-data.py +++ b/toolkit/components/telemetry/gen-histogram-data.py @@ -71,7 +71,7 @@ def print_array_entry(histogram, name_index, exp_index): def write_histogram_table(histograms): table = StringTable() - print "const TelemetryHistogram gHistograms[] = {" + print "const HistogramInfo gHistograms[] = {" for histogram in histograms: name_index = table.stringIndex(histogram.name()) exp_index = table.stringIndex(histogram.expiration()) diff --git a/toolkit/components/telemetry/gen-histogram-enum.py b/toolkit/components/telemetry/gen-histogram-enum.py index 5b1b0beeef..5210cc226b 100644 --- a/toolkit/components/telemetry/gen-histogram-enum.py +++ b/toolkit/components/telemetry/gen-histogram-enum.py @@ -23,6 +23,10 @@ def main(argv): filenames = argv print banner + print "#ifndef mozilla_TelemetryHistogramEnums_h" + print "#define mozilla_TelemetryHistogramEnums_h" + print "namespace mozilla {" + print "namespace Telemetry {" print "enum ID : uint32_t {" groups = itertools.groupby(histogram_tools.from_files(filenames), @@ -63,5 +67,8 @@ def main(argv): print " HistogramLastUseCounter = 0," print " HistogramUseCounterCount = 0" print "};" + print "} // namespace mozilla" + print "} // namespace Telemetry" + print "#endif // mozilla_TelemetryHistogramEnums_h" main(sys.argv[1:]) diff --git a/toolkit/components/telemetry/moz.build b/toolkit/components/telemetry/moz.build index d9faaa6d35..5d28eb1bf6 100644 --- a/toolkit/components/telemetry/moz.build +++ b/toolkit/components/telemetry/moz.build @@ -20,6 +20,7 @@ EXPORTS.mozilla += [ SOURCES += [ 'Telemetry.cpp', + 'TelemetryHistogram.cpp', 'WebrtcTelemetry.cpp', ] diff --git a/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js b/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js index f5f9930979..2099f57780 100644 --- a/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js +++ b/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js @@ -54,7 +54,7 @@ function test_histogram(histogram_type, name, min, max, bucket_count) { do_check_eq(i, 1); } var hgrams = Telemetry.histogramSnapshots - gh = hgrams[name] + let gh = hgrams[name] do_check_eq(gh.histogram_type, histogram_type); do_check_eq(gh.min, min) @@ -631,6 +631,11 @@ function test_histogram_recording_enabled() { Assert.equal(orig.sum + 2, h.snapshot().sum, "When recording is re-enabled add should record."); + // Check that we're correctly accumulating values other than 1. + h.clear(); + h.add(3); + Assert.equal(3, h.snapshot().sum, "Recording counts greater than 1 should work."); + // Check that a histogram with recording disabled by default behaves correctly h = Telemetry.getHistogramById("TELEMETRY_TEST_COUNT_INIT_NO_RECORD"); orig = h.snapshot(); @@ -658,7 +663,7 @@ function test_keyed_histogram_recording_enabled() { // Check RecordingEnabled for keyed histograms which are recording by default const TEST_KEY = "record_foo"; - h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT"); + let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT"); h.clear(); h.add(TEST_KEY, 1);