--- a/build/Makefile.in
+++ b/build/Makefile.in
@@ -93,25 +93,32 @@ GARBAGE_DIRS += $(_VALGRIND_DIR)
$(topsrcdir)/build/valgrind/i386-redhat-linux-gnu.sup \
$(topsrcdir)/build/valgrind/x86_64-redhat-linux-gnu.sup \
$(NULL)
libs:: $(_VALGRIND_FILES)
$(INSTALL) $^ $(_VALGRIND_DIR)
endif
-ifdef ENABLE_TESTS
+ifneq (,$(ENABLE_TESTS)$(MOZ_DMD))
libs:: $(topsrcdir)/tools/rb/fix_stack_using_bpsyms.py
$(INSTALL) $< $(DIST)/bin
ifeq ($(OS_ARCH),Darwin)
libs:: $(topsrcdir)/tools/rb/fix_macosx_stack.py
$(INSTALL) $< $(DIST)/bin
endif
ifeq ($(OS_ARCH),Linux)
libs:: $(topsrcdir)/tools/rb/fix_linux_stack.py
$(INSTALL) $< $(DIST)/bin
endif
+endif # ENABLE_TESTS or MOZ_DMD
+ifdef ENABLE_TESTS
GARBAGE += $(srcdir)/automationutils.pyc
+endif # ENABLE_TESTS
-endif # ENABLE_TESTS
+ifdef MOZ_DMD
+libs:: $(topsrcdir)/memory/replace/dmd/dmd.py
+ $(INSTALL) $< $(DIST)/bin
+endif
+
--- a/config/external/nss/nss.def
+++ b/config/external/nss/nss.def
@@ -511,20 +511,22 @@ SECKEY_DecodeDERSubjectPublicKeyInfo
SECKEY_DestroyPrivateKey
SECKEY_DestroyPrivateKeyList
SECKEY_DestroyPublicKey
SECKEY_DestroySubjectPublicKeyInfo
SECKEY_ECParamsToBasePointOrderLen
SECKEY_ECParamsToKeySize
SECKEY_EncodeDERSubjectPublicKeyInfo
SECKEY_ExtractPublicKey
+SECKEY_GetPublicKeyType
SECKEY_ImportDERPublicKey
SECKEY_PublicKeyStrength
SECKEY_PublicKeyStrengthInBits
SECKEY_RSAPSSParamsTemplate DATA
+SECKEY_SignatureLen
SECMIME_DecryptionAllowed
SECMOD_AddNewModule
SECMOD_AddNewModuleEx
SECMOD_CancelWait
SECMOD_CanDeleteInternalModule
SECMOD_CloseUserDB
SECMOD_CreateModule
SECMOD_DeleteInternalModule
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -1057,23 +1057,16 @@ void HTMLMediaElement::UpdatePreloadActi
}
} else {
// Use the suggested "missing value default" of "metadata", or the value
// specified by the media.preload.default, if present.
nextAction = static_cast<PreloadAction>(preloadDefault);
}
}
- if ((mBegun || mIsRunningSelectResource) && nextAction < mPreloadAction) {
- // We've started a load or are already downloading, and the preload was
- // changed to a state where we buffer less. We don't support this case,
- // so don't change the preload behaviour.
- return;
- }
-
mPreloadAction = nextAction;
if (nextAction == HTMLMediaElement::PRELOAD_ENOUGH) {
if (mSuspendedForPreloadNone) {
// Our load was previouly suspended due to the media having preload
// value "none". The preload value has changed to preload:auto, so
// resume the load.
ResumeLoad(PRELOAD_ENOUGH);
} else {
--- a/content/media/MediaDecoderReader.h
+++ b/content/media/MediaDecoderReader.h
@@ -83,16 +83,19 @@ public:
// If aSkipToKeyframe is true, the decode should skip ahead to the
// the next keyframe at or after aTimeThreshold microseconds.
virtual void RequestVideoData(bool aSkipToNextKeyframe,
int64_t aTimeThreshold);
virtual bool HasAudio() = 0;
virtual bool HasVideo() = 0;
+ // A function that is called before ReadMetadata() call.
+ virtual void PreReadMetadata() {};
+
// Read header data for all bitstreams in the file. Fills aInfo with
// the data required to present the media, and optionally fills *aTags
// with tag metadata from the file.
// Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
virtual nsresult ReadMetadata(MediaInfo* aInfo,
MetadataTags** aTags) = 0;
// Moves the decode head to aTime microseconds. aStartTime and aEndTime
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -1416,21 +1416,31 @@ void MediaDecoderStateMachine::StartWait
AssertCurrentThreadInMonitor();
SetState(DECODER_STATE_WAIT_FOR_RESOURCES);
DECODER_LOG("StartWaitForResources");
}
void MediaDecoderStateMachine::NotifyWaitingForResourcesStatusChanged()
{
AssertCurrentThreadInMonitor();
- if (mState != DECODER_STATE_WAIT_FOR_RESOURCES ||
- mReader->IsWaitingMediaResources()) {
+ DECODER_LOG("NotifyWaitingForResourcesStatusChanged");
+ RefPtr<nsIRunnable> task(
+ NS_NewRunnableMethod(this,
+ &MediaDecoderStateMachine::DoNotifyWaitingForResourcesStatusChanged));
+ mDecodeTaskQueue->Dispatch(task);
+}
+
+void MediaDecoderStateMachine::DoNotifyWaitingForResourcesStatusChanged()
+{
+ NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
+ ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+ if (mState != DECODER_STATE_WAIT_FOR_RESOURCES) {
return;
}
- DECODER_LOG("NotifyWaitingForResourcesStatusChanged");
+ DECODER_LOG("DoNotifyWaitingForResourcesStatusChanged");
// The reader is no longer waiting for resources (say a hardware decoder),
// we can now proceed to decode metadata.
SetState(DECODER_STATE_DECODING_NONE);
ScheduleStateMachine();
}
void MediaDecoderStateMachine::Play()
{
@@ -1905,16 +1915,18 @@ MediaDecoderStateMachine::CallDecodeMeta
nsresult MediaDecoderStateMachine::DecodeMetadata()
{
AssertCurrentThreadInMonitor();
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA);
DECODER_LOG("Decoding Media Headers");
+ mReader->PreReadMetadata();
+
if (mReader->IsWaitingMediaResources()) {
StartWaitForResources();
return NS_OK;
}
nsresult res;
MediaInfo info;
{
--- a/content/media/MediaDecoderStateMachine.h
+++ b/content/media/MediaDecoderStateMachine.h
@@ -322,19 +322,19 @@ public:
bool IsShutdown();
void QueueMetadata(int64_t aPublishTime, MediaInfo* aInfo, MetadataTags* aTags);
// Returns true if we're currently playing. The decoder monitor must
// be held.
bool IsPlaying();
+ // Dispatch DoNotifyWaitingForResourcesStatusChanged task to mDecodeTaskQueue.
// Called when the reader may have acquired the hardware resources required
- // to begin decoding. The state machine may move into DECODING_METADATA if
- // appropriate. The decoder monitor must be held while calling this.
+ // to begin decoding. The decoder monitor must be held while calling this.
void NotifyWaitingForResourcesStatusChanged();
// Notifies the state machine that should minimize the number of samples
// decoded we preroll, until playback starts. The first time playback starts
// the state machine is free to return to prerolling normally. Note
// "prerolling" in this context refers to when we decode and buffer decoded
// samples in advance of when they're needed for playback.
void SetMinimizePrerollUntilPlaybackStarts();
@@ -634,16 +634,20 @@ protected:
// Called by the AudioSink to signal that all outstanding work is complete
// and the sink is shutting down.
void OnAudioSinkComplete();
// Called by the AudioSink to signal errors.
void OnAudioSinkError();
+ // The state machine may move into DECODING_METADATA if we are in
+ // DECODER_STATE_WAIT_FOR_RESOURCES.
+ void DoNotifyWaitingForResourcesStatusChanged();
+
// The decoder object that created this state machine. The state machine
// holds a strong reference to the decoder to ensure that the decoder stays
// alive once media element has started the decoder shutdown process, and has
// dropped its reference to the decoder. This enables the state machine to
// keep using the decoder's monitor until the state machine has finished
// shutting down, without fear of the monitor being destroyed. After
// shutting down, the state machine will then release this reference,
// causing the decoder to be destroyed. This is accessed on the decode,
--- a/content/media/MediaResource.cpp
+++ b/content/media/MediaResource.cpp
@@ -78,17 +78,16 @@ ChannelMediaResource::ChannelMediaResour
nsIURI* aURI,
const nsACString& aContentType)
: BaseMediaResource(aDecoder, aChannel, aURI, aContentType),
mOffset(0), mSuspendCount(0),
mReopenOnError(false), mIgnoreClose(false),
mCacheStream(MOZ_THIS_IN_INITIALIZER_LIST()),
mLock("ChannelMediaResource.mLock"),
mIgnoreResume(false),
- mSeekingForMetadata(false),
mIsTransportSeekable(true)
{
#ifdef PR_LOGGING
if (!gMediaResourceLog) {
gMediaResourceLog = PR_NewLogModule("MediaResource");
}
#endif
}
@@ -353,21 +352,17 @@ ChannelMediaResource::OnStartRequest(nsI
{
MutexAutoLock lock(mLock);
mIsTransportSeekable = seekable;
mChannelStatistics->Start();
}
mReopenOnError = false;
- // If we are seeking to get metadata, because we are playing an OGG file,
- // ignore if the channel gets closed without us suspending it explicitly. We
- // don't want to tell the element that the download has finished whereas we
- // just happended to have reached the end of the media while seeking.
- mIgnoreClose = mSeekingForMetadata;
+ mIgnoreClose = false;
if (mSuspendCount > 0) {
// Re-suspend the channel if it needs to be suspended
// No need to call PossiblySuspend here since the channel is
// definitely in the right state for us in OnStartRequest.
mChannel->Suspend();
mIgnoreResume = false;
}
@@ -803,26 +798,16 @@ nsresult ChannelMediaResource::Seek(int3
{
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
CMLOG("Seek requested for aOffset [%lld] for decoder [%p]",
aOffset, mDecoder);
return mCacheStream.Seek(aWhence, aOffset);
}
-void ChannelMediaResource::StartSeekingForMetadata()
-{
- mSeekingForMetadata = true;
-}
-
-void ChannelMediaResource::EndSeekingForMetadata()
-{
- mSeekingForMetadata = false;
-}
-
int64_t ChannelMediaResource::Tell()
{
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
return mCacheStream.Tell();
}
nsresult ChannelMediaResource::GetCachedRanges(nsTArray<MediaByteRange>& aRanges)
@@ -1226,18 +1211,16 @@ public:
// Other thread
virtual void SetReadMode(MediaCacheStream::ReadMode aMode) {}
virtual void SetPlaybackRate(uint32_t aBytesPerSecond) {}
virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
virtual nsresult ReadAt(int64_t aOffset, char* aBuffer,
uint32_t aCount, uint32_t* aBytes);
virtual nsresult Seek(int32_t aWhence, int64_t aOffset);
- virtual void StartSeekingForMetadata() {};
- virtual void EndSeekingForMetadata() {};
virtual int64_t Tell();
// Any thread
virtual void Pin() {}
virtual void Unpin() {}
virtual double GetDownloadRate(bool* aIsReliable)
{
// The data's all already here
--- a/content/media/MediaResource.h
+++ b/content/media/MediaResource.h
@@ -306,18 +306,16 @@ public:
//
// The default strategy does not do any seeking - the only issue is
// a blocked read which it handles by causing the listener to close
// the pipe, as per the http case.
//
// The file strategy doesn't block for any great length of time so
// is fine for a no-op cancel.
virtual nsresult Seek(int32_t aWhence, int64_t aOffset) = 0;
- virtual void StartSeekingForMetadata() = 0;
- virtual void EndSeekingForMetadata() = 0;
// Report the current offset in bytes from the start of the stream.
virtual int64_t Tell() = 0;
// Moves any existing channel loads into the background, so that they don't
// block the load event. Any new loads initiated (for example to seek)
// will also be in the background.
virtual void MoveLoadsToBackground() {}
// Ensures that the value returned by IsSuspendedByCache below is up to date
// (i.e. the cache has examined this stream at least once).
@@ -583,18 +581,16 @@ public:
// Other thread
virtual void SetReadMode(MediaCacheStream::ReadMode aMode);
virtual void SetPlaybackRate(uint32_t aBytesPerSecond);
virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
virtual nsresult ReadAt(int64_t offset, char* aBuffer,
uint32_t aCount, uint32_t* aBytes);
virtual nsresult Seek(int32_t aWhence, int64_t aOffset);
- virtual void StartSeekingForMetadata();
- virtual void EndSeekingForMetadata();
virtual int64_t Tell();
// Any thread
virtual void Pin();
virtual void Unpin();
virtual double GetDownloadRate(bool* aIsReliable);
virtual int64_t GetLength();
virtual int64_t GetNextCachedData(int64_t aOffset);
@@ -709,19 +705,16 @@ protected:
Mutex mLock;
nsRefPtr<MediaChannelStatistics> mChannelStatistics;
// True if we couldn't suspend the stream and we therefore don't want
// to resume later. This is usually due to the channel not being in the
// isPending state at the time of the suspend request.
bool mIgnoreResume;
- // True if we are seeking to get the real duration of the file.
- bool mSeekingForMetadata;
-
// Start and end offset of the bytes to be requested.
MediaByteRange mByteRange;
// True if the stream can seek into unbuffered ranged, i.e. if the
// connection supports byte range requests.
bool mIsTransportSeekable;
};
--- a/content/media/RtspMediaResource.h
+++ b/content/media/RtspMediaResource.h
@@ -126,20 +126,16 @@ public:
MOZ_OVERRIDE {
return NS_OK;
}
// dummy
virtual nsresult Seek(int32_t aWhence, int64_t aOffset) MOZ_OVERRIDE {
return NS_OK;
}
// dummy
- virtual void StartSeekingForMetadata() MOZ_OVERRIDE {}
- // dummy
- virtual void EndSeekingForMetadata() MOZ_OVERRIDE {}
- // dummy
virtual int64_t Tell() MOZ_OVERRIDE { return 0; }
// Any thread
virtual void Pin() MOZ_OVERRIDE {}
virtual void Unpin() MOZ_OVERRIDE {}
virtual bool IsSuspendedByCache() MOZ_OVERRIDE { return mIsSuspend; }
--- a/content/media/mediasource/MediaSourceResource.h
+++ b/content/media/mediasource/MediaSourceResource.h
@@ -34,18 +34,16 @@ public:
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() MOZ_OVERRIDE { UNIMPLEMENTED(); return nullptr; }
virtual bool CanClone() MOZ_OVERRIDE { UNIMPLEMENTED(); return false; }
virtual already_AddRefed<MediaResource> CloneData(MediaDecoder* aDecoder) MOZ_OVERRIDE { UNIMPLEMENTED(); return nullptr; }
virtual void SetReadMode(MediaCacheStream::ReadMode aMode) MOZ_OVERRIDE { UNIMPLEMENTED(); }
virtual void SetPlaybackRate(uint32_t aBytesPerSecond) MOZ_OVERRIDE { UNIMPLEMENTED(); }
virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) MOZ_OVERRIDE { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) MOZ_OVERRIDE { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
virtual nsresult Seek(int32_t aWhence, int64_t aOffset) MOZ_OVERRIDE { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
- virtual void StartSeekingForMetadata() MOZ_OVERRIDE { UNIMPLEMENTED(); }
- virtual void EndSeekingForMetadata() MOZ_OVERRIDE { UNIMPLEMENTED(); }
virtual int64_t Tell() MOZ_OVERRIDE { UNIMPLEMENTED(); return -1; }
virtual void Pin() MOZ_OVERRIDE { UNIMPLEMENTED(); }
virtual void Unpin() MOZ_OVERRIDE { UNIMPLEMENTED(); }
virtual double GetDownloadRate(bool* aIsReliable) MOZ_OVERRIDE { UNIMPLEMENTED(); *aIsReliable = false; return 0; }
virtual int64_t GetLength() MOZ_OVERRIDE { UNIMPLEMENTED(); return -1; }
virtual int64_t GetNextCachedData(int64_t aOffset) MOZ_OVERRIDE { UNIMPLEMENTED(); return -1; }
virtual int64_t GetCachedDataEnd(int64_t aOffset) MOZ_OVERRIDE { UNIMPLEMENTED(); return -1; }
virtual bool IsDataCachedToEndOfResource(int64_t aOffset) MOZ_OVERRIDE { UNIMPLEMENTED(); return false; }
--- a/content/media/mediasource/SourceBufferResource.h
+++ b/content/media/mediasource/SourceBufferResource.h
@@ -52,18 +52,16 @@ public:
virtual void Resume() MOZ_OVERRIDE { UNIMPLEMENTED(); }
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() MOZ_OVERRIDE { UNIMPLEMENTED(); return nullptr; }
virtual already_AddRefed<MediaResource> CloneData(MediaDecoder* aDecoder) MOZ_OVERRIDE { UNIMPLEMENTED(); return nullptr; }
virtual void SetReadMode(MediaCacheStream::ReadMode aMode) MOZ_OVERRIDE { UNIMPLEMENTED(); }
virtual void SetPlaybackRate(uint32_t aBytesPerSecond) MOZ_OVERRIDE { UNIMPLEMENTED(); }
virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) MOZ_OVERRIDE;
virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) MOZ_OVERRIDE;
virtual nsresult Seek(int32_t aWhence, int64_t aOffset) MOZ_OVERRIDE;
- virtual void StartSeekingForMetadata() MOZ_OVERRIDE { UNIMPLEMENTED(); }
- virtual void EndSeekingForMetadata() MOZ_OVERRIDE { UNIMPLEMENTED(); }
virtual int64_t Tell() MOZ_OVERRIDE { return mOffset; }
virtual void Pin() MOZ_OVERRIDE { UNIMPLEMENTED(); }
virtual void Unpin() MOZ_OVERRIDE { UNIMPLEMENTED(); }
virtual double GetDownloadRate(bool* aIsReliable) MOZ_OVERRIDE { UNIMPLEMENTED(); *aIsReliable = false; return 0; }
virtual int64_t GetLength() MOZ_OVERRIDE { return mInputBuffer.GetLength(); }
virtual int64_t GetNextCachedData(int64_t aOffset) MOZ_OVERRIDE {
ReentrantMonitorAutoEnter mon(mMonitor);
MOZ_ASSERT(aOffset >= 0);
--- a/content/media/ogg/OggReader.cpp
+++ b/content/media/ogg/OggReader.cpp
@@ -484,31 +484,29 @@ nsresult OggReader::ReadMetadata(MediaIn
MediaResource* resource = mDecoder->GetResource();
if (mDecoder->GetMediaDuration() == -1 &&
!mDecoder->IsShutdown() &&
resource->GetLength() >= 0 &&
mDecoder->IsMediaSeekable())
{
// We didn't get a duration from the index or a Content-Duration header.
// Seek to the end of file to find the end time.
- mDecoder->GetResource()->StartSeekingForMetadata();
int64_t length = resource->GetLength();
NS_ASSERTION(length > 0, "Must have a content length to get end time");
int64_t endTime = 0;
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
endTime = RangeEndTime(length);
}
if (endTime != -1) {
mDecoder->SetMediaEndTime(endTime);
LOG(PR_LOG_DEBUG, ("Got Ogg duration from seeking to end %lld", endTime));
}
- mDecoder->GetResource()->EndSeekingForMetadata();
}
} else {
return NS_ERROR_FAILURE;
}
*aInfo = mInfo;
return NS_OK;
}
--- a/content/media/omx/MediaCodecReader.cpp
+++ b/content/media/omx/MediaCodecReader.cpp
@@ -280,16 +280,17 @@ MediaCodecReader::ProcessCachedDataTask:
MediaCodecReader::MediaCodecReader(AbstractMediaDecoder* aDecoder)
: MediaOmxCommonReader(aDecoder)
, mColorConverterBufferSize(0)
, mExtractor(nullptr)
, mParserMonitor("MediaCodecReader::mParserMonitor")
, mParseDataFromCache(true)
, mNextParserPosition(INT64_C(0))
, mParsedDataLength(INT64_C(0))
+ , mIsWaitingResources(false)
{
mHandler = new MessageHandler(this);
mVideoListener = new VideoResourceListener(this);
}
MediaCodecReader::~MediaCodecReader()
{
MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
@@ -299,17 +300,24 @@ nsresult
MediaCodecReader::Init(MediaDecoderReader* aCloneDonor)
{
return NS_OK;
}
bool
MediaCodecReader::IsWaitingMediaResources()
{
- return mVideoTrack.mCodec != nullptr && !mVideoTrack.mCodec->allocated();
+ return mIsWaitingResources;
+}
+
+void
+MediaCodecReader::UpdateIsWaitingMediaResources()
+{
+ mIsWaitingResources = (mVideoTrack.mCodec != nullptr) &&
+ (!mVideoTrack.mCodec->allocated());
}
bool
MediaCodecReader::IsDormantNeeded()
{
return mVideoTrack.mSource != nullptr;
}
@@ -642,30 +650,42 @@ MediaCodecReader::ParseDataSegment(const
MOZ_ASSERT(mDecoder);
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
mDecoder->UpdateEstimatedMediaDuration(duration);
}
return true;
}
+void
+MediaCodecReader::PreReadMetadata()
+{
+ UpdateIsWaitingMediaResources();
+}
+
+
nsresult
MediaCodecReader::ReadMetadata(MediaInfo* aInfo,
MetadataTags** aTags)
{
MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
if (!ReallocateResources()) {
return NS_ERROR_FAILURE;
}
if (!TriggerIncrementalParser()) {
return NS_ERROR_FAILURE;
}
+ // Bug 1050667, both MediaDecoderStateMachine and MediaCodecReader
+ // relies on IsWaitingMediaResources() function. And the waiting state will be
+ // changed by binder thread, so we store the waiting state in a cache value to
+ // make them in the same waiting state.
+ UpdateIsWaitingMediaResources();
if (IsWaitingMediaResources()) {
return NS_OK;
}
// TODO: start streaming
if (!UpdateDuration()) {
return NS_ERROR_FAILURE;
--- a/content/media/omx/MediaCodecReader.h
+++ b/content/media/omx/MediaCodecReader.h
@@ -73,16 +73,17 @@ public:
int64_t aTimeThreshold) MOZ_OVERRIDE;
// Disptach a DecodeAduioDataTask to decode video data.
virtual void RequestAudioData() MOZ_OVERRIDE;
virtual bool HasAudio();
virtual bool HasVideo();
+ virtual void PreReadMetadata() MOZ_OVERRIDE;
// Read header data for all bitstreams in the file. Fills aInfo with
// the data required to present the media, and optionally fills *aTags
// with tag metadata from the file.
// Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
virtual nsresult ReadMetadata(MediaInfo* aInfo,
MetadataTags** aTags);
// Moves the decode head to aTime microseconds. aStartTime and aEndTime
@@ -147,17 +148,24 @@ protected:
// Receive a notify from ResourceListener.
// Called on Binder thread.
virtual void codecReserved(Track& aTrack);
virtual void codecCanceled(Track& aTrack);
virtual bool CreateExtractor();
+ // Check the underlying HW resource is available and store the result in
+ // mIsWaitingResources.
+ void UpdateIsWaitingMediaResources();
+
android::sp<android::MediaExtractor> mExtractor;
+ // A cache value updated by UpdateIsWaitingMediaResources(), makes the
+ // "waiting resources state" is synchronous to StateMachine.
+ bool mIsWaitingResources;
private:
// An intermediary class that can be managed by android::sp<T>.
// Redirect onMessageReceived() to MediaCodecReader.
class MessageHandler : public android::AHandler
{
public:
MessageHandler(MediaCodecReader* aReader);
--- a/content/media/omx/MediaOmxReader.cpp
+++ b/content/media/omx/MediaOmxReader.cpp
@@ -142,16 +142,17 @@ MediaOmxReader::MediaOmxReader(AbstractM
, mHasVideo(false)
, mHasAudio(false)
, mVideoSeekTimeUs(-1)
, mAudioSeekTimeUs(-1)
, mSkipCount(0)
, mUseParserDuration(false)
, mLastParserDuration(-1)
, mIsShutdown(false)
+ , mIsWaitingResources(false)
{
#ifdef PR_LOGGING
if (!gMediaDecoderLog) {
gMediaDecoderLog = PR_NewLogModule("MediaDecoder");
}
#endif
mAudioChannel = dom::AudioChannelService::GetDefaultAudioChannel();
@@ -183,20 +184,26 @@ void MediaOmxReader::Shutdown()
ReleaseMediaResources();
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &MediaOmxReader::ReleaseDecoder);
NS_DispatchToMainThread(event);
}
bool MediaOmxReader::IsWaitingMediaResources()
{
- if (!mOmxDecoder.get()) {
- return false;
+ return mIsWaitingResources;
+}
+
+void MediaOmxReader::UpdateIsWaitingMediaResources()
+{
+ if (mOmxDecoder.get()) {
+ mIsWaitingResources = mOmxDecoder->IsWaitingMediaResources();
+ } else {
+ mIsWaitingResources = false;
}
- return mOmxDecoder->IsWaitingMediaResources();
}
bool MediaOmxReader::IsDormantNeeded()
{
if (!mOmxDecoder.get()) {
return false;
}
return mOmxDecoder->IsDormantNeeded();
@@ -233,16 +240,21 @@ nsresult MediaOmxReader::InitOmxDecoder(
mOmxDecoder = new OmxDecoder(mDecoder->GetResource(), mDecoder);
if (!mOmxDecoder->Init(mExtractor)) {
return NS_ERROR_FAILURE;
}
}
return NS_OK;
}
+void MediaOmxReader::PreReadMetadata()
+{
+ UpdateIsWaitingMediaResources();
+}
+
nsresult MediaOmxReader::ReadMetadata(MediaInfo* aInfo,
MetadataTags** aTags)
{
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
EnsureActive();
*aTags = nullptr;
@@ -256,23 +268,31 @@ nsresult MediaOmxReader::ReadMetadata(Me
if (isMP3) {
// When read sdcard's file on b2g platform at constructor,
// the mDecoder->GetResource()->GetLength() would return -1.
// Delay set the total duration on this function.
mMP3FrameParser.SetLength(mDecoder->GetResource()->GetLength());
ProcessCachedData(0, true);
}
- if (!mOmxDecoder->TryLoad()) {
+ if (!mOmxDecoder->AllocateMediaResources()) {
return NS_ERROR_FAILURE;
}
-
+ // Bug 1050667, both MediaDecoderStateMachine and MediaOmxReader
+ // relies on IsWaitingMediaResources() function. And the waiting state will be
+ // changed by binder thread, so we store the waiting state in a cache value to
+ // make them in consistent state.
+ UpdateIsWaitingMediaResources();
if (IsWaitingMediaResources()) {
return NS_OK;
}
+ // After resources are available, set the metadata.
+ if (!mOmxDecoder->EnsureMetadata()) {
+ return NS_ERROR_FAILURE;
+ }
if (isMP3 && mMP3FrameParser.IsMP3()) {
int64_t duration = mMP3FrameParser.GetDuration();
// The MP3FrameParser may reported a duration;
// return -1 if no frame has been parsed.
if (duration >= 0) {
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
mUseParserDuration = true;
--- a/content/media/omx/MediaOmxReader.h
+++ b/content/media/omx/MediaOmxReader.h
@@ -42,26 +42,36 @@ class MediaOmxReader : public MediaOmxCo
int64_t mLastParserDuration;
int32_t mSkipCount;
bool mUseParserDuration;
bool mIsShutdown;
protected:
android::sp<android::OmxDecoder> mOmxDecoder;
android::sp<android::MediaExtractor> mExtractor;
MP3FrameParser mMP3FrameParser;
+
+ // A cache value updated by UpdateIsWaitingMediaResources(), makes the
+ // "waiting resources state" is synchronous to StateMachine.
+ bool mIsWaitingResources;
+
// Called by ReadMetadata() during MediaDecoderStateMachine::DecodeMetadata()
// on decode thread. It create and initialize the OMX decoder including
// setting up custom extractor. The extractor provide the essential
// information used for creating OMX decoder such as video/audio codec.
virtual nsresult InitOmxDecoder();
// Called inside DecodeVideoFrame, DecodeAudioData, ReadMetadata and Seek
// to activate the decoder automatically.
virtual void EnsureActive();
+ // Check the underlying HW resources are available and store the result in
+ // mIsWaitingResources. The result might be changed by binder thread,
+ // Can only called by ReadMetadata.
+ void UpdateIsWaitingMediaResources();
+
public:
MediaOmxReader(AbstractMediaDecoder* aDecoder);
~MediaOmxReader();
virtual nsresult Init(MediaDecoderReader* aCloneDonor);
virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
@@ -74,21 +84,23 @@ public:
return mHasAudio;
}
virtual bool HasVideo()
{
return mHasVideo;
}
- virtual bool IsWaitingMediaResources();
+ // Return mIsWaitingResources.
+ virtual bool IsWaitingMediaResources() MOZ_OVERRIDE;
virtual bool IsDormantNeeded();
virtual void ReleaseMediaResources();
+ virtual void PreReadMetadata() MOZ_OVERRIDE;
virtual nsresult ReadMetadata(MediaInfo* aInfo,
MetadataTags** aTags);
virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
virtual bool IsMediaSeekable() MOZ_OVERRIDE;
virtual void SetIdle() MOZ_OVERRIDE;
--- a/content/media/omx/OmxDecoder.cpp
+++ b/content/media/omx/OmxDecoder.cpp
@@ -157,29 +157,17 @@ bool OmxDecoder::Init(sp<MediaExtractor>
// mAudioTrack is be used by OMXCodec. For offloaded audio track, using same
// object gives undetermined behavior. So get a new track
mAudioOffloadTrack = extractor->getTrack(audioTrackIndex);
#endif
}
return true;
}
-bool OmxDecoder::TryLoad() {
-
- if (!AllocateMediaResources()) {
- return false;
- }
-
- //check if video is waiting resources
- if (mVideoSource.get()) {
- if (mVideoSource->IsWaitingResources()) {
- return true;
- }
- }
-
+bool OmxDecoder::EnsureMetadata() {
// calculate duration
int64_t totalDurationUs = 0;
int64_t durationUs = 0;
if (mVideoTrack.get() && mVideoTrack->getFormat()->findInt64(kKeyDuration, &durationUs)) {
if (durationUs > totalDurationUs)
totalDurationUs = durationUs;
}
if (mAudioTrack.get()) {
--- a/content/media/omx/OmxDecoder.h
+++ b/content/media/omx/OmxDecoder.h
@@ -147,19 +147,26 @@ public:
// MediaExtractor::getTrackMetaData().
// In general cases, the extractor is created by a sp<DataSource> which
// connect to a MediaResource like ChannelMediaResource.
// Data is read from the MediaResource to create a suitable extractor which
// extracts data from a container.
// Note: RTSP requires a custom extractor because it doesn't have a container.
bool Init(sp<MediaExtractor>& extractor);
- bool TryLoad();
bool IsDormantNeeded();
+
+ // Called after resources(video/audio codec) are allocated, set the
+ // mDurationUs and video/audio metadata.
+ bool EnsureMetadata();
+
+ // Only called by MediaOmxDecoder, do not call this function arbitrarily.
+ // See bug 1050667.
bool IsWaitingMediaResources();
+
bool AllocateMediaResources();
void ReleaseMediaResources();
bool SetVideoFormat();
bool SetAudioFormat();
void ReleaseDecoder();
void GetDuration(int64_t *durationUs) {
--- a/content/media/test/test_preload_actions.html
+++ b/content/media/test/test_preload_actions.html
@@ -381,44 +381,16 @@ var tests = [
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("loadeddata", this.loadeddata, false);
v.src = test.name; // Causes implicit load.
document.body.appendChild(v);
},
name: "test11",
},
- /*{
- // 12. Change preload value from auto to metadata after load started,
- // should still do full load, should not halt after metadata only.
- // disable this test since the spec is no longer found in the document
- // http://dev.w3.org/html5/spec-preview/media-elements.html
- canplaythrough:
- function(e) {
- var v = e.target;
- is(v._gotLoadedMetaData, true, "(12) Must get loadedmetadata.");
- is(v._gotLoadStart, true, "(12) Must get loadstart.");
- maybeFinish(v, 12);
- },
-
- setup:
- function(v) {
- v._gotLoadStart = false;
- v._gotLoadedMetaData = false;
- v.preload = "auto";
- v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
- v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
- v.addEventListener("canplaythrough", this.canplaythrough, false);
- v.src = test.name; // Causes implicit load.
- document.body.appendChild(v);
- v.preload = "metadata";
- },
-
- name: "test12",
- },*/
{
// 13. Change preload value from auto to none after specifying a src
// should load according to preload none, no buffering should have taken place
suspend:
function(e) {
var v = e.target;
is(v._gotLoadStart, true, "(13) Must get loadstart.");
is(v._gotLoadedMetaData, false, "(13) Must not get loadedmetadata.");
@@ -563,38 +535,46 @@ var tests = [
v.play(); // Should cause preload:none to be overridden.
},
name: "test18",
},
{
// 19. Set preload='auto' on first video source then switching preload='none' and swapping the video source to another.
// The second video should not start playing as it's preload state has been changed to 'none' from 'auto'
- loadedmetadata: function(e) {
- var v = e.target;
- is(v.preload === "auto", true, "(19) preload is initially auto");
- setTimeout(function() {
+ setup:
+ function(v) {
+ v.preload = "auto";
+ v.src = test.name;
+ // add a listener for when the video has loaded, so we know preload auto has worked
+ v.onloadedmetadata = function() {
+ is(v.preload, "auto", "(19) preload is initially auto");
// set preload state to none and switch video sources
v.preload="none";
v.src = test.name + "?asdf";
- setTimeout(function() {
- is(v.readyState === 0, true, "(19) no buffering has taken place");
- maybeFinish(v, 19);
- }, 2000);
- }, 2000);
- },
+ v.onloadedmetadata = function() {
+ ok(false, "(19) 'loadedmetadata' shouldn't fire when preload is none");
+ }
- setup:
- function(v) {
- var that = this;
- v.preload = "auto";
- v.src = test.name;
- // add a listener for when the video has loaded, so we know preload auto has worked
- v.addEventListener( "loadedmetadata", this.loadedmetadata, false);
+ var ontimeout = function() {
+ v.removeEventListener("suspend", onsuspend, false);
+ ok(false, "(19) 'suspend' should've fired");
+ maybeFinish(v, 19);
+ }
+ var cancel = setTimeout(ontimeout, 10000);
+
+ var onsuspend = function() {
+ v.removeEventListener("suspend", onsuspend, false);
+ clearTimeout(cancel);
+ is(v.readyState, 0, "(19) no buffering has taken place");
+ maybeFinish(v, 19);
+ }
+ v.addEventListener("suspend", onsuspend, false);
+ }
document.body.appendChild(v);
},
name: "test19",
}
];
var iterationCount = 0;
--- a/content/media/webrtc/MediaEngineWebRTCVideo.cpp
+++ b/content/media/webrtc/MediaEngineWebRTCVideo.cpp
@@ -969,16 +969,18 @@ MediaEngineWebRTCVideoSource::OnTakePict
new GenerateBlobRunnable(mPhotoCallbacks, aData, aLength, aMimeType));
}
}
uint32_t
MediaEngineWebRTCVideoSource::ConvertPixelFormatToFOURCC(int aFormat)
{
switch (aFormat) {
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ return libyuv::FOURCC_BGRA;
case HAL_PIXEL_FORMAT_YCrCb_420_SP:
return libyuv::FOURCC_NV21;
case HAL_PIXEL_FORMAT_YV12:
return libyuv::FOURCC_YV12;
default: {
LOG((" xxxxx Unknown pixel format %d", aFormat));
MOZ_ASSERT(false, "Unknown pixel format.");
return libyuv::FOURCC_ANY;
--- a/content/media/webspeech/recognition/SpeechGrammarList.cpp
+++ b/content/media/webspeech/recognition/SpeechGrammarList.cpp
@@ -3,43 +3,56 @@
/* 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 "SpeechGrammarList.h"
#include "mozilla/dom/SpeechGrammarListBinding.h"
#include "mozilla/ErrorResult.h"
+#include "nsCOMPtr.h"
+#include "nsXPCOMStrings.h"
+#include "SpeechRecognition.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(SpeechGrammarList, mParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(SpeechGrammarList)
NS_IMPL_CYCLE_COLLECTING_RELEASE(SpeechGrammarList)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SpeechGrammarList)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
-SpeechGrammarList::SpeechGrammarList(nsISupports* aParent)
+SpeechGrammarList::SpeechGrammarList(nsISupports* aParent, nsISpeechRecognitionService* aRecognitionService)
: mParent(aParent)
{
+ this->mRecognitionService = aRecognitionService;
SetIsDOMBinding();
}
SpeechGrammarList::~SpeechGrammarList()
{
}
-SpeechGrammarList*
+already_AddRefed<SpeechGrammarList>
SpeechGrammarList::Constructor(const GlobalObject& aGlobal,
ErrorResult& aRv)
{
- return new SpeechGrammarList(aGlobal.GetAsSupports());
+ nsCOMPtr<nsISpeechRecognitionService> recognitionService;
+ recognitionService = GetSpeechRecognitionService();
+ if (!recognitionService) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ } else {
+ nsRefPtr<SpeechGrammarList> speechGrammarList =
+ new SpeechGrammarList(aGlobal.GetAsSupports(), recognitionService);
+ return speechGrammarList.forget();
+ }
}
JSObject*
SpeechGrammarList::WrapObject(JSContext* aCx)
{
return SpeechGrammarListBinding::Wrap(aCx, this);
}
@@ -71,17 +84,17 @@ SpeechGrammarList::AddFromURI(const nsAS
return;
}
void
SpeechGrammarList::AddFromString(const nsAString& aString,
const Optional<float>& aWeight,
ErrorResult& aRv)
{
- aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ mRecognitionService->ValidateAndSetGrammarList(this, nullptr);
return;
}
already_AddRefed<SpeechGrammar>
SpeechGrammarList::IndexedGetter(uint32_t aIndex, bool& aPresent,
ErrorResult& aRv)
{
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
--- a/content/media/webspeech/recognition/SpeechGrammarList.h
+++ b/content/media/webspeech/recognition/SpeechGrammarList.h
@@ -6,16 +6,17 @@
#ifndef mozilla_dom_SpeechGrammarList_h
#define mozilla_dom_SpeechGrammarList_h
#include "mozilla/Attributes.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsWrapperCache.h"
+#include "nsISpeechRecognitionService.h"
struct JSContext;
namespace mozilla {
class ErrorResult;
namespace dom {
@@ -23,38 +24,39 @@ namespace dom {
class GlobalObject;
class SpeechGrammar;
template<typename> class Optional;
class SpeechGrammarList MOZ_FINAL : public nsISupports,
public nsWrapperCache
{
public:
- explicit SpeechGrammarList(nsISupports* aParent);
+ explicit SpeechGrammarList(nsISupports* aParent, nsISpeechRecognitionService* aRecognitionService);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(SpeechGrammarList)
- SpeechGrammarList* Constructor(const GlobalObject& aGlobal,
- ErrorResult& aRv);
+ static already_AddRefed<SpeechGrammarList> Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
nsISupports* GetParentObject() const;
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
uint32_t Length() const;
already_AddRefed<SpeechGrammar> Item(uint32_t aIndex, ErrorResult& aRv);
void AddFromURI(const nsAString& aSrc, const Optional<float>& aWeight, ErrorResult& aRv);
void AddFromString(const nsAString& aString, const Optional<float>& aWeight, ErrorResult& aRv);
already_AddRefed<SpeechGrammar> IndexedGetter(uint32_t aIndex, bool& aPresent, ErrorResult& aRv);
+ nsCOMPtr<nsISpeechRecognitionService> mRecognitionService;
+
private:
~SpeechGrammarList();
nsCOMPtr<nsISupports> mParent;
};
} // namespace dom
} // namespace mozilla
--- a/content/media/webspeech/recognition/SpeechRecognition.cpp
+++ b/content/media/webspeech/recognition/SpeechRecognition.cpp
@@ -52,16 +52,46 @@ GetSpeechRecognitionLog()
return sLog;
}
#define SR_LOG(...) PR_LOG(GetSpeechRecognitionLog(), PR_LOG_DEBUG, (__VA_ARGS__))
#else
#define SR_LOG(...)
#endif
+already_AddRefed<nsISpeechRecognitionService>
+GetSpeechRecognitionService()
+{
+ nsAutoCString speechRecognitionServiceCID;
+
+ nsAdoptingCString prefValue =
+ Preferences::GetCString(PREFERENCE_DEFAULT_RECOGNITION_SERVICE);
+ nsAutoCString speechRecognitionService;
+
+ if (!prefValue.get() || prefValue.IsEmpty()) {
+ speechRecognitionService = DEFAULT_RECOGNITION_SERVICE;
+ } else {
+ speechRecognitionService = prefValue;
+ }
+
+ if (!SpeechRecognition::mTestConfig.mFakeRecognitionService){
+ speechRecognitionServiceCID =
+ NS_LITERAL_CSTRING(NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX) +
+ speechRecognitionService;
+ } else {
+ speechRecognitionServiceCID =
+ NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX "fake";
+ }
+
+ nsresult aRv;
+ nsCOMPtr<nsISpeechRecognitionService> recognitionService;
+ recognitionService = do_GetService(speechRecognitionServiceCID.get(), &aRv);
+ return recognitionService.forget();
+}
+
NS_INTERFACE_MAP_BEGIN(SpeechRecognition)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
NS_IMPL_ADDREF_INHERITED(SpeechRecognition, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(SpeechRecognition, DOMEventTargetHelper)
struct SpeechRecognition::TestConfig SpeechRecognition::mTestConfig;
@@ -323,43 +353,16 @@ SpeechRecognition::ProcessAudioSegment(A
samples += iterator->GetDuration();
iterator.Next();
}
mRecognitionService->ProcessAudioSegment(aSegment, aTrackRate);
return samples;
}
-void
-SpeechRecognition::GetRecognitionServiceCID(nsACString& aResultCID)
-{
- if (mTestConfig.mFakeRecognitionService) {
- aResultCID =
- NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX "fake";
-
- return;
- }
-
- nsAdoptingCString prefValue =
- Preferences::GetCString(PREFERENCE_DEFAULT_RECOGNITION_SERVICE);
-
- nsAutoCString speechRecognitionService;
- if (!prefValue.get() || prefValue.IsEmpty()) {
- speechRecognitionService = DEFAULT_RECOGNITION_SERVICE;
- } else {
- speechRecognitionService = prefValue;
- }
-
- aResultCID =
- NS_LITERAL_CSTRING(NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX) +
- speechRecognitionService;
-
- return;
-}
-
/****************************************************************************
* FSM Transition functions
*
* If a transition function may cause a DOM event to be fired,
* it may also be re-entered, since the event handler may cause the
* event loop to spin and new SpeechEvents to be processed.
*
* Rules:
@@ -686,23 +689,20 @@ SpeechRecognition::SetServiceURI(const n
void
SpeechRecognition::Start(const Optional<NonNull<DOMMediaStream>>& aStream, ErrorResult& aRv)
{
if (mCurrentState != STATE_IDLE) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
- nsAutoCString speechRecognitionServiceCID;
- GetRecognitionServiceCID(speechRecognitionServiceCID);
+ mRecognitionService = GetSpeechRecognitionService();
+ NS_ENSURE_TRUE_VOID(mRecognitionService);
nsresult rv;
- mRecognitionService = do_GetService(speechRecognitionServiceCID.get(), &rv);
- NS_ENSURE_SUCCESS_VOID(rv);
-
rv = mRecognitionService->Initialize(this);
NS_ENSURE_SUCCESS_VOID(rv);
MediaStreamConstraints constraints;
constraints.mAudio.SetAsBoolean() = true;
if (aStream.WasPassed()) {
StartRecording(&aStream.Value());
--- a/content/media/webspeech/recognition/SpeechRecognition.h
+++ b/content/media/webspeech/recognition/SpeechRecognition.h
@@ -48,16 +48,18 @@ class SpeechEvent;
#ifdef PR_LOGGING
PRLogModuleInfo* GetSpeechRecognitionLog();
#define SR_LOG(...) PR_LOG(GetSpeechRecognitionLog(), PR_LOG_DEBUG, (__VA_ARGS__))
#else
#define SR_LOG(...)
#endif
+already_AddRefed<nsISpeechRecognitionService> GetSpeechRecognitionService();
+
class SpeechRecognition MOZ_FINAL : public DOMEventTargetHelper,
public nsIObserver,
public SupportsWeakPtr<SpeechRecognition>
{
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(SpeechRecognition)
explicit SpeechRecognition(nsPIDOMWindow* aOwnerWindow);
@@ -229,18 +231,16 @@ private:
void DoNothing(SpeechEvent* aEvent);
void AbortSilently(SpeechEvent* aEvent);
void AbortError(SpeechEvent* aEvent);
nsRefPtr<DOMMediaStream> mDOMStream;
nsRefPtr<SpeechStreamListener> mSpeechListener;
nsCOMPtr<nsISpeechRecognitionService> mRecognitionService;
- void GetRecognitionServiceCID(nsACString& aResultCID);
-
FSMState mCurrentState;
Endpointer mEndpointer;
uint32_t mEstimationSamples;
uint32_t mAudioSamplesPerChunk;
// buffer holds one chunk of mAudioSamplesPerChunk
--- a/content/media/webspeech/recognition/nsISpeechRecognitionService.idl
+++ b/content/media/webspeech/recognition/nsISpeechRecognitionService.idl
@@ -2,24 +2,42 @@
/* 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 "nsISupports.idl"
%{C++
#include "mozilla/WeakPtr.h"
+
+namespace mozilla {
+class AudioSegment;
+namespace dom {
+class SpeechRecognition;
+class SpeechRecognitionResultList;
+class SpeechGrammarList;
+class SpeechGrammar;
+}
+}
%}
native SpeechRecognitionWeakPtr(mozilla::WeakPtr<mozilla::dom::SpeechRecognition>);
[ptr] native AudioSegmentPtr(mozilla::AudioSegment);
+[ptr] native SpeechGrammarListPtr(mozilla::dom::SpeechGrammarList);
+[ptr] native SpeechGrammarPtr(mozilla::dom::SpeechGrammar);
+
+[uuid(374583f0-4507-11e4-a183-164230d1df67)]
+interface nsISpeechGrammarCompilationCallback : nsISupports {
+ void grammarCompilationEnd(in SpeechGrammarPtr grammarObject, in boolean success);
+};
[uuid(857f3fa2-a980-4d3e-a959-a2f53af74232)]
interface nsISpeechRecognitionService : nsISupports {
void initialize(in SpeechRecognitionWeakPtr aSpeechRecognition);
void processAudioSegment(in AudioSegmentPtr aAudioSegment, in long aSampleRate);
+ void validateAndSetGrammarList(in SpeechGrammarListPtr aSpeechGramarList, in nsISpeechGrammarCompilationCallback aCallback);
void soundEnd();
void abort();
};
%{C++
#define NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX "@mozilla.org/webspeech/service;1?name="
%}
--- a/content/media/webspeech/recognition/test/FakeSpeechRecognitionService.cpp
+++ b/content/media/webspeech/recognition/test/FakeSpeechRecognitionService.cpp
@@ -47,16 +47,22 @@ FakeSpeechRecognitionService::ProcessAud
NS_IMETHODIMP
FakeSpeechRecognitionService::SoundEnd()
{
return NS_OK;
}
NS_IMETHODIMP
+FakeSpeechRecognitionService::ValidateAndSetGrammarList(mozilla::dom::SpeechGrammarList*, nsISpeechGrammarCompilationCallback*)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
FakeSpeechRecognitionService::Abort()
{
return NS_OK;
}
NS_IMETHODIMP
FakeSpeechRecognitionService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
{
--- a/content/media/webspeech/recognition/test/FakeSpeechRecognitionService.h
+++ b/content/media/webspeech/recognition/test/FakeSpeechRecognitionService.h
@@ -4,27 +4,16 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_FakeSpeechRecognitionService_h
#define mozilla_dom_FakeSpeechRecognitionService_h
#include "nsCOMPtr.h"
#include "nsIObserver.h"
-
-// nsISpeechRecognitionService needs these declarations
-namespace mozilla {
- class AudioSegment;
-
- namespace dom {
- class SpeechRecognition;
- class SpeechRecognitionResultList;
- }
-}
-
#include "nsISpeechRecognitionService.h"
#define NS_FAKE_SPEECH_RECOGNITION_SERVICE_CID \
{0x48c345e7, 0x9929, 0x4f9a, {0xa5, 0x63, 0xf4, 0x78, 0x22, 0x2d, 0xab, 0xcd}};
namespace mozilla {
class FakeSpeechRecognitionService : public nsISpeechRecognitionService,
--- a/dom/canvas/WebGL2Context.h
+++ b/dom/canvas/WebGL2Context.h
@@ -238,13 +238,19 @@ public:
void DeleteVertexArray(WebGLVertexArrayObject* vertexArray);
bool IsVertexArray(WebGLVertexArrayObject* vertexArray);
void BindVertexArray(WebGLVertexArrayObject* vertexArray);
*/
private:
WebGL2Context();
+
+ bool ValidateSizedInternalFormat(GLenum internalFormat, const char* info);
+ bool ValidateTexStorage(GLenum target, GLsizei levels, GLenum internalformat,
+ GLsizei width, GLsizei height, GLsizei depth,
+ const char* info);
+
};
} // namespace mozilla
#endif
--- a/dom/canvas/WebGL2ContextTextures.cpp
+++ b/dom/canvas/WebGL2ContextTextures.cpp
@@ -4,23 +4,177 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WebGL2Context.h"
#include "GLContext.h"
using namespace mozilla;
using namespace mozilla::dom;
+bool
+WebGL2Context::ValidateSizedInternalFormat(GLenum internalformat, const char* info)
+{
+ switch (internalformat) {
+ // Sized Internal Formats
+ // https://www.khronos.org/opengles/sdk/docs/man3/html/glTexStorage2D.xhtml
+ case LOCAL_GL_R8:
+ case LOCAL_GL_R8_SNORM:
+ case LOCAL_GL_R16F:
+ case LOCAL_GL_R32F:
+ case LOCAL_GL_R8UI:
+ case LOCAL_GL_R8I:
+ case LOCAL_GL_R16UI:
+ case LOCAL_GL_R16I:
+ case LOCAL_GL_R32UI:
+ case LOCAL_GL_R32I:
+ case LOCAL_GL_RG8:
+ case LOCAL_GL_RG8_SNORM:
+ case LOCAL_GL_RG16F:
+ case LOCAL_GL_RG32F:
+ case LOCAL_GL_RG8UI:
+ case LOCAL_GL_RG8I:
+ case LOCAL_GL_RG16UI:
+ case LOCAL_GL_RG16I:
+ case LOCAL_GL_RG32UI:
+ case LOCAL_GL_RG32I:
+ case LOCAL_GL_RGB8:
+ case LOCAL_GL_SRGB8:
+ case LOCAL_GL_RGB565:
+ case LOCAL_GL_RGB8_SNORM:
+ case LOCAL_GL_R11F_G11F_B10F:
+ case LOCAL_GL_RGB9_E5:
+ case LOCAL_GL_RGB16F:
+ case LOCAL_GL_RGB32F:
+ case LOCAL_GL_RGB8UI:
+ case LOCAL_GL_RGB8I:
+ case LOCAL_GL_RGB16UI:
+ case LOCAL_GL_RGB16I:
+ case LOCAL_GL_RGB32UI:
+ case LOCAL_GL_RGB32I:
+ case LOCAL_GL_RGBA8:
+ case LOCAL_GL_SRGB8_ALPHA8:
+ case LOCAL_GL_RGBA8_SNORM:
+ case LOCAL_GL_RGB5_A1:
+ case LOCAL_GL_RGBA4:
+ case LOCAL_GL_RGB10_A2:
+ case LOCAL_GL_RGBA16F:
+ case LOCAL_GL_RGBA32F:
+ case LOCAL_GL_RGBA8UI:
+ case LOCAL_GL_RGBA8I:
+ case LOCAL_GL_RGB10_A2UI:
+ case LOCAL_GL_RGBA16UI:
+ case LOCAL_GL_RGBA16I:
+ case LOCAL_GL_RGBA32I:
+ case LOCAL_GL_RGBA32UI:
+ case LOCAL_GL_DEPTH_COMPONENT16:
+ case LOCAL_GL_DEPTH_COMPONENT24:
+ case LOCAL_GL_DEPTH_COMPONENT32F:
+ case LOCAL_GL_DEPTH24_STENCIL8:
+ case LOCAL_GL_DEPTH32F_STENCIL8:
+ return true;
+ }
+
+ if (IsCompressedTextureFormat(internalformat)) {
+ return true;
+ }
+
+ const char* name = EnumName(internalformat);
+ if (name && name[0] != '[')
+ ErrorInvalidEnum("%s: invalid internal format %s", info, name);
+ else
+ ErrorInvalidEnum("%s: invalid internal format 0x%04X", info, internalformat);
+
+ return false;
+}
+
+/** Validates parameters to texStorage{2D,3D} */
+bool
+WebGL2Context::ValidateTexStorage(GLenum target, GLsizei levels, GLenum internalformat,
+ GLsizei width, GLsizei height, GLsizei depth,
+ const char* info)
+{
+ // GL_INVALID_OPERATION is generated if the default texture object is curently bound to target.
+ WebGLTexture* tex = activeBoundTextureForTarget(target);
+ if (!tex) {
+ ErrorInvalidOperation("%s: no texture is bound to target %s", info, EnumName(target));
+ return false;
+ }
+
+ // GL_INVALID_OPERATION is generated if the texture object currently bound to target already has
+ // GL_TEXTURE_IMMUTABLE_FORMAT set to GL_TRUE.
+ if (tex->IsImmutable()) {
+ ErrorInvalidOperation("%s: texture bound to target %s is already immutable", info, EnumName(target));
+ return false;
+ }
+
+ // GL_INVALID_ENUM is generated if internalformat is not a valid sized internal format.
+ if (!ValidateSizedInternalFormat(internalformat, info))
+ return false;
+
+ // GL_INVALID_VALUE is generated if width, height or levels are less than 1.
+ if (width < 1) { ErrorInvalidValue("%s: width is < 1", info); return false; }
+ if (height < 1) { ErrorInvalidValue("%s: height is < 1", info); return false; }
+ if (depth < 1) { ErrorInvalidValue("%s: depth is < 1", info); return false; }
+ if (levels < 1) { ErrorInvalidValue("%s: levels is < 1", info); return false; }
+
+ // The following check via FloorLog2 only requires a depth value if target is TEXTURE_3D.
+ bool is3D = (target != LOCAL_GL_TEXTURE_3D);
+ if (!is3D)
+ depth = 1;
+
+ // GL_INVALID_OPERATION is generated if levels is greater than floor(log2(max(width, height, depth)))+1.
+ if (FloorLog2(std::max(std::max(width, height), depth))+1 < levels) {
+ ErrorInvalidOperation("%s: levels > floor(log2(max(width, height%s)))+1", info, is3D ? ", depth" : "");
+ return false;
+ }
+
+ return true;
+}
+
// -------------------------------------------------------------------------
// Texture objects
void
WebGL2Context::TexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height)
{
- MOZ_CRASH("Not Implemented.");
+ if (IsContextLost())
+ return;
+
+ // GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants.
+ if (target != LOCAL_GL_TEXTURE_2D && target != LOCAL_GL_TEXTURE_CUBE_MAP)
+ return ErrorInvalidEnum("texStorage2D: target is not TEXTURE_2D or TEXTURE_CUBE_MAP.");
+
+ if (!ValidateTexStorage(target, levels, internalformat, width, height, 1, "texStorage2D"))
+ return;
+
+ WebGLTexture* tex = activeBoundTextureForTarget(target);
+ tex->SetImmutable();
+
+ const size_t facesCount = (target == LOCAL_GL_TEXTURE_2D) ? 1 : 6;
+ GLsizei w = width;
+ GLsizei h = height;
+ for (size_t l = 0; l < size_t(levels); l++) {
+ for (size_t f = 0; f < facesCount; f++) {
+ TexImageTarget imageTarget = TexImageTargetForTargetAndFace(target, f);
+ // FIXME: SetImageInfo wants a type, to go with the internalformat that it stores.
+ // 'type' is deprecated by sized internalformats, which are how TexStorage works.
+ // We must fix WebGLTexture::ImageInfo to store an "effective internalformat",
+ // which in the present case is just the sized internalformat, and drop 'types'
+ // altogether. For now, we just pass LOCAL_GL_UNSIGNED_BYTE, which works For
+ // the most commonly used formats.
+ const GLenum type = LOCAL_GL_UNSIGNED_BYTE;
+ tex->SetImageInfo(imageTarget, l, w, h,
+ internalformat, type,
+ WebGLImageDataStatus::UninitializedImageData);
+ }
+ w = std::max(1, w/2);
+ h = std::max(1, h/2);
+ }
+
+ gl->fTexStorage2D(target, levels, internalformat, width, height);
}
void
WebGL2Context::TexStorage3D(GLenum target, GLsizei levels, GLenum internalformat,
GLsizei width, GLsizei height, GLsizei depth)
{
MOZ_CRASH("Not Implemented.");
}
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -390,16 +390,22 @@ WebGLContext::CopyTexSubImage2D_base(Tex
MakeContextCurrent();
WebGLTexture *tex = activeBoundTextureForTexImageTarget(texImageTarget);
if (!tex)
return ErrorInvalidOperation("%s: no texture is bound to this target");
+ if (tex->IsImmutable()) {
+ if (!sub) {
+ return ErrorInvalidOperation("copyTexImage2D: disallowed because the texture bound to this target has already been made immutable by texStorage2D");
+ }
+ }
+
if (CanvasUtils::CheckSaneSubrectSize(x, y, width, height, framebufferWidth, framebufferHeight)) {
if (sub)
gl->fCopyTexSubImage2D(texImageTarget.get(), level, xoffset, yoffset, x, y, width, height);
else
gl->fCopyTexImage2D(texImageTarget.get(), level, internalformat, x, y, width, height, 0);
} else {
// the rect doesn't fit in the framebuffer
@@ -3331,20 +3337,27 @@ WebGLContext::CompressedTexImage2D(GLenu
if (!ValidateCompTexImageSize(level, internalformat, 0, 0, width, height, width, height, func))
{
return;
}
const TexImageTarget texImageTarget(rawTexImgTarget);
+ WebGLTexture* tex = activeBoundTextureForTexImageTarget(texImageTarget);
+ MOZ_ASSERT(tex);
+ if (tex->IsImmutable()) {
+ return ErrorInvalidOperation(
+ "compressedTexImage2D: disallowed because the texture bound to "
+ "this target has already been made immutable by texStorage2D");
+ }
+
MakeContextCurrent();
gl->fCompressedTexImage2D(texImageTarget.get(), level, internalformat, width, height, border, byteLength, view.Data());
- WebGLTexture* tex = activeBoundTextureForTexImageTarget(texImageTarget);
- MOZ_ASSERT(tex);
+
tex->SetImageInfo(texImageTarget, level, width, height, internalformat, LOCAL_GL_UNSIGNED_BYTE,
WebGLImageDataStatus::InitializedImageData);
}
void
WebGLContext::CompressedTexSubImage2D(GLenum rawTexImgTarget, GLint level, GLint xoffset,
GLint yoffset, GLsizei width, GLsizei height,
GLenum internalformat,
@@ -3682,16 +3695,21 @@ WebGLContext::TexImage2D_base(TexImageTa
return ErrorInvalidOperation("texImage2D: not enough data for operation (need %d, have %d)",
bytesNeeded, byteLength);
WebGLTexture *tex = activeBoundTextureForTexImageTarget(texImageTarget);
if (!tex)
return ErrorInvalidOperation("texImage2D: no texture is bound to this target");
+ if (tex->IsImmutable()) {
+ return ErrorInvalidOperation(
+ "texImage2D: disallowed because the texture "
+ "bound to this target has already been made immutable by texStorage2D");
+ }
MakeContextCurrent();
nsAutoArrayPtr<uint8_t> convertedData;
void* pixels = nullptr;
WebGLImageDataStatus imageInfoStatusIfSuccess = WebGLImageDataStatus::UninitializedImageData;
if (byteLength) {
size_t srcStride = srcStrideOrZero ? srcStrideOrZero : checked_alignedRowSize.value();
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -501,16 +501,26 @@ WebGLContext::IsCompressedTextureFormat(
case LOCAL_GL_ATC_RGB:
case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA:
case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA:
case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1:
case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1:
case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1:
case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1:
case LOCAL_GL_ETC1_RGB8_OES:
+ case LOCAL_GL_COMPRESSED_R11_EAC:
+ case LOCAL_GL_COMPRESSED_SIGNED_R11_EAC:
+ case LOCAL_GL_COMPRESSED_RG11_EAC:
+ case LOCAL_GL_COMPRESSED_SIGNED_RG11_EAC:
+ case LOCAL_GL_COMPRESSED_RGB8_ETC2:
+ case LOCAL_GL_COMPRESSED_SRGB8_ETC2:
+ case LOCAL_GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
+ case LOCAL_GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
+ case LOCAL_GL_COMPRESSED_RGBA8_ETC2_EAC:
+ case LOCAL_GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
return true;
default:
return false;
}
}
bool
--- a/dom/canvas/WebGLTexture.cpp
+++ b/dom/canvas/WebGLTexture.cpp
@@ -27,16 +27,17 @@ WebGLTexture::WebGLTexture(WebGLContext
, WebGLContextBoundObject(context)
, mMinFilter(LOCAL_GL_NEAREST_MIPMAP_LINEAR)
, mMagFilter(LOCAL_GL_LINEAR)
, mWrapS(LOCAL_GL_REPEAT)
, mWrapT(LOCAL_GL_REPEAT)
, mFacesCount(0)
, mMaxLevelWithCustomImages(0)
, mHaveGeneratedMipmap(false)
+ , mImmutable(false)
, mFakeBlackStatus(WebGLTextureFakeBlackStatus::IncompleteTexture)
{
SetIsDOMBinding();
mContext->MakeContextCurrent();
mContext->gl->fGenTextures(1, &mGLName);
mContext->mTextures.insertBack(this);
}
@@ -222,29 +223,22 @@ WebGLTexture::IsCubeComplete() const {
if (mTarget != LOCAL_GL_TEXTURE_CUBE_MAP)
return false;
const ImageInfo &first = ImageInfoAt(LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0);
if (!first.IsPositive() || !first.IsSquare())
return false;
return AreAllLevel0ImageInfosEqual();
}
-static TexImageTarget
-GLCubeMapFaceById(int id)
-{
- // Correctness is checked by the constructor for TexImageTarget
- return TexImageTarget(LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + id);
-}
-
bool
WebGLTexture::IsMipmapCubeComplete() const {
if (!IsCubeComplete()) // in particular, this checks that this is a cube map
return false;
for (int i = 0; i < 6; i++) {
- const TexImageTarget face = GLCubeMapFaceById(i);
+ const TexImageTarget face = TexImageTargetForTargetAndFace(LOCAL_GL_TEXTURE_CUBE_MAP, i);
if (!DoesTexture2DMipmapHaveAllLevelsConsistentlyDefined(face))
return false;
}
return true;
}
WebGLTextureFakeBlackStatus
WebGLTexture::ResolvedFakeBlackStatus() {
@@ -404,19 +398,17 @@ WebGLTexture::ResolvedFakeBlackStatus()
if (hasAnyInitializedImageData) {
// The texture contains some initialized image data, and some uninitialized image data.
// In this case, we have no choice but to initialize all image data now. Fortunately,
// in this case we know that we can't be dealing with a depth texture per WEBGL_depth_texture
// and ANGLE_depth_texture (which allow only one image per texture) so we can assume that
// glTexImage2D is able to upload data to images.
for (size_t level = 0; level <= mMaxLevelWithCustomImages; ++level) {
for (size_t face = 0; face < mFacesCount; ++face) {
- TexImageTarget imageTarget = mTarget == LOCAL_GL_TEXTURE_2D
- ? LOCAL_GL_TEXTURE_2D
- : LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
+ TexImageTarget imageTarget = TexImageTargetForTargetAndFace(mTarget, face);
const ImageInfo& imageInfo = ImageInfoAt(imageTarget, level);
if (imageInfo.mImageDataStatus == WebGLImageDataStatus::UninitializedImageData) {
DoDeferredImageInitialization(imageTarget, level);
}
}
}
mFakeBlackStatus = WebGLTextureFakeBlackStatus::NotNeeded;
} else {
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -197,17 +197,19 @@ protected:
TexMinFilter mMinFilter;
TexMagFilter mMagFilter;
TexWrap mWrapS, mWrapT;
size_t mFacesCount, mMaxLevelWithCustomImages;
nsTArray<ImageInfo> mImageInfos;
- bool mHaveGeneratedMipmap;
+ bool mHaveGeneratedMipmap; // set by generateMipmap
+ bool mImmutable; // set by texStorage*
+
WebGLTextureFakeBlackStatus mFakeBlackStatus;
void EnsureMaxLevelWithCustomImagesAtLeast(size_t aMaxLevelWithCustomImages) {
mMaxLevelWithCustomImages = std::max(mMaxLevelWithCustomImages, aMaxLevelWithCustomImages);
mImageInfos.EnsureLengthAtLeast((mMaxLevelWithCustomImages + 1) * mFacesCount);
}
bool CheckFloatTextureFilterParams() const {
@@ -266,16 +268,29 @@ public:
bool IsMipmapTexture2DComplete() const;
bool IsCubeComplete() const;
bool IsMipmapCubeComplete() const;
void SetFakeBlackStatus(WebGLTextureFakeBlackStatus x);
+ bool IsImmutable() const { return mImmutable; }
+ void SetImmutable() { mImmutable = true; }
+
+ size_t MaxLevelWithCustomImages() const { return mMaxLevelWithCustomImages; }
+
// Returns the current fake-black-status, except if it was Unknown,
// in which case this function resolves it first, so it never returns Unknown.
WebGLTextureFakeBlackStatus ResolvedFakeBlackStatus();
};
+inline TexImageTarget
+TexImageTargetForTargetAndFace(TexTarget target, size_t face)
+{
+ return target == LOCAL_GL_TEXTURE_2D
+ ? LOCAL_GL_TEXTURE_2D
+ : LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
+}
+
} // namespace mozilla
#endif
--- a/dom/identity/Identity.manifest
+++ b/dom/identity/Identity.manifest
@@ -1,9 +1,8 @@
# nsDOMIdentity.js
component {210853d9-2c97-4669-9761-b1ab9cbf57ef} nsDOMIdentity.js
-contract @mozilla.org/dom/identity;1 {210853d9-2c97-4669-9761-b1ab9cbf57ef}
-category JavaScript-navigator-property mozId @mozilla.org/dom/identity;1
+contract @mozilla.org/identity/manager;1 {210853d9-2c97-4669-9761-b1ab9cbf57ef}
# nsIDService.js (initialization on startup)
component {4e0a0e98-b1d3-4745-a1eb-f815199dd06b} nsIDService.js
contract @mozilla.org/dom/identity/service;1 {4e0a0e98-b1d3-4745-a1eb-f815199dd06b}
category app-startup IDService @mozilla.org/dom/identity/service;1
--- a/dom/identity/nsDOMIdentity.js
+++ b/dom/identity/nsDOMIdentity.js
@@ -41,39 +41,20 @@ XPCOMUtils.defineLazyServiceGetter(this,
const ERRORS = {
"ERROR_INVALID_ASSERTION_AUDIENCE":
"Assertion audience may not differ from origin",
"ERROR_REQUEST_WHILE_NOT_HANDLING_USER_INPUT":
"The request() method may only be invoked when handling user input",
};
-function nsDOMIdentity(aIdentityInternal) {
- this._identityInternal = aIdentityInternal;
+function nsDOMIdentity() {
}
-nsDOMIdentity.prototype = {
- __exposedProps__: {
- // Relying Party (RP)
- watch: 'r',
- request: 'r',
- logout: 'r',
- get: 'r',
- getVerifiedEmail: 'r',
- // Provisioning
- beginProvisioning: 'r',
- genKeyPair: 'r',
- registerCertificate: 'r',
- raiseProvisioningFailure: 'r',
-
- // Authentication
- beginAuthentication: 'r',
- completeAuthentication: 'r',
- raiseAuthenticationFailure: 'r'
- },
+nsDOMIdentity.prototype = {
// require native events unless syntheticEventsOk is set
get nativeEventsRequired() {
if (Services.prefs.prefHasUserValue(PREF_SYNTHETIC_EVENTS_OK) &&
(Services.prefs.getPrefType(PREF_SYNTHETIC_EVENTS_OK) ===
Ci.nsIPrefBranch.PREF_BOOL)) {
return !Services.prefs.getBoolPref(PREF_SYNTHETIC_EVENTS_OK);
}
@@ -143,17 +124,17 @@ nsDOMIdentity.prototype = {
this._rpWatcher.audience = message.audience;
if (message.errors.length) {
this.reportErrors(message);
// We don't delete the rpWatcher object, because we don't want the
// broken client to be able to call watch() any more. It's broken.
return;
}
- this._identityInternal._mm.sendAsyncMessage(
+ this._mm.sendAsyncMessage(
"Identity:RP:Watch",
message,
null,
this._window.document.nodePrincipal
);
},
request: function nsDOMIdentity_request(aOptions = {}) {
@@ -219,17 +200,17 @@ nsDOMIdentity.prototype = {
throw new Error("oncancel is not a function");
} else {
// Store optional cancel callback for later.
this._onCancelRequestCallback = aOptions.oncancel;
}
}
this._rpCalls++;
- this._identityInternal._mm.sendAsyncMessage(
+ this._mm.sendAsyncMessage(
"Identity:RP:Request",
message,
null,
this._window.document.nodePrincipal
);
},
logout: function nsDOMIdentity_logout() {
@@ -244,17 +225,17 @@ nsDOMIdentity.prototype = {
let message = this.DOMIdentityMessage();
// Report and fail hard on any errors.
if (message.errors.length) {
this.reportErrors(message);
return;
}
- this._identityInternal._mm.sendAsyncMessage(
+ this._mm.sendAsyncMessage(
"Identity:RP:Logout",
message,
null,
this._window.document.nodePrincipal
);
},
/*
@@ -332,17 +313,17 @@ nsDOMIdentity.prototype = {
if (this._beginProvisioningCallback) {
throw new Error("navigator.id.beginProvisioning already called.");
}
if (!aCallback || typeof(aCallback) !== "function") {
throw new Error("beginProvisioning callback is required.");
}
this._beginProvisioningCallback = aCallback;
- this._identityInternal._mm.sendAsyncMessage(
+ this._mm.sendAsyncMessage(
"Identity:IDP:BeginProvisioning",
this.DOMIdentityMessage(),
null,
this._window.document.nodePrincipal
);
},
genKeyPair: function nsDOMIdentity_genKeyPair(aCallback) {
@@ -353,17 +334,17 @@ nsDOMIdentity.prototype = {
if (this._genKeyPairCallback) {
throw new Error("navigator.id.genKeyPair already called.");
}
if (!aCallback || typeof(aCallback) !== "function") {
throw new Error("genKeyPair callback is required.");
}
this._genKeyPairCallback = aCallback;
- this._identityInternal._mm.sendAsyncMessage(
+ this._mm.sendAsyncMessage(
"Identity:IDP:GenKeyPair",
this.DOMIdentityMessage(),
null,
this._window.document.nodePrincipal
);
},
registerCertificate: function nsDOMIdentity_registerCertificate(aCertificate) {
@@ -373,17 +354,17 @@ nsDOMIdentity.prototype = {
}
if (this._provisioningEnded) {
throw new Error("Provisioning already ended");
}
this._provisioningEnded = true;
let message = this.DOMIdentityMessage();
message.cert = aCertificate;
- this._identityInternal._mm.sendAsyncMessage(
+ this._mm.sendAsyncMessage(
"Identity:IDP:RegisterCertificate",
message,
null,
this._window.document.nodePrincipal
);
},
raiseProvisioningFailure: function nsDOMIdentity_raiseProvisioningFailure(aReason) {
@@ -393,17 +374,17 @@ nsDOMIdentity.prototype = {
}
if (!aReason || typeof(aReason) != "string") {
throw new Error("raiseProvisioningFailure reason is required");
}
this._provisioningEnded = true;
let message = this.DOMIdentityMessage();
message.reason = aReason;
- this._identityInternal._mm.sendAsyncMessage(
+ this._mm.sendAsyncMessage(
"Identity:IDP:ProvisioningFailure",
message,
null,
this._window.document.nodePrincipal
);
},
/**
@@ -418,34 +399,34 @@ nsDOMIdentity.prototype = {
if (typeof(aCallback) !== "function") {
throw new Error("beginAuthentication callback is required.");
}
if (!aCallback || typeof(aCallback) !== "function") {
throw new Error("beginAuthentication callback is required.");
}
this._beginAuthenticationCallback = aCallback;
- this._identityInternal._mm.sendAsyncMessage(
+ this._mm.sendAsyncMessage(
"Identity:IDP:BeginAuthentication",
this.DOMIdentityMessage(),
null,
this._window.document.nodePrincipal
);
},
completeAuthentication: function nsDOMIdentity_completeAuthentication() {
if (this._authenticationEnded) {
throw new Error("Authentication already ended");
}
if (!this._beginAuthenticationCallback) {
throw new Error("navigator.id.completeAuthentication called outside of authentication");
}
this._authenticationEnded = true;
- this._identityInternal._mm.sendAsyncMessage(
+ this._mm.sendAsyncMessage(
"Identity:IDP:CompleteAuthentication",
this.DOMIdentityMessage(),
null,
this._window.document.nodePrincipal
);
},
raiseAuthenticationFailure: function nsDOMIdentity_raiseAuthenticationFailure(aReason) {
@@ -453,44 +434,24 @@ nsDOMIdentity.prototype = {
throw new Error("Authentication already ended");
}
if (!aReason || typeof(aReason) != "string") {
throw new Error("raiseProvisioningFailure reason is required");
}
let message = this.DOMIdentityMessage();
message.reason = aReason;
- this._identityInternal._mm.sendAsyncMessage(
+ this._mm.sendAsyncMessage(
"Identity:IDP:AuthenticationFailure",
message,
null,
this._window.document.nodePrincipal
);
},
- // Private.
- _init: function nsDOMIdentity__init(aWindow) {
-
- this._initializeState();
-
- // Store window and origin URI.
- this._window = aWindow;
- this._origin = aWindow.document.nodePrincipal.origin;
- this._appStatus = aWindow.document.nodePrincipal.appStatus;
- this._appId = aWindow.document.nodePrincipal.appId;
-
- // Setup identifiers for current window.
- let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
-
- // We need to inherit the id from the internalIdentity service.
- // See comments below in that service's init.
- this._id = this._identityInternal._id;
- },
-
/**
* Called during init and shutdown.
*/
_initializeState: function nsDOMIdentity__initializeState() {
// Some state to prevent abuse
// Limit the number of calls to .request
this._rpCalls = 0;
this._provisioningEnded = false;
@@ -498,22 +459,28 @@ nsDOMIdentity.prototype = {
this._rpWatcher = null;
this._onCancelRequestCallback = null;
this._beginProvisioningCallback = null;
this._genKeyPairCallback = null;
this._beginAuthenticationCallback = null;
},
- _receiveMessage: function nsDOMIdentity_receiveMessage(aMessage) {
+ // nsIMessageListener
+ receiveMessage: function nsDOMIdentity_receiveMessage(aMessage) {
let msg = aMessage.json;
+ // Is this message intended for this window?
+ if (msg.id != this._id) {
+ return;
+ }
+
switch (aMessage.name) {
case "Identity:ResetState":
- if (!this._identityInternal._debug) {
+ if (!this._debug) {
return;
}
this._initializeState();
Services.obs.notifyObservers(null, "identity-DOM-state-reset", this._id);
break;
case "Identity:RP:Watch:OnLogin":
// Do we have a watcher?
if (!this._rpWatcher) {
@@ -581,20 +548,16 @@ nsDOMIdentity.prototype = {
this._callGenKeyPairCallback(msg);
break;
case "Identity:IDP:CallBeginAuthenticationCallback":
this._callBeginAuthenticationCallback(msg);
break;
}
},
- _log: function nsDOMIdentity__log(msg) {
- this._identityInternal._log(msg);
- },
-
_callGenKeyPairCallback: function nsDOMIdentity__callGenKeyPairCallback(message) {
// create a pubkey object that works
let chrome_pubkey = JSON.parse(message.publicKey);
// bunch of stuff to create a proper object in window context
function genPropDesc(value) {
return {
enumerable: true, configurable: true, writable: true, value: value
@@ -671,98 +634,79 @@ nsDOMIdentity.prototype = {
// Replace any audience supplied by the RP with one that has been sanitised
message.audience = _audience;
this._log("DOMIdentityMessage: " + JSON.stringify(message));
return message;
},
- uninit: function DOMIdentity_uninit() {
- this._log("nsDOMIdentity uninit() " + this._id);
- this._identityInternal._mm.sendAsyncMessage(
- "Identity:RP:Unwatch",
- { id: this._id },
- null,
- this._window.document.nodePrincipal
- );
- }
-
-};
-
-/**
- * Internal functions that shouldn't be exposed to content.
- */
-function nsDOMIdentityInternal() {
-}
-nsDOMIdentityInternal.prototype = {
-
- // nsIMessageListener
- receiveMessage: function nsDOMIdentityInternal_receiveMessage(aMessage) {
- let msg = aMessage.json;
- // Is this message intended for this window?
- if (msg.id != this._id) {
- return;
- }
- this._identity._receiveMessage(aMessage);
- },
-
+ /**
+ * Internal methods that are not exposed to content.
+ * See dom/webidl/Identity.webidl for the public interface.
+ */
// nsIObserver
observe: function nsDOMIdentityInternal_observe(aSubject, aTopic, aData) {
let window = aSubject.QueryInterface(Ci.nsIDOMWindow);
if (window != this._window) {
return;
}
- this._identity.uninit();
+ this.uninit();
Services.obs.removeObserver(this, "dom-window-destroyed");
- this._identity._initializeState();
- this._identity = null;
+ this._initializeState();
// TODO: Also send message to DOMIdentity notifiying window is no longer valid
// ie. in the case that the user closes the auth. window and we need to know.
try {
for (let msgName of this._messages) {
this._mm.removeMessageListener(msgName, this);
}
} catch (ex) {
// Avoid errors when removing more than once.
}
this._mm = null;
},
- // nsIDOMGlobalPropertyInitializer
+ // Because we implement nsIDOMGlobalPropertyInitializer, our init() method
+ // is invoked with content window as its single argument.
init: function nsDOMIdentityInternal_init(aWindow) {
if (Services.prefs.getPrefType(PREF_ENABLED) != Ci.nsIPrefBranch.PREF_BOOL
|| !Services.prefs.getBoolPref(PREF_ENABLED)) {
return null;
}
this._debug =
Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
&& Services.prefs.getBoolPref(PREF_DEBUG);
+ // Setup identifiers for current window.
let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
// To avoid cross-process windowId collisions, use a uuid as an
// almost certainly unique identifier.
//
// XXX Bug 869182 - use a combination of child process id and
// innerwindow id to construct the unique id.
this._id = uuidgen.generateUUID().toString();
this._window = aWindow;
// nsDOMIdentity needs to know our _id, so this goes after
// its creation.
- this._identity = new nsDOMIdentity(this);
- this._identity._init(aWindow);
+ this._initializeState();
+
+ // Store window and origin URI.
+ this._window = aWindow;
+ this._origin = aWindow.document.nodePrincipal.origin;
+ this._appStatus = aWindow.document.nodePrincipal.appStatus;
+ this._appId = aWindow.document.nodePrincipal.appId;
this._log("init was called from " + aWindow.document.location);
this._mm = cpmm;
// Setup listeners for messages from parent process.
this._messages = [
"Identity:ResetState",
@@ -776,34 +720,42 @@ nsDOMIdentityInternal.prototype = {
"Identity:IDP:CallBeginAuthenticationCallback"
];
this._messages.forEach(function(msgName) {
this._mm.addMessageListener(msgName, this);
}, this);
// Setup observers so we can remove message listeners.
Services.obs.addObserver(this, "dom-window-destroyed", false);
+ },
- return this._identity;
- },
+ uninit: function DOMIdentity_uninit() {
+ this._log("nsDOMIdentity uninit() " + this._id);
+ this._mm.sendAsyncMessage(
+ "Identity:RP:Unwatch",
+ { id: this._id }
+ );
+ },
// Private.
_log: function nsDOMIdentityInternal__log(msg) {
if (!this._debug) {
return;
}
dump("nsDOMIdentity (" + this._id + "): " + msg + "\n");
},
// Component setup.
classID: Components.ID("{210853d9-2c97-4669-9761-b1ab9cbf57ef}"),
- QueryInterface: XPCOMUtils.generateQI(
- [Ci.nsIDOMGlobalPropertyInitializer, Ci.nsIMessageListener]
- ),
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIMessageListener,
+ Ci.nsIObserver,
+ Ci.nsIDOMGlobalPropertyInitializer
+ ]),
classInfo: XPCOMUtils.generateCI({
classID: Components.ID("{210853d9-2c97-4669-9761-b1ab9cbf57ef}"),
contractID: "@mozilla.org/dom/identity;1",
interfaces: [],
classDescription: "Identity DOM Implementation"
})
};
@@ -835,9 +787,9 @@ function assertCorrectCallbacks(aOptions
for (let cbName of optionalCallbacks) {
if (aOptions[cbName] && typeof(aOptions[cbName]) != "function") {
throw new Error(cbName + " must be a function");
}
}
}
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDOMIdentityInternal]);
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDOMIdentity]);
--- a/dom/tests/mochitest/geolocation/test_mozsettings.html
+++ b/dom/tests/mochitest/geolocation/test_mozsettings.html
@@ -9,23 +9,25 @@ https://bugzilla.mozilla.org/show_bug.cg
<script type="text/javascript" src="geolocation_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=777594">Mozilla Bug 777594</a>
<p id="display"></p>
<div id="content" style="display: none">
-
+
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
+var timeToWaitMs = 1000;
+
resume_geolocationProvider(function() {
force_prompt(true, test1);
});
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
}
@@ -41,41 +43,41 @@ function test1() {
function test2() {
ok(navigator.geolocation, "get geolocation object");
toggleGeolocationSetting(false, function() {
ok(true, "turned off geolocation via mozSettings");
setTimeout(function() {
navigator.geolocation.getCurrentPosition(successCallbackAfterMozsettingOff,
failureCallbackAfterMozsettingOff);
- }, 500); // need to wait a bit for all of these async callbacks to finish
+ }, timeToWaitMs); // need to wait a bit for all of these async callbacks to finish
});
}
function successCallbackAfterMozsettingOff(position) {
ok(false, "Success callback should not have been called after setting geolocation.enabled to false.");
toggleGeolocationSetting(true, function() {
ok(true, "turned on geolocation via mozSettings");
setTimeout(function() {
navigator.geolocation.getCurrentPosition(successCallbackAfterMozsettingOn,
failureCallbackAfterMozsettingOn);
- }, 500); // need to wait a bit for all of these async callbacks to finish
+ }, timeToWaitMs); // need to wait a bit for all of these async callbacks to finish
});
}
function failureCallbackAfterMozsettingOff(error) {
ok(true, "Geolocation didn't work after setting geolocation.enabled to false.");
toggleGeolocationSetting(true, function() {
ok(true, "turned on geolocation via mozSettings");
setTimeout(function() {
navigator.geolocation.getCurrentPosition(successCallbackAfterMozsettingOn,
failureCallbackAfterMozsettingOn);
- }, 500); // need to wait a bit for all of these async callbacks to finish
+ }, timeToWaitMs); // need to wait a bit for all of these async callbacks to finish
});
}
function successCallbackAfterMozsettingOn(position) {
ok(true, "Geolocation worked after setting geolocation.enabled to true.");
SimpleTest.finish();
}
new file mode 100644
--- /dev/null
+++ b/dom/webidl/Identity.webidl
@@ -0,0 +1,70 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+callback IdentityOnReadyCallback = void();
+callback IdentityOnLoginCallback = void(DOMString identityAssertion);
+callback IdentityOnLogoutCallback = void();
+callback IdentityOnCancelCallback = void(DOMString? error);
+callback IdentityOnErrorCallback = void(DOMString error);
+
+dictionary IdentityWatchOptions {
+ // Required callback
+ IdentityOnLoginCallback onlogin;
+
+ // Optional parameters
+ DOMString wantIssuer;
+ DOMString loggedInUser;
+
+ // Optional callbacks
+ IdentityOnReadyCallback onready;
+ IdentityOnLogoutCallback onlogout;
+ IdentityOnErrorCallback onerror;
+
+ // Certified apps can specify this
+ DOMString audience;
+};
+
+dictionary IdentityRequestOptions {
+ // Optional parameters
+ long refreshAuthentication;
+ DOMString termsOfService;
+ DOMString privacyPolicy;
+ DOMString backgroundColor;
+ DOMString siteLogo;
+ DOMString siteName;
+ DOMString returnTo;
+
+ IdentityOnCancelCallback oncancel;
+
+ // Certified apps can specify this
+ DOMString origin;
+};
+
+dictionary IdentityGetOptions {
+ DOMString privacyPolicy;
+ DOMString termsOfService;
+ DOMString privacyURL;
+ DOMString tosURL;
+ DOMString siteName;
+ DOMString siteLogo;
+};
+
+[JSImplementation="@mozilla.org/identity/manager;1",
+ NoInterfaceObject,
+ NavigatorProperty="mozId",
+ Pref="dom.identity.enabled"]
+interface IdentityManager {
+ void watch(optional IdentityWatchOptions options);
+ void request(optional IdentityRequestOptions options);
+ void logout();
+
+ [Pref="dom.identity.exposeLegacyGetAPI"]
+ void get(IdentityOnLoginCallback callback, optional IdentityGetOptions options);
+
+ [Pref="dom.identity.exposeLegacyGetVerifiedEmailAPI"]
+ void getVerifiedEmail(IdentityOnLoginCallback callback);
+};
+
--- a/dom/webidl/SpeechGrammarList.webidl
+++ b/dom/webidl/SpeechGrammarList.webidl
@@ -5,17 +5,17 @@
*
* The origin of this IDL file is
* http://dvcs.w3.org/hg/speech-api/raw-file/tip/speechapi.html
*
* Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
* liability, trademark and document use rules apply.
*/
-[Pref="media.webspeech.recognition.enable"]
+[Constructor, Pref="media.webspeech.recognition.enable"]
interface SpeechGrammarList {
readonly attribute unsigned long length;
[Throws]
getter SpeechGrammar item(unsigned long index);
[Throws]
void addFromURI(DOMString src, optional float weight);
[Throws]
void addFromString(DOMString string, optional float weight);
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -227,16 +227,17 @@ WEBIDL_FILES = [
'IDBIndex.webidl',
'IDBKeyRange.webidl',
'IDBMutableFile.webidl',
'IDBObjectStore.webidl',
'IDBOpenDBRequest.webidl',
'IDBRequest.webidl',
'IDBTransaction.webidl',
'IDBVersionChangeEvent.webidl',
+ 'Identity.webidl',
'ImageCapture.webidl',
'ImageData.webidl',
'ImageDocument.webidl',
'InputEvent.webidl',
'InputMethod.webidl',
'InspectorUtils.webidl',
'InstallEvent.webidl',
'InstallPhaseEvent.webidl',
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -121,16 +121,17 @@ static const char *sExtensionNames[] = {
"GL_EXT_sRGB",
"GL_EXT_shader_texture_lod",
"GL_EXT_texture3D",
"GL_EXT_texture_compression_dxt1",
"GL_EXT_texture_compression_s3tc",
"GL_EXT_texture_filter_anisotropic",
"GL_EXT_texture_format_BGRA8888",
"GL_EXT_texture_sRGB",
+ "GL_EXT_texture_storage",
"GL_EXT_transform_feedback",
"GL_EXT_unpack_subimage",
"GL_IMG_read_format",
"GL_IMG_texture_compression_pvrtc",
"GL_IMG_texture_npot",
"GL_KHR_debug",
"GL_NV_draw_instanced",
"GL_NV_fence",
@@ -904,16 +905,39 @@ GLContext::InitWithPrefix(const char *pr
if (!LoadSymbols(useCore ? coreSymbols : extSymbols, trygl, prefix)) {
NS_ERROR("GL supports array instanced without supplying it function.");
MarkUnsupported(GLFeature::instanced_arrays);
ClearSymbols(coreSymbols);
}
}
+ if (IsSupported(GLFeature::texture_storage)) {
+ SymLoadStruct coreSymbols[] = {
+ { (PRFuncPtr*) &mSymbols.fTexStorage2D, { "TexStorage2D", nullptr } },
+ { (PRFuncPtr*) &mSymbols.fTexStorage3D, { "TexStorage3D", nullptr } },
+ END_SYMBOLS
+ };
+
+ SymLoadStruct extSymbols[] = {
+ { (PRFuncPtr*) &mSymbols.fTexStorage2D, { "TexStorage2DEXT", nullptr } },
+ { (PRFuncPtr*) &mSymbols.fTexStorage3D, { "TexStorage3DEXT", nullptr } },
+ END_SYMBOLS
+ };
+
+ bool useCore = IsFeatureProvidedByCoreSymbols(GLFeature::texture_storage);
+ if (!LoadSymbols(useCore ? coreSymbols : extSymbols, trygl, prefix)) {
+ NS_ERROR("GL supports texture storage without supplying its functions.");
+
+ MarkUnsupported(GLFeature::texture_storage);
+ MarkExtensionSupported(useCore ? ARB_texture_storage : EXT_texture_storage);
+ ClearSymbols(coreSymbols);
+ }
+ }
+
if (IsSupported(GLFeature::sampler_objects)) {
SymLoadStruct samplerObjectsSymbols[] = {
{ (PRFuncPtr*) &mSymbols.fGenSamplers, { "GenSamplers", nullptr } },
{ (PRFuncPtr*) &mSymbols.fDeleteSamplers, { "DeleteSamplers", nullptr } },
{ (PRFuncPtr*) &mSymbols.fIsSampler, { "IsSampler", nullptr } },
{ (PRFuncPtr*) &mSymbols.fBindSampler, { "BindSampler", nullptr } },
{ (PRFuncPtr*) &mSymbols.fSamplerParameteri, { "SamplerParameteri", nullptr } },
{ (PRFuncPtr*) &mSymbols.fSamplerParameteriv, { "SamplerParameteriv", nullptr } },
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -403,16 +403,17 @@ public:
EXT_sRGB,
EXT_shader_texture_lod,
EXT_texture3D,
EXT_texture_compression_dxt1,
EXT_texture_compression_s3tc,
EXT_texture_filter_anisotropic,
EXT_texture_format_BGRA8888,
EXT_texture_sRGB,
+ EXT_texture_storage,
EXT_transform_feedback,
EXT_unpack_subimage,
IMG_read_format,
IMG_texture_compression_pvrtc,
IMG_texture_npot,
KHR_debug,
NV_draw_instanced,
NV_fence,
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -2454,16 +2454,17 @@ bool AsyncPanZoomController::UpdateAnima
}
UpdateSharedCompositorFrameMetrics();
return true;
}
return false;
}
Matrix4x4 AsyncPanZoomController::GetOverscrollTransform() const {
+ ReentrantMonitorAutoEnter lock(mMonitor);
if (!IsOverscrolled()) {
return Matrix4x4();
}
// The overscroll effect is a uniform stretch along the overscrolled axis,
// with the edge of the content where we have reached the end of the
// scrollable area pinned into place.
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -55,16 +55,19 @@
#include "mozilla/layers/CompositorD3D9.h"
#endif
#include "GeckoProfiler.h"
#include "mozilla/ipc/ProtocolTypes.h"
#include "mozilla/unused.h"
#include "mozilla/Hal.h"
#include "mozilla/HalTypes.h"
#include "mozilla/StaticPtr.h"
+#ifdef MOZ_ENABLE_PROFILER_SPS
+#include "ProfilerMarkers.h"
+#endif
namespace mozilla {
namespace layers {
using namespace base;
using namespace mozilla::ipc;
using namespace mozilla::gfx;
using namespace std;
@@ -1112,16 +1115,35 @@ CompositorParent::ComputeRenderIntegrity
{
if (mLayerManager) {
return mLayerManager->ComputeRenderIntegrity();
}
return 1.0f;
}
+static void
+InsertVsyncProfilerMarker(TimeStamp aVsyncTimestamp)
+{
+#ifdef MOZ_ENABLE_PROFILER_SPS
+ MOZ_ASSERT(CompositorParent::IsInCompositorThread());
+ MOZ_ASSERT(profiler_is_active());
+ VsyncPayload* payload = new VsyncPayload(aVsyncTimestamp);
+ PROFILER_MARKER_PAYLOAD("VsyncTimestamp", payload);
+#endif
+}
+
+/*static */ void
+CompositorParent::PostInsertVsyncProfilerMarker(TimeStamp aVsyncTimestamp)
+{
+ if (profiler_is_active()) {
+ CompositorLoop()->PostTask(FROM_HERE,
+ NewRunnableFunction(InsertVsyncProfilerMarker, aVsyncTimestamp));
+ }
+}
/**
* This class handles layer updates pushed directly from child
* processes to the compositor thread. It's associated with a
* CompositorParent on the compositor thread. While it uses the
* PCompositor protocol to manage these updates, it doesn't actually
* drive compositing itself. For that it hands off work to the
* CompositorParent it's associated with.
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -256,16 +256,21 @@ public:
/**
* Lookup the indirect shadow tree for |aId| and return it if it
* exists. Otherwise null is returned. This must only be called on
* the compositor thread.
*/
static LayerTreeState* GetIndirectShadowTree(uint64_t aId);
+ /**
+ * Used by the profiler to denote when a vsync occured
+ */
+ static void PostInsertVsyncProfilerMarker(mozilla::TimeStamp aVsyncTimestamp);
+
float ComputeRenderIntegrity();
/**
* Returns true if the calling thread is the compositor thread.
*/
static bool IsInCompositorThread();
protected:
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -198,17 +198,17 @@ LayerTransactionParent::RecvUpdate(const
const TargetConfig& targetConfig,
const bool& isFirstPaint,
const bool& scheduleComposite,
const uint32_t& paintSequenceNumber,
const bool& isRepeatTransaction,
const mozilla::TimeStamp& aTransactionStart,
InfallibleTArray<EditReply>* reply)
{
- profiler_tracing("Paint", "Composite", TRACING_INTERVAL_START);
+ profiler_tracing("Paint", "LayerTransaction", TRACING_INTERVAL_START);
PROFILER_LABEL("LayerTransactionParent", "RecvUpdate",
js::ProfileEntry::Category::GRAPHICS);
#ifdef COMPOSITOR_PERFORMANCE_WARNING
TimeStamp updateStart = TimeStamp::Now();
#endif
MOZ_LAYERS_LOG(("[ParentSide] received txn with %d edits", cset.Length()));
@@ -576,16 +576,17 @@ LayerTransactionParent::RecvUpdate(const
float severity = (latency - TimeDuration::FromMilliseconds(kVisualWarningTrigger)).ToMilliseconds() /
(kVisualWarningMax - kVisualWarningTrigger);
if (severity > 1.f) {
severity = 1.f;
}
mLayerManager->VisualFrameWarning(severity);
}
+ profiler_tracing("Paint", "LayerTransaction", TRACING_INTERVAL_END);
return true;
}
bool
LayerTransactionParent::RecvSetTestSampleTime(const TimeStamp& aTime)
{
return mShadowLayersManager->SetTestSampleTime(this, aTime);
}
--- a/hal/gonk/GonkHal.cpp
+++ b/hal/gonk/GonkHal.cpp
@@ -1479,16 +1479,64 @@ SetNiceForPid(int aPid, int aNice)
}
HAL_LOG("Changed nice for pid %d from %d to %d.",
aPid, origProcPriority, aNice);
closedir(tasksDir);
}
+/*
+ * Used to store the nice value adjustments and oom_adj values for the various
+ * process priority levels.
+ */
+struct ProcessPriorityPrefs {
+ bool initialized;
+ int lowPriorityNice;
+ struct {
+ int nice;
+ int oomScoreAdj;
+ } priorities[NUM_PROCESS_PRIORITY];
+};
+
+/*
+ * Reads the preferences for the various process priority levels and sets up
+ * watchers so that if they're dynamically changed the change is reflected on
+ * the appropriate variables.
+ */
+void
+EnsureProcessPriorityPrefs(ProcessPriorityPrefs* prefs)
+{
+ if (prefs->initialized) {
+ return;
+ }
+
+ // Read the preferences for process priority levels
+ for (int i = PROCESS_PRIORITY_BACKGROUND; i < NUM_PROCESS_PRIORITY; i++) {
+ ProcessPriority priority = static_cast<ProcessPriority>(i);
+
+ // Read the nice values
+ const char* processPriorityStr = ProcessPriorityToString(priority);
+ nsPrintfCString niceStr("hal.processPriorityManager.gonk.%s.Nice",
+ processPriorityStr);
+ Preferences::AddIntVarCache(&prefs->priorities[i].nice, niceStr.get());
+
+ // Read the oom_adj scores
+ nsPrintfCString oomStr("hal.processPriorityManager.gonk.%s.OomScoreAdjust",
+ processPriorityStr);
+ Preferences::AddIntVarCache(&prefs->priorities[i].oomScoreAdj,
+ oomStr.get());
+ }
+
+ Preferences::AddIntVarCache(&prefs->lowPriorityNice,
+ "hal.processPriorityManager.gonk.LowCPUNice");
+
+ prefs->initialized = true;
+}
+
void
SetProcessPriority(int aPid,
ProcessPriority aPriority,
ProcessCPUPriority aCPUPriority,
uint32_t aBackgroundLRU)
{
HAL_LOG("SetProcessPriority(pid=%d, priority=%d, cpuPriority=%d, LRU=%u)",
aPid, aPriority, aCPUPriority, aBackgroundLRU);
@@ -1497,72 +1545,59 @@ SetProcessPriority(int aPid,
// OOM parameters according to our prefs.
//
// We could/should do this on startup instead of waiting for the first
// SetProcessPriorityCall. But in practice, the master process needs to set
// its priority early in the game, so we can reasonably rely on
// SetProcessPriority being called early in startup.
EnsureKernelLowMemKillerParamsSet();
- int32_t oomScoreAdj = 0;
- nsresult rv = Preferences::GetInt(nsPrintfCString(
- "hal.processPriorityManager.gonk.%s.OomScoreAdjust",
- ProcessPriorityToString(aPriority)).get(), &oomScoreAdj);
+ static ProcessPriorityPrefs prefs = { 0 };
+ EnsureProcessPriorityPrefs(&prefs);
+
+ int oomScoreAdj = prefs.priorities[aPriority].oomScoreAdj;
RoundOomScoreAdjUpWithBackroundLRU(oomScoreAdj, aBackgroundLRU);
- if (NS_SUCCEEDED(rv)) {
- int clampedOomScoreAdj = clamped<int>(oomScoreAdj, OOM_SCORE_ADJ_MIN,
- OOM_SCORE_ADJ_MAX);
- if(clampedOomScoreAdj != oomScoreAdj) {
- HAL_LOG("Clamping OOM adjustment for pid %d to %d", aPid,
- clampedOomScoreAdj);
- } else {
- HAL_LOG("Setting OOM adjustment for pid %d to %d", aPid,
- clampedOomScoreAdj);
- }
-
- // We try the newer interface first, and fall back to the older interface
- // on failure.
-
- if (!WriteToFile(nsPrintfCString("/proc/%d/oom_score_adj", aPid).get(),
- nsPrintfCString("%d", clampedOomScoreAdj).get()))
- {
- int oomAdj = OomAdjOfOomScoreAdj(clampedOomScoreAdj);
-
- WriteToFile(nsPrintfCString("/proc/%d/oom_adj", aPid).get(),
- nsPrintfCString("%d", oomAdj).get());
- }
+ int clampedOomScoreAdj = clamped<int>(oomScoreAdj, OOM_SCORE_ADJ_MIN,
+ OOM_SCORE_ADJ_MAX);
+ if (clampedOomScoreAdj != oomScoreAdj) {
+ HAL_LOG("Clamping OOM adjustment for pid %d to %d", aPid,
+ clampedOomScoreAdj);
} else {
- HAL_ERR("Unable to read oom_score_adj pref for priority %s; "
- "are the prefs messed up?", ProcessPriorityToString(aPriority));
- MOZ_ASSERT(false);
+ HAL_LOG("Setting OOM adjustment for pid %d to %d", aPid,
+ clampedOomScoreAdj);
}
- int32_t nice = 0;
+ // We try the newer interface first, and fall back to the older interface
+ // on failure.
+
+ if (!WriteToFile(nsPrintfCString("/proc/%d/oom_score_adj", aPid).get(),
+ nsPrintfCString("%d", clampedOomScoreAdj).get()))
+ {
+ int oomAdj = OomAdjOfOomScoreAdj(clampedOomScoreAdj);
+
+ WriteToFile(nsPrintfCString("/proc/%d/oom_adj", aPid).get(),
+ nsPrintfCString("%d", oomAdj).get());
+ }
+
+ int nice = 0;
if (aCPUPriority == PROCESS_CPU_PRIORITY_NORMAL) {
- rv = Preferences::GetInt(
- nsPrintfCString("hal.processPriorityManager.gonk.%s.Nice",
- ProcessPriorityToString(aPriority)).get(),
- &nice);
+ nice = prefs.priorities[aPriority].nice;
} else if (aCPUPriority == PROCESS_CPU_PRIORITY_LOW) {
- rv = Preferences::GetInt("hal.processPriorityManager.gonk.LowCPUNice",
- &nice);
+ nice = prefs.lowPriorityNice;
} else {
- HAL_ERR("Unable to read niceness pref for priority %s; "
- "are the prefs messed up?", ProcessPriorityToString(aPriority));
+ HAL_ERR("Unknown aCPUPriority value %d", aCPUPriority);
MOZ_ASSERT(false);
- rv = NS_ERROR_FAILURE;
+ return;
}
- if (NS_SUCCEEDED(rv)) {
- HAL_LOG("Setting nice for pid %d to %d", aPid, nice);
- SetNiceForPid(aPid, nice);
- }
+ HAL_LOG("Setting nice for pid %d to %d", aPid, nice);
+ SetNiceForPid(aPid, nice);
}
static bool
IsValidRealTimePriority(int aValue, int aSchedulePolicy)
{
return (aValue >= sched_get_priority_min(aSchedulePolicy)) &&
(aValue <= sched_get_priority_max(aSchedulePolicy));
}
@@ -1603,47 +1638,86 @@ SetRealTimeThreadPriority(pid_t aTid,
int rv = sched_setscheduler(aTid, policy, &schedParam);
if (rv) {
HAL_LOG("Failed to set thread %d to real time priority level %s; error %s",
aTid, ThreadPriorityToString(aThreadPriority), strerror(errno));
}
}
+/*
+ * Used to store the nice value adjustments and real time priorities for the
+ * various thread priority levels.
+ */
+struct ThreadPriorityPrefs {
+ bool initialized;
+ struct {
+ int nice;
+ int realTime;
+ } priorities[NUM_THREAD_PRIORITY];
+};
+
+/*
+ * Reads the preferences for the various process priority levels and sets up
+ * watchers so that if they're dynamically changed the change is reflected on
+ * the appropriate variables.
+ */
+void
+EnsureThreadPriorityPrefs(ThreadPriorityPrefs* prefs)
+{
+ if (prefs->initialized) {
+ return;
+ }
+
+ for (int i = THREAD_PRIORITY_COMPOSITOR; i < NUM_THREAD_PRIORITY; i++) {
+ ThreadPriority priority = static_cast<ThreadPriority>(i);
+
+ // Read the nice values
+ const char* threadPriorityStr = ThreadPriorityToString(priority);
+ nsPrintfCString niceStr("hal.gonk.%s.nice", threadPriorityStr);
+ Preferences::AddIntVarCache(&prefs->priorities[i].nice, niceStr.get());
+
+ // Read the real-time priorities
+ nsPrintfCString realTimeStr("hal.gonk.%s.rt_priority", threadPriorityStr);
+ Preferences::AddIntVarCache(&prefs->priorities[i].realTime,
+ realTimeStr.get());
+ }
+
+ prefs->initialized = true;
+}
+
static void
SetThreadPriority(pid_t aTid, hal::ThreadPriority aThreadPriority)
{
// See bug 999115, we can only read preferences on the main thread otherwise
// we create a race condition in HAL
MOZ_ASSERT(NS_IsMainThread(), "Can only set thread priorities on main thread");
MOZ_ASSERT(aThreadPriority >= 0);
- const char* threadPriorityStr;
+ static ThreadPriorityPrefs prefs = { 0 };
+ EnsureThreadPriorityPrefs(&prefs);
+
switch (aThreadPriority) {
case THREAD_PRIORITY_COMPOSITOR:
- threadPriorityStr = ThreadPriorityToString(aThreadPriority);
break;
default:
HAL_ERR("Unrecognized thread priority %d; Doing nothing",
aThreadPriority);
return;
}
- int realTimePriority = Preferences::GetInt(
- nsPrintfCString("hal.gonk.%s.rt_priority", threadPriorityStr).get());
+ int realTimePriority = prefs.priorities[aThreadPriority].realTime;
if (IsValidRealTimePriority(realTimePriority, SCHED_FIFO)) {
SetRealTimeThreadPriority(aTid, aThreadPriority, realTimePriority);
return;
}
- int niceValue = Preferences::GetInt(
- nsPrintfCString("hal.gonk.%s.nice", threadPriorityStr).get());
-
- SetThreadNiceValue(aTid, aThreadPriority, niceValue);
+ SetThreadNiceValue(aTid, aThreadPriority,
+ prefs.priorities[aThreadPriority].nice);
}
namespace {
/**
* This class sets the priority of threads given the kernel thread's id and a
* value taken from hal::ThreadPriority.
*
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -834,58 +834,22 @@ js::obj_getOwnPropertyDescriptor(JSConte
if (!GetFirstArgumentAsObject(cx, args, "Object.getOwnPropertyDescriptor", &obj))
return false;
RootedId id(cx);
if (!ValueToId<CanGC>(cx, args.get(1), &id))
return false;
return GetOwnPropertyDescriptor(cx, obj, id, args.rval());
}
-// ES6 draft rev25 (2014/05/22) 19.1.2.14 Object.keys(O)
+// ES6 draft rev27 (2014/08/24) 19.1.2.14 Object.keys(O)
static bool
obj_keys(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
-
- // Steps 1-2.
- RootedObject obj(cx);
- if (!GetFirstArgumentAsObject(cx, args, "Object.keys", &obj))
- return false;
-
- // Steps 3-10. Since JSITER_SYMBOLS and JSITER_HIDDEN are not passed,
- // GetPropertyNames performs the type check in step 10.c. and the
- // [[Enumerable]] check specified in step 10.c.iii.
- AutoIdVector props(cx);
- if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &props))
- return false;
-
- AutoValueVector namelist(cx);
- if (!namelist.reserve(props.length()))
- return false;
- for (size_t i = 0, len = props.length(); i < len; i++) {
- jsid id = props[i];
- JSString *str;
- if (JSID_IS_STRING(id)) {
- str = JSID_TO_STRING(id);
- } else {
- str = Int32ToString<CanGC>(cx, JSID_TO_INT(id));
- if (!str)
- return false;
- }
- namelist.infallibleAppend(StringValue(str));
- }
-
- // Step 11.
- MOZ_ASSERT(props.length() <= UINT32_MAX);
- JSObject *aobj = NewDenseCopiedArray(cx, uint32_t(namelist.length()), namelist.begin());
- if (!aobj)
- return false;
-
- args.rval().setObject(*aobj);
- return true;
+ return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY);
}
/* ES6 draft 15.2.3.16 */
static bool
obj_is(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/strict-compare-same-operands.js
@@ -0,0 +1,49 @@
+function f(l, m) {
+ var a = NaN;
+ var b = 13;
+ var c = "test";
+ var d = undefined;
+ var e = null;
+ var f = 15.7;
+ var g = Math.fround(189777.111);
+ var h = "ABC";
+ var i = String.fromCharCode(65, 65, 65);
+ var j = {};
+ var k = Math.fround("".charCodeAt(15));
+
+ // Special case rigt here:
+ assertEq(a === a, false);
+ assertEq(a !== a, true);
+ assertEq(k === k, false);
+ assertEq(k !== k, true);
+ assertEq(l === l, false);
+ assertEq(l !== l, true);
+
+ assertEq(b === b, true);
+ assertEq(b !== b, false);
+ assertEq(c === c, true);
+ assertEq(c !== c, false);
+ assertEq(d === d, true);
+ assertEq(d !== d, false);
+ assertEq(e === e, true);
+ assertEq(e !== e, false);
+ assertEq(f === f, true);
+ assertEq(f !== f, false);
+ assertEq(g === g, true);
+ assertEq(g !== g, false);
+ assertEq(h === h, true);
+ assertEq(h !== h, false);
+ assertEq(i === i, true);
+ assertEq(i !== i, false);
+ assertEq(j === j, true);
+ assertEq(j !== j, false);
+ assertEq(m === m, true);
+ assertEq(m !== m, false);
+}
+
+function test() {
+ for (var i = 0; i < 100; i++)
+ f("".charCodeAt(15), 42);
+}
+
+test();
--- a/js/src/jit/IonTypes.h
+++ b/js/src/jit/IonTypes.h
@@ -528,16 +528,26 @@ IsNullOrUndefined(MIRType type)
}
static inline bool
IsSimdType(MIRType type)
{
return type == MIRType_Int32x4 || type == MIRType_Float32x4;
};
+static inline bool
+IsMagicType(MIRType type)
+{
+ return type == MIRType_MagicHole ||
+ type == MIRType_MagicOptimizedOut ||
+ type == MIRType_MagicIsConstructing ||
+ type == MIRType_MagicOptimizedArguments ||
+ type == MIRType_MagicUninitializedLexical;
+}
+
// Returns the number of vector elements (hereby called "length") for a given
// SIMD kind. It is the Y part of the name "Foo x Y".
static inline unsigned
SimdTypeToLength(MIRType type)
{
MOZ_ASSERT(IsSimdType(type));
switch (type) {
case MIRType_Int32x4:
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -2068,21 +2068,30 @@ IonBuilder::inlineToObject(CallInfo &cal
}
IonBuilder::InliningStatus
IonBuilder::inlineToInteger(CallInfo &callInfo)
{
if (callInfo.argc() != 1 || callInfo.constructing())
return InliningStatus_NotInlined;
- MIRType type = callInfo.getArg(0)->type();
+ MDefinition *input = callInfo.getArg(0);
- // Only optimize cases where input is number, null, or boolean
- if (!IsNumberType(type) && type != MIRType_Null && type != MIRType_Boolean)
+ // Only optimize cases where input contains only number, null or boolean
+ if (input->mightBeType(MIRType_Object) ||
+ input->mightBeType(MIRType_String) ||
+ input->mightBeType(MIRType_Symbol) ||
+ input->mightBeType(MIRType_Undefined) ||
+ input->mightBeMagicType())
+ {
return InliningStatus_NotInlined;
+ }
+
+ MOZ_ASSERT(input->type() == MIRType_Value || input->type() == MIRType_Null ||
+ input->type() == MIRType_Boolean || IsNumberType(input->type()));
// Only optimize cases where output is int32
if (getInlineReturnType() != MIRType_Int32)
return InliningStatus_NotInlined;
callInfo.setImplicitlyUsedUnchecked();
MToInt32 *toInt32 = MToInt32::New(alloc(), callInfo.getArg(0));
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -2834,20 +2834,59 @@ MClampToUint8::foldsTo(TempAllocator &al
int32_t clamped = ClampIntForUint8Array(v.toInt32());
return MConstant::New(alloc, Int32Value(clamped));
}
}
return this;
}
bool
+MCompare::tryFoldEqualOperands(bool *result)
+{
+ if (lhs() != rhs())
+ return false;
+
+ // Intuitively somebody would think that if lhs == rhs,
+ // then we can just return true. (Or false for !==)
+ // However NaN !== NaN is true! So we spend some time trying
+ // to eliminate this case.
+
+ if (jsop() != JSOP_STRICTEQ && jsop() != JSOP_STRICTNE)
+ return false;
+
+ if (compareType_ == Compare_Unknown)
+ return false;
+
+ MOZ_ASSERT(compareType_ == Compare_Undefined || compareType_ == Compare_Null ||
+ compareType_ == Compare_Boolean || compareType_ == Compare_Int32 ||
+ compareType_ == Compare_Int32MaybeCoerceBoth ||
+ compareType_ == Compare_Int32MaybeCoerceLHS ||
+ compareType_ == Compare_Int32MaybeCoerceRHS || compareType_ == Compare_UInt32 ||
+ compareType_ == Compare_Double || compareType_ == Compare_DoubleMaybeCoerceLHS ||
+ compareType_ == Compare_DoubleMaybeCoerceRHS || compareType_ == Compare_Float32 ||
+ compareType_ == Compare_String || compareType_ == Compare_StrictString ||
+ compareType_ == Compare_Object || compareType_ == Compare_Value);
+
+ if (isDoubleComparison() || isFloat32Comparison()) {
+ if (!operandsAreNeverNaN())
+ return false;
+ }
+
+ *result = (jsop() == JSOP_STRICTEQ);
+ return true;
+}
+
+bool
MCompare::tryFold(bool *result)
{
JSOp op = jsop();
+ if (tryFoldEqualOperands(result))
+ return true;
+
if (compareType_ == Compare_Null || compareType_ == Compare_Undefined) {
MOZ_ASSERT(op == JSOP_EQ || op == JSOP_STRICTEQ ||
op == JSOP_NE || op == JSOP_STRICTNE);
// The LHS is the value we want to test against null or undefined.
switch (lhs()->type()) {
case MIRType_Value:
return false;
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -591,16 +591,26 @@ class MDefinition : public MNode
return true;
if (MIRType_Value != this->type())
return false;
return !resultTypeSet() || resultTypeSet()->mightBeMIRType(type);
}
+ bool mightBeMagicType() const {
+ if (IsMagicType(type()))
+ return true;
+
+ if (MIRType_Value != type())
+ return false;
+
+ return !resultTypeSet() || resultTypeSet()->hasType(types::Type::MagicArgType());
+ }
+
// Float32 specialization operations (see big comment in IonAnalysis before the Float32
// specialization algorithm).
virtual bool isFloat32Commutative() const { return false; }
virtual bool canProduceFloat32() const { return false; }
virtual bool canConsumeFloat32(MUse *use) const { return false; }
virtual void trySpecializeFloat32(TempAllocator &alloc) {}
#ifdef DEBUG
// Used during the pass that checks that Float32 flow into valid MDefinitions
@@ -3468,16 +3478,18 @@ class MCompare
// Both sides of the compare can be Float32
return compareType_ == Compare_Float32;
}
# endif
ALLOW_CLONE(MCompare)
protected:
+ bool tryFoldEqualOperands(bool *result);
+
bool congruentTo(const MDefinition *ins) const {
if (!binaryCongruentTo(ins))
return false;
return compareType() == ins->toCompare()->compareType() &&
jsop() == ins->toCompare()->jsop();
}
};
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Object/keys.js
@@ -0,0 +1,23 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var BUGNUMBER = 1038545;
+var summary = "Coerce the argument passed to Object.keys using ToObject";
+print(BUGNUMBER + ": " + summary);
+
+assertThrowsInstanceOf(() => Object.keys(), TypeError);
+assertThrowsInstanceOf(() => Object.keys(undefined), TypeError);
+assertThrowsInstanceOf(() => Object.keys(null), TypeError);
+
+assertDeepEq(Object.keys(1), []);
+assertDeepEq(Object.keys(true), []);
+if (typeof Symbol === "function") {
+ assertDeepEq(Object.keys(Symbol("foo")), []);
+}
+
+assertDeepEq(Object.keys("foo"), ["0", "1", "2"]);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -350,24 +350,32 @@ FontFaceSet::Length()
static PLDHashOperator DestroyIterator(nsPtrHashKey<nsFontFaceLoader>* aKey,
void* aUserArg)
{
aKey->GetKey()->Cancel();
return PL_DHASH_REMOVE;
}
void
+FontFaceSet::DisconnectFromRule(FontFace* aFontFace)
+{
+ nsCSSFontFaceRule* rule = aFontFace->GetRule();
+ aFontFace->DisconnectFromRule();
+ mRuleFaceMap.Remove(rule);
+}
+
+void
FontFaceSet::DestroyUserFontSet()
{
Disconnect();
mDocument = nullptr;
mPresContext = nullptr;
mLoaders.EnumerateEntries(DestroyIterator, nullptr);
for (size_t i = 0; i < mRuleFaces.Length(); i++) {
- mRuleFaces[i].mFontFace->DisconnectFromRule();
+ DisconnectFromRule(mRuleFaces[i].mFontFace);
mRuleFaces[i].mFontFace->SetUserFontEntry(nullptr);
}
for (size_t i = 0; i < mNonRuleFaces.Length(); i++) {
mNonRuleFaces[i]->SetUserFontEntry(nullptr);
}
for (size_t i = 0; i < mUnavailableFaces.Length(); i++) {
mUnavailableFaces[i]->SetUserFontEntry(nullptr);
}
@@ -582,17 +590,17 @@ FontFaceSet::UpdateRules(const nsTArray<
}
}
// Any left over FontFace objects should also cease being rule backed.
MOZ_ASSERT(!mUnavailableFaces.Contains(f),
"FontFace should not occur in mUnavailableFaces twice");
mUnavailableFaces.AppendElement(f);
- f->DisconnectFromRule();
+ DisconnectFromRule(f);
}
}
if (modified) {
IncrementGeneration(true);
mHasLoadingFontFacesIsDirty = true;
CheckLoadingStarted();
CheckLoadingFinished();
@@ -978,17 +986,17 @@ FontFaceSet::FindRuleForUserFontEntry(gf
}
}
return nullptr;
}
gfxUserFontEntry*
FontFaceSet::FindUserFontEntryForRule(nsCSSFontFaceRule* aRule)
{
- FontFace* f = aRule->GetFontFace();
+ FontFace* f = mRuleFaceMap.Get(aRule);
if (f) {
return f->GetUserFontEntry();
}
return nullptr;
}
nsresult
FontFaceSet::LogMessage(gfxUserFontEntry* aUserFontEntry,
@@ -1263,27 +1271,33 @@ FontFaceSet::DoRebuildUserFontSet()
}
mPresContext->RebuildUserFontSet();
}
FontFace*
FontFaceSet::FontFaceForRule(nsCSSFontFaceRule* aRule)
{
- FontFace* f = aRule->GetFontFace();
+ MOZ_ASSERT(aRule);
+
+ FontFace* f = mRuleFaceMap.Get(aRule);
if (f) {
+ MOZ_ASSERT(f->GetFontFaceSet() == this,
+ "existing FontFace is from another FontFaceSet?");
return f;
}
// We might be creating a FontFace object for an @font-face rule that we are
// just about to create a user font entry for, so entry might be null.
gfxUserFontEntry* entry = FindUserFontEntryForRule(aRule);
nsRefPtr<FontFace> newFontFace =
FontFace::CreateForRule(GetParentObject(), mPresContext, aRule, entry);
- aRule->SetFontFace(newFontFace);
+ MOZ_ASSERT(newFontFace->GetFontFaceSet() == this,
+ "new FontFace is from another FontFaceSet?");
+ mRuleFaceMap.Put(aRule, newFontFace);
return newFontFace;
}
void
FontFaceSet::AddUnavailableFontFace(FontFace* aFontFace)
{
MOZ_ASSERT(!aFontFace->HasRule());
MOZ_ASSERT(!aFontFace->IsInFontFaceSet());
--- a/layout/style/FontFaceSet.h
+++ b/layout/style/FontFaceSet.h
@@ -192,16 +192,22 @@ private:
bool HasAvailableFontFace(FontFace* aFontFace);
/**
* Removes any listeners and observers.
*/
void Disconnect();
/**
+ * Calls DisconnectFromRule on the given FontFace and removes its entry from
+ * mRuleFaceMap.
+ */
+ void DisconnectFromRule(FontFace* aFontFace);
+
+ /**
* Returns whether there might be any pending font loads, which should cause
* the mReady Promise not to be resolved yet.
*/
bool MightHavePendingFontLoads();
/**
* Checks to see whether it is time to replace mReady and dispatch a
* "loading" event.
@@ -300,16 +306,23 @@ private:
// The non rule backed FontFace objects that have been added to this
// FontFaceSet and their corresponding user font entries.
nsTArray<nsRefPtr<FontFace>> mNonRuleFaces;
// The non rule backed FontFace objects that have not been added to
// this FontFaceSet.
nsTArray<FontFace*> mUnavailableFaces;
+ // Map of nsCSSFontFaceRule objects to FontFace objects. We hold a weak
+ // reference to both; for actively used FontFaces, mRuleFaces will hold
+ // a strong reference to the FontFace and the FontFace will hold on to
+ // the nsCSSFontFaceRule. FontFaceSet::DisconnectFromRule will ensure its
+ // entry in this array will be removed.
+ nsDataHashtable<nsPtrHashKey<nsCSSFontFaceRule>, FontFace*> mRuleFaceMap;
+
// The overall status of the loading or loaded fonts in the FontFaceSet.
mozilla::dom::FontFaceSetLoadStatus mStatus;
// Whether mNonRuleFaces has changed since last time UpdateRules ran.
bool mNonRuleFacesDirty;
// Whether we have called MaybeResolve() on mReady.
bool mReadyIsResolved;
--- a/layout/style/test/test_font_loading_api.html
+++ b/layout/style/test/test_font_loading_api.html
@@ -300,16 +300,19 @@ function runTest() {
is(aError.name, "SyntaxError", "FontFace.loaded with invalid family name " + aFamilyName + " should be rejected with a SyntaxError (TEST 17)");
});
});
});
return familyTests;
}).then(function() {
+ // XXX Disabled this sub-test due to intermittent failures (bug 1076803).
+ return;
+
// (TEST 18) Test passing valid url() source strings to the FontFace
// constructor.
var srcTests = Promise.resolve();
gCSSFontFaceDescriptors.src.values.forEach(function(aSrc) {
srcTests = srcTests.then(function() {
var face = new FontFace("test", aSrc);
return face.load().then(function() {
ok(true, "FontFace should load with valid url() src " + aSrc + " (TEST 18)");
--- a/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
@@ -3563,36 +3563,39 @@ status_t MPEG4Source::fragmentedRead(
bool isSyncSample = false;
bool newBuffer = false;
if (mBuffer == NULL) {
newBuffer = true;
if (mCurrentSampleIndex >= mCurrentSamples.size()) {
// move to next fragment
off64_t nextMoof = mNextMoofOffset; // lastSample.offset + lastSample.size;
-
- // If we're pointing to a sidx box then we skip it.
- uint32_t hdr[2];
- if (mDataSource->readAt(nextMoof, hdr, 8) < 8) {
- return ERROR_END_OF_STREAM;
- }
- uint64_t chunk_size = ntohl(hdr[0]);
- uint32_t chunk_type = ntohl(hdr[1]);
- if (chunk_type == FOURCC('s', 'i', 'd', 'x')) {
- nextMoof += chunk_size;
- }
-
- mCurrentMoofOffset = nextMoof;
mCurrentSamples.clear();
mCurrentSampleIndex = 0;
mTrackFragmentData.mPresent = false;
- parseChunk(&nextMoof);
- if (mCurrentSampleIndex >= mCurrentSamples.size()) {
- return ERROR_END_OF_STREAM;
- }
+ uint32_t hdr[2];
+ do {
+ if (mDataSource->readAt(nextMoof, hdr, 8) < 8) {
+ return ERROR_END_OF_STREAM;
+ }
+ uint64_t chunk_size = ntohl(hdr[0]);
+ uint32_t chunk_type = ntohl(hdr[1]);
+
+ // If we're pointing to a segment type or sidx box then we skip them.
+ if (chunk_type == FOURCC('s', 't', 'y', 'p') ||
+ chunk_type == FOURCC('s', 'i', 'd', 'x')) {
+ nextMoof += chunk_size;
+ continue;
+ }
+ mCurrentMoofOffset = nextMoof;
+ status_t ret = parseChunk(&nextMoof);
+ if (ret != OK) {
+ return ret;
+ }
+ } while (mCurrentSamples.size() == 0);
if (mTrackFragmentData.mPresent) {
mCurrentTime = mTrackFragmentData.mBaseMediaDecodeTime;
}
}
const Sample *smpl = &mCurrentSamples[mCurrentSampleIndex];
offset = smpl->offset;
--- a/media/webrtc/signaling/src/sipcc/core/sdp/sdp_token.c
+++ b/media/webrtc/signaling/src/sipcc/core/sdp/sdp_token.c
@@ -1370,49 +1370,49 @@ sdp_result_e sdp_parse_media (sdp_t *sdp
* transport/profile types per line, so these are handled differently. */
if ((mca_p->transport == SDP_TRANSPORT_AAL2_ITU) ||
(mca_p->transport == SDP_TRANSPORT_AAL2_ATMF) ||
(mca_p->transport == SDP_TRANSPORT_AAL2_CUSTOM)) {
if (sdp_parse_multiple_profile_payload_types(sdp_p, mca_p, ptr) !=
SDP_SUCCESS) {
sdp_p->conf_p->num_invalid_param++;
- SDP_FREE(mca_p);
+ SDP_FREE(mca_p);
return (SDP_INVALID_PARAMETER);
}
- } else {
- /* Transport is a non-AAL2 type. Parse payloads normally. */
- sdp_parse_payload_types(sdp_p, mca_p, ptr);
- }
-
/* Parse DTLS/SCTP port */
- if (mca_p->transport == SDP_TRANSPORT_DTLSSCTP) {
+ } else if (mca_p->transport == SDP_TRANSPORT_DTLSSCTP) {
ptr = sdp_getnextstrtok(ptr, port, sizeof(port), " \t", &result);
if (result != SDP_SUCCESS) {
sdp_parse_error(sdp_p->peerconnection,
"%s No sctp port specified in m= media line, "
"parse failed.", sdp_p->debug_str);
SDP_FREE(mca_p);
sdp_p->conf_p->num_invalid_param++;
return (SDP_INVALID_PARAMETER);
}
port_ptr = port;
if (sdp_getchoosetok(port_ptr, &port_ptr, "/ \t", &result)) {
- sctp_port = SDP_CHOOSE_PARAM;
+ sctp_port = SDP_CHOOSE_PARAM;
} else {
- sctp_port = sdp_getnextnumtok(port_ptr, (const char **)&port_ptr,
+ sctp_port = sdp_getnextnumtok(port_ptr, (const char **)&port_ptr,
"/ \t", &result);
if (result != SDP_SUCCESS) {
- return (SDP_INVALID_PARAMETER);
+ return (SDP_INVALID_PARAMETER);
}
mca_p->sctpport = sctp_port;
}
+ } else {
+ /* Transport is a non-AAL2 type and not SCTP. Parse payloads
+ normally. */
+ sdp_parse_payload_types(sdp_p, mca_p, ptr);
}
+
/* Media line params are valid. Add it into the SDP. */
sdp_p->mca_count++;
if (sdp_p->mca_p == NULL) {
sdp_p->mca_p = mca_p;
} else {
for (next_mca_p = sdp_p->mca_p; next_mca_p->next_p != NULL;
next_mca_p = next_mca_p->next_p) {
; // Empty For
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -31,21 +31,23 @@
#include "nscore.h"
#include "nsStackWalk.h"
#include "js/HashTable.h"
#include "js/Vector.h"
#include "mozilla/Assertions.h"
#include "mozilla/HashFunctions.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/JSONWriter.h"
#include "mozilla/Likely.h"
#include "mozilla/MemoryReporting.h"
-// CodeAddressService is defined entirely in the header, so this does not make DMD
-// depend on XPCOM's object file.
+// CodeAddressService is defined entirely in the header, so this does not make
+// DMD depend on XPCOM's object file.
#include "CodeAddressService.h"
// MOZ_REPLACE_ONLY_MEMALIGN saves us from having to define
// replace_{posix_memalign,aligned_alloc,valloc}. It requires defining
// PAGE_SIZE. Nb: sysconf() is expensive, but it's only used for (the obsolete
// and rarely used) valloc.
#define MOZ_REPLACE_ONLY_MEMALIGN 1
@@ -216,53 +218,41 @@ StatusMsg(const char* aFmt, ...)
#endif
va_end(ap);
}
/* static */ void
InfallibleAllocPolicy::ExitOnFailure(const void* aP)
{
if (!aP) {
- StatusMsg("out of memory; aborting\n");
- MOZ_CRASH();
+ MOZ_CRASH("DMD out of memory; aborting");
}
}
-void
-Writer::Write(const char* aFmt, ...) const
+class FpWriteFunc : public JSONWriteFunc
{
- va_list ap;
- va_start(ap, aFmt);
- mWriterFun(mWriteState, aFmt, ap);
- va_end(ap);
-}
+public:
+ explicit FpWriteFunc(FILE* aFp) : mFp(aFp) {}
+ ~FpWriteFunc() { fclose(mFp); }
-#define W(...) aWriter.Write(__VA_ARGS__);
-
-#define WriteSeparator(...) \
- W("#-----------------------------------------------------------------\n\n");
+ void Write(const char* aStr) { fputs(aStr, mFp); }
-MOZ_EXPORT void
-FpWrite(void* aWriteState, const char* aFmt, va_list aAp)
-{
- FILE* fp = static_cast<FILE*>(aWriteState);
- vfprintf(fp, aFmt, aAp);
-}
+private:
+ FILE* mFp;
+};
static double
Percent(size_t part, size_t whole)
{
return (whole == 0) ? 0 : 100 * (double)part / whole;
}
-// Commifies the number and prepends a '~' if requested. Best used with
-// |kBufLen| and |gBuf[1234]|, because they should be big enough for any number
-// we'll see.
+// Commifies the number.
static char*
-Show(size_t n, char* buf, size_t buflen, bool addTilde = false)
+Show(size_t n, char* buf, size_t buflen)
{
int nc = 0, i = 0, lasti = buflen - 2;
buf[lasti + 1] = '\0';
if (n == 0) {
buf[lasti - i] = '0';
i++;
} else {
while (n > 0) {
@@ -273,37 +263,20 @@ Show(size_t n, char* buf, size_t buflen,
}
buf[lasti - i] = static_cast<char>((n % 10) + '0');
i++;
n /= 10;
}
}
int firstCharIndex = lasti - i + 1;
- if (addTilde) {
- firstCharIndex--;
- buf[firstCharIndex] = '~';
- }
-
MOZ_ASSERT(firstCharIndex >= 0);
return &buf[firstCharIndex];
}
-static const char*
-Plural(size_t aN)
-{
- return aN == 1 ? "" : "s";
-}
-
-// Used by calls to Show().
-static const size_t kBufLen = 64;
-static char gBuf1[kBufLen];
-static char gBuf2[kBufLen];
-static char gBuf3[kBufLen];
-
//---------------------------------------------------------------------------
// Options (Part 1)
//---------------------------------------------------------------------------
class Options
{
template <typename T>
struct NumOption
@@ -321,32 +294,33 @@ class Options
Test, // do some basic correctness tests
Stress // do some performance stress tests
};
char* mDMDEnvVar; // a saved copy, for later printing
NumOption<size_t> mSampleBelowSize;
NumOption<uint32_t> mMaxFrames;
- NumOption<uint32_t> mMaxRecords;
+ bool mShowDumpStats;
Mode mMode;
void BadArg(const char* aArg);
static const char* ValueIfMatch(const char* aArg, const char* aOptionName);
static bool GetLong(const char* aArg, const char* aOptionName,
- long aMin, long aMax, long* aN);
+ long aMin, long aMax, long* aValue);
+ static bool GetBool(const char* aArg, const char* aOptionName, bool* aValue);
public:
explicit Options(const char* aDMDEnvVar);
const char* DMDEnvVar() const { return mDMDEnvVar; }
size_t SampleBelowSize() const { return mSampleBelowSize.mActual; }
size_t MaxFrames() const { return mMaxFrames.mActual; }
- size_t MaxRecords() const { return mMaxRecords.mActual; }
+ size_t ShowDumpStats() const { return mShowDumpStats; }
void SetSampleBelowSize(size_t aN) { mSampleBelowSize.mActual = aN; }
bool IsTestMode() const { return mMode == Test; }
bool IsStressMode() const { return mMode == Stress; }
};
static Options *gOptions;
@@ -673,40 +647,37 @@ typedef CodeAddressService<StringTable,
//---------------------------------------------------------------------------
class StackTrace
{
public:
static const uint32_t MaxFrames = 24;
private:
- uint32_t mLength; // The number of PCs.
- void* mPcs[MaxFrames]; // The PCs themselves. If --max-frames is less
- // than 24, this array is bigger than necessary,
- // but that case is unusual.
+ uint32_t mLength; // The number of PCs.
+ const void* mPcs[MaxFrames]; // The PCs themselves. If --max-frames is less
+ // than 24, this array is bigger than
+ // necessary, but that case is unusual.
public:
StackTrace() : mLength(0) {}
uint32_t Length() const { return mLength; }
- void* Pc(uint32_t i) const { MOZ_ASSERT(i < mLength); return mPcs[i]; }
+ const void* Pc(uint32_t i) const
+ {
+ MOZ_ASSERT(i < mLength);
+ return mPcs[i];
+ }
uint32_t Size() const { return mLength * sizeof(mPcs[0]); }
// The stack trace returned by this function is interned in gStackTraceTable,
// and so is immortal and unmovable.
static const StackTrace* Get(Thread* aT);
- void Sort()
- {
- qsort(mPcs, mLength, sizeof(mPcs[0]), StackTrace::Cmp);
- }
-
- void Print(const Writer& aWriter, CodeAddressService* aLocService) const;
-
// Hash policy.
typedef StackTrace* Lookup;
static uint32_t hash(const StackTrace* const& aSt)
{
return mozilla::HashBytes(aSt->mPcs, aSt->Size());
}
@@ -714,57 +685,45 @@ public:
static bool match(const StackTrace* const& aA,
const StackTrace* const& aB)
{
return aA->mLength == aB->mLength &&
memcmp(aA->mPcs, aB->mPcs, aA->Size()) == 0;
}
private:
- static void StackWalkCallback(void* aPc, void* aSp, void* aClosure)
+ static void StackWalkCallback(uint32_t aFrameNumber, void* aPc, void* aSp,
+ void* aClosure)
{
StackTrace* st = (StackTrace*) aClosure;
MOZ_ASSERT(st->mLength < MaxFrames);
st->mPcs[st->mLength] = aPc;
st->mLength++;
- }
-
- static int Cmp(const void* aA, const void* aB)
- {
- const void* const a = *static_cast<const void* const*>(aA);
- const void* const b = *static_cast<const void* const*>(aB);
- if (a < b) return -1;
- if (a > b) return 1;
- return 0;
+ MOZ_ASSERT(st->mLength == aFrameNumber);
}
};
typedef js::HashSet<StackTrace*, StackTrace, InfallibleAllocPolicy>
StackTraceTable;
static StackTraceTable* gStackTraceTable = nullptr;
+typedef js::HashSet<const StackTrace*, js::DefaultHasher<const StackTrace*>,
+ InfallibleAllocPolicy>
+ StackTraceSet;
+
+typedef js::HashSet<const void*, js::DefaultHasher<const void*>,
+ InfallibleAllocPolicy>
+ PointerSet;
+typedef js::HashMap<const void*, uint32_t, js::DefaultHasher<const void*>,
+ InfallibleAllocPolicy>
+ PointerIdMap;
+
// We won't GC the stack trace table until it this many elements.
static uint32_t gGCStackTraceTableWhenSizeExceeds = 4 * 1024;
-void
-StackTrace::Print(const Writer& aWriter, CodeAddressService* aLocService) const
-{
- if (mLength == 0) {
- W(" (empty)\n"); // StackTrace::Get() must have failed
- return;
- }
-
- static const size_t buflen = 1024;
- char buf[buflen];
- for (uint32_t i = 0; i < mLength; i++) {
- aLocService->GetLocation(Pc(i), buf, buflen);
- aWriter.Write(" %s\n", buf);
- }
-}
-
/* static */ const StackTrace*
StackTrace::Get(Thread* aT)
{
MOZ_ASSERT(gStateLock->IsLocked());
MOZ_ASSERT(aT->InterceptsAreBlocked());
// On Windows, NS_StackWalk can acquire a lock from the shared library
// loader. Another thread might call malloc while holding that lock (when
@@ -792,19 +751,19 @@ StackTrace::Get(Thread* aT)
// attempt to create a semaphore (which can happen in printf) could
// deadlock.
//
// However, the most complex thing DMD does after Get() returns is to put
// something in a hash table, which might call
// InfallibleAllocPolicy::malloc_. I'm not yet sure if this needs special
// handling, hence the forced abort. Sorry. If you hit this, please file
// a bug and CC nnethercote.
- MOZ_CRASH();
+ MOZ_CRASH("unexpected case in StackTrace::Get()");
} else {
- MOZ_CRASH(); // should be impossible
+ MOZ_CRASH("impossible case in StackTrace::Get()");
}
StackTraceTable::AddPtr p = gStackTraceTable->lookupForAdd(&tmp);
if (!p) {
StackTrace* stnew = InfallibleAllocPolicy::new_<StackTrace>(tmp);
(void)gStackTraceTable->add(p, stnew);
}
return *p;
@@ -893,16 +852,18 @@ public:
: mPtr(aPtr),
mReqSize(aReqSize),
mAllocStackTrace_mSampled(aAllocStackTrace, aSampled),
mReportStackTrace_mReportedOnAlloc() // all fields get zeroed
{
MOZ_ASSERT(aAllocStackTrace);
}
+ const void* Address() const { return mPtr; }
+
size_t ReqSize() const { return mReqSize; }
// Sampled blocks always have zero slop.
size_t SlopSize() const
{
return IsSampled() ? 0 : MallocSizeOf(mPtr) - mReqSize;
}
@@ -916,33 +877,50 @@ public:
return mAllocStackTrace_mSampled.Tag();
}
const StackTrace* AllocStackTrace() const
{
return mAllocStackTrace_mSampled.Ptr();
}
- const StackTrace* ReportStackTrace1() const {
+ const StackTrace* ReportStackTrace1() const
+ {
return mReportStackTrace_mReportedOnAlloc[0].Ptr();
}
- const StackTrace* ReportStackTrace2() const {
+ const StackTrace* ReportStackTrace2() const
+ {
return mReportStackTrace_mReportedOnAlloc[1].Ptr();
}
- bool ReportedOnAlloc1() const {
+ bool ReportedOnAlloc1() const
+ {
return mReportStackTrace_mReportedOnAlloc[0].Tag();
}
- bool ReportedOnAlloc2() const {
+ bool ReportedOnAlloc2() const
+ {
return mReportStackTrace_mReportedOnAlloc[1].Tag();
}
- uint32_t NumReports() const {
+ void AddStackTracesToTable(StackTraceSet& aStackTraces) const
+ {
+ aStackTraces.put(AllocStackTrace()); // never null
+ const StackTrace* st;
+ if ((st = ReportStackTrace1())) { // may be null
+ aStackTraces.put(st);
+ }
+ if ((st = ReportStackTrace2())) { // may be null
+ aStackTraces.put(st);
+ }
+ }
+
+ uint32_t NumReports() const
+ {
if (ReportStackTrace2()) {
MOZ_ASSERT(ReportStackTrace1());
return 2;
}
if (ReportStackTrace1()) {
return 1;
}
return 0;
@@ -989,60 +967,47 @@ public:
{
return aB.mPtr == aPtr;
}
};
typedef js::HashSet<Block, Block, InfallibleAllocPolicy> BlockTable;
static BlockTable* gBlockTable = nullptr;
-typedef js::HashSet<const StackTrace*, js::DefaultHasher<const StackTrace*>,
- InfallibleAllocPolicy>
- StackTraceSet;
-
// Add a pointer to each live stack trace into the given StackTraceSet. (A
// stack trace is live if it's used by one of the live blocks.)
static void
GatherUsedStackTraces(StackTraceSet& aStackTraces)
{
MOZ_ASSERT(gStateLock->IsLocked());
MOZ_ASSERT(Thread::Fetch()->InterceptsAreBlocked());
aStackTraces.finish();
- aStackTraces.init(1024);
+ aStackTraces.init(512);
for (BlockTable::Range r = gBlockTable->all(); !r.empty(); r.popFront()) {
const Block& b = r.front();
- aStackTraces.put(b.AllocStackTrace());
- aStackTraces.put(b.ReportStackTrace1());
- aStackTraces.put(b.ReportStackTrace2());
+ b.AddStackTracesToTable(aStackTraces);
}
-
- // Any of the stack traces added above may have been null. For the sake of
- // cleanliness, don't leave the null pointer in the set.
- aStackTraces.remove(nullptr);
}
// Delete stack traces that we aren't using, and compact our hashtable.
static void
GCStackTraces()
{
MOZ_ASSERT(gStateLock->IsLocked());
MOZ_ASSERT(Thread::Fetch()->InterceptsAreBlocked());
StackTraceSet usedStackTraces;
GatherUsedStackTraces(usedStackTraces);
// Delete all unused stack traces from gStackTraceTable. The Enum destructor
// will automatically rehash and compact the table.
- for (StackTraceTable::Enum e(*gStackTraceTable);
- !e.empty();
- e.popFront()) {
+ for (StackTraceTable::Enum e(*gStackTraceTable); !e.empty(); e.popFront()) {
StackTrace* const& st = e.front();
-
if (!usedStackTraces.has(st)) {
e.removeFront();
InfallibleAllocPolicy::delete_(st);
}
}
// Schedule a GC when we have twice as many stack traces as we had right after
// this GC finished.
@@ -1245,203 +1210,16 @@ replace_free(void* aPtr)
FreeCallback(aPtr, t);
gMallocTable->free(aPtr);
}
namespace mozilla {
namespace dmd {
//---------------------------------------------------------------------------
-// Heap block records
-//---------------------------------------------------------------------------
-
-class RecordKey
-{
-public:
- const StackTrace* const mAllocStackTrace; // never null
-protected:
- const StackTrace* const mReportStackTrace1; // nullptr if unreported
- const StackTrace* const mReportStackTrace2; // nullptr if not 2x-reported
-
-public:
- explicit RecordKey(const Block& aB)
- : mAllocStackTrace(aB.AllocStackTrace()),
- mReportStackTrace1(aB.ReportStackTrace1()),
- mReportStackTrace2(aB.ReportStackTrace2())
- {
- MOZ_ASSERT(mAllocStackTrace);
- }
-
- // Hash policy.
-
- typedef RecordKey Lookup;
-
- static uint32_t hash(const RecordKey& aKey)
- {
- return mozilla::HashGeneric(aKey.mAllocStackTrace,
- aKey.mReportStackTrace1,
- aKey.mReportStackTrace2);
- }
-
- static bool match(const RecordKey& aA, const RecordKey& aB)
- {
- return aA.mAllocStackTrace == aB.mAllocStackTrace &&
- aA.mReportStackTrace1 == aB.mReportStackTrace1 &&
- aA.mReportStackTrace2 == aB.mReportStackTrace2;
- }
-};
-
-class RecordSize
-{
- static const size_t kReqBits = sizeof(size_t) * 8 - 1; // 31 or 63
-
- size_t mReq; // size requested
- size_t mSlop:kReqBits; // slop bytes
- size_t mSampled:1; // were one or more blocks contributing to this
- // RecordSize sampled?
-public:
- RecordSize()
- : mReq(0),
- mSlop(0),
- mSampled(false)
- {}
-
- size_t Req() const { return mReq; }
- size_t Slop() const { return mSlop; }
- size_t Usable() const { return mReq + mSlop; }
-
- bool IsSampled() const { return mSampled; }
-
- void Add(const Block& aB)
- {
- mReq += aB.ReqSize();
- mSlop += aB.SlopSize();
- mSampled = mSampled || aB.IsSampled();
- }
-
- void Add(const RecordSize& aRecordSize)
- {
- mReq += aRecordSize.Req();
- mSlop += aRecordSize.Slop();
- mSampled = mSampled || aRecordSize.IsSampled();
- }
-
- static int CmpByUsable(const RecordSize& aA, const RecordSize& aB)
- {
- // Primary sort: put bigger usable sizes first.
- if (aA.Usable() > aB.Usable()) return -1;
- if (aA.Usable() < aB.Usable()) return 1;
-
- // Secondary sort: put bigger requested sizes first.
- if (aA.Req() > aB.Req()) return -1;
- if (aA.Req() < aB.Req()) return 1;
-
- // Tertiary sort: put non-sampled records before sampled records.
- if (!aA.mSampled && aB.mSampled) return -1;
- if ( aA.mSampled && !aB.mSampled) return 1;
-
- return 0;
- }
-};
-
-// A collection of one or more heap blocks with a common RecordKey.
-class Record : public RecordKey
-{
- // The RecordKey base class serves as the key in RecordTables. These two
- // fields constitute the value, so it's ok for them to be |mutable|.
- mutable uint32_t mNumBlocks; // number of blocks with this RecordKey
- mutable RecordSize mRecordSize; // combined size of those blocks
-
-public:
- explicit Record(const RecordKey& aKey)
- : RecordKey(aKey),
- mNumBlocks(0),
- mRecordSize()
- {}
-
- uint32_t NumBlocks() const { return mNumBlocks; }
-
- const RecordSize& GetRecordSize() const { return mRecordSize; }
-
- // This is |const| thanks to the |mutable| fields above.
- void Add(const Block& aB) const
- {
- mNumBlocks++;
- mRecordSize.Add(aB);
- }
-
- void Print(const Writer& aWriter, CodeAddressService* aLocService,
- uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
- size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
- size_t aTotalUsableSize, bool aShowCategoryPercentage,
- bool aShowReportedAt) const;
-
- static int CmpByUsable(const void* aA, const void* aB)
- {
- const Record* const a = *static_cast<const Record* const*>(aA);
- const Record* const b = *static_cast<const Record* const*>(aB);
-
- return RecordSize::CmpByUsable(a->mRecordSize, b->mRecordSize);
- }
-};
-
-typedef js::HashSet<Record, Record, InfallibleAllocPolicy> RecordTable;
-
-void
-Record::Print(const Writer& aWriter, CodeAddressService* aLocService,
- uint32_t aM, uint32_t aN, const char* aStr, const char* astr,
- size_t aCategoryUsableSize, size_t aCumulativeUsableSize,
- size_t aTotalUsableSize, bool aShowCategoryPercentage,
- bool aShowReportedAt) const
-{
- bool showTilde = mRecordSize.IsSampled();
-
- W("%s {\n", aStr);
- W(" %s block%s in heap block record %s of %s\n",
- Show(mNumBlocks, gBuf1, kBufLen, showTilde), Plural(mNumBlocks),
- Show(aM, gBuf2, kBufLen),
- Show(aN, gBuf3, kBufLen));
-
- W(" %s bytes (%s requested / %s slop)\n",
- Show(mRecordSize.Usable(), gBuf1, kBufLen, showTilde),
- Show(mRecordSize.Req(), gBuf2, kBufLen, showTilde),
- Show(mRecordSize.Slop(), gBuf3, kBufLen, showTilde));
-
- W(" %4.2f%% of the heap (%4.2f%% cumulative)\n",
- Percent(mRecordSize.Usable(), aTotalUsableSize),
- Percent(aCumulativeUsableSize, aTotalUsableSize));
-
- if (aShowCategoryPercentage) {
- W(" %4.2f%% of %s (%4.2f%% cumulative)\n",
- Percent(mRecordSize.Usable(), aCategoryUsableSize),
- astr,
- Percent(aCumulativeUsableSize, aCategoryUsableSize));
- }
-
- W(" Allocated at {\n");
- mAllocStackTrace->Print(aWriter, aLocService);
- W(" }\n");
-
- if (aShowReportedAt) {
- if (mReportStackTrace1) {
- W(" Reported at {\n");
- mReportStackTrace1->Print(aWriter, aLocService);
- W(" }\n");
- }
- if (mReportStackTrace2) {
- W(" Reported again at {\n");
- mReportStackTrace2->Print(aWriter, aLocService);
- W(" }\n");
- }
- }
-
- W("}\n\n");
-}
-
-//---------------------------------------------------------------------------
// Options (Part 2)
//---------------------------------------------------------------------------
// Given an |aOptionName| like "foo", succeed if |aArg| has the form "foo=blah"
// (where "blah" is non-empty) and return the pointer to "blah". |aArg| can
// have leading space chars (but not other whitespace).
const char*
Options::ValueIfMatch(const char* aArg, const char* aOptionName)
@@ -1454,23 +1232,41 @@ Options::ValueIfMatch(const char* aArg,
}
return nullptr;
}
// Extracts a |long| value for an option from an argument. It must be within
// the range |aMin..aMax| (inclusive).
bool
Options::GetLong(const char* aArg, const char* aOptionName,
- long aMin, long aMax, long* aN)
+ long aMin, long aMax, long* aValue)
{
if (const char* optionValue = ValueIfMatch(aArg, aOptionName)) {
char* endPtr;
- *aN = strtol(optionValue, &endPtr, /* base */ 10);
- if (!*endPtr && aMin <= *aN && *aN <= aMax &&
- *aN != LONG_MIN && *aN != LONG_MAX) {
+ *aValue = strtol(optionValue, &endPtr, /* base */ 10);
+ if (!*endPtr && aMin <= *aValue && *aValue <= aMax &&
+ *aValue != LONG_MIN && *aValue != LONG_MAX) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Extracts a |bool| value for an option -- encoded as "yes" or "no" -- from an
+// argument.
+bool
+Options::GetBool(const char* aArg, const char* aOptionName, bool* aValue)
+{
+ if (const char* optionValue = ValueIfMatch(aArg, aOptionName)) {
+ if (strcmp(optionValue, "yes") == 0) {
+ *aValue = true;
+ return true;
+ }
+ if (strcmp(optionValue, "no") == 0) {
+ *aValue = false;
return true;
}
}
return false;
}
// The sample-below default is a prime number close to 4096.
// - Why that size? Because it's *much* faster but only moderately less precise
@@ -1479,17 +1275,17 @@ Options::GetLong(const char* aArg, const
// of 4096, for example, then our alloc counter would only take on even
// values, because jemalloc always rounds up requests sizes. In contrast, a
// prime size will explore all possible values of the alloc counter.
//
Options::Options(const char* aDMDEnvVar)
: mDMDEnvVar(InfallibleAllocPolicy::strdup_(aDMDEnvVar)),
mSampleBelowSize(4093, 100 * 100 * 1000),
mMaxFrames(StackTrace::MaxFrames, StackTrace::MaxFrames),
- mMaxRecords(1000, 1000000),
+ mShowDumpStats(false),
mMode(Normal)
{
char* e = mDMDEnvVar;
if (strcmp(e, "1") != 0) {
bool isEnd = false;
while (!isEnd) {
// Consume leading whitespace.
while (isspace(*e)) {
@@ -1505,24 +1301,25 @@ Options::Options(const char* aDMDEnvVar)
e++;
}
char replacedChar = *e;
isEnd = replacedChar == '\0';
*e = '\0';
// Handle arg
long myLong;
+ bool myBool;
if (GetLong(arg, "--sample-below", 1, mSampleBelowSize.mMax, &myLong)) {
mSampleBelowSize.mActual = myLong;
} else if (GetLong(arg, "--max-frames", 1, mMaxFrames.mMax, &myLong)) {
mMaxFrames.mActual = myLong;
- } else if (GetLong(arg, "--max-records", 1, mMaxRecords.mMax, &myLong)) {
- mMaxRecords.mActual = myLong;
+ } else if (GetBool(arg, "--show-dump-stats", &myBool)) {
+ mShowDumpStats = myBool;
} else if (strcmp(arg, "--mode=normal") == 0) {
mMode = Options::Normal;
} else if (strcmp(arg, "--mode=test") == 0) {
mMode = Options::Test;
} else if (strcmp(arg, "--mode=stress") == 0) {
mMode = Options::Stress;
@@ -1555,49 +1352,49 @@ Options::BadArg(const char* aArg)
StatusMsg("The following options are allowed; defaults are shown in [].\n");
StatusMsg(" --sample-below=<1..%d> Sample blocks smaller than this [%d]\n",
int(mSampleBelowSize.mMax),
int(mSampleBelowSize.mDefault));
StatusMsg(" (prime numbers are recommended)\n");
StatusMsg(" --max-frames=<1..%d> Max. depth of stack traces [%d]\n",
int(mMaxFrames.mMax),
int(mMaxFrames.mDefault));
- StatusMsg(" --max-records=<1..%u> Max. number of records printed [%u]\n",
- mMaxRecords.mMax,
- mMaxRecords.mDefault);
+ StatusMsg(" --show-dump-stats=<yes|no> Show stats about dumps? [no]\n");
StatusMsg(" --mode=<normal|test|stress> Mode of operation [normal]\n");
StatusMsg("\n");
exit(1);
}
//---------------------------------------------------------------------------
// DMD start-up
//---------------------------------------------------------------------------
#ifdef XP_MACOSX
static void
-NopStackWalkCallback(void* aPc, void* aSp, void* aClosure)
+NopStackWalkCallback(uint32_t aFrameNumber, void* aPc, void* aSp,
+ void* aClosure)
{
}
#endif
// Note that fopen() can allocate.
static FILE*
OpenOutputFile(const char* aFilename)
{
FILE* fp = fopen(aFilename, "w");
if (!fp) {
StatusMsg("can't create %s file: %s\n", aFilename, strerror(errno));
exit(1);
}
return fp;
}
-static void RunTestMode(FILE* fp);
-static void RunStressMode(FILE* fp);
+static void RunTestMode(UniquePtr<FpWriteFunc> aF1, UniquePtr<FpWriteFunc> aF2,
+ UniquePtr<FpWriteFunc> aF3, UniquePtr<FpWriteFunc> aF4);
+static void RunStressMode(UniquePtr<FpWriteFunc> aF);
// WARNING: this function runs *very* early -- before all static initializers
// have run. For this reason, non-scalar globals such as gStateLock and
// gStackTraceTable are allocated dynamically (so we can guarantee their
// construction in this function) rather than statically.
static void
Init(const malloc_table_t* aMallocTable)
{
@@ -1645,37 +1442,38 @@ Init(const malloc_table_t* aMallocTable)
gStackTraceTable = InfallibleAllocPolicy::new_<StackTraceTable>();
gStackTraceTable->init(8192);
gBlockTable = InfallibleAllocPolicy::new_<BlockTable>();
gBlockTable->init(8192);
}
if (gOptions->IsTestMode()) {
- // OpenOutputFile() can allocate. So do this before setting
- // gIsDMDRunning so those allocations don't show up in our results. Once
- // gIsDMDRunning is set we are intercepting malloc et al. in earnest.
- FILE* fp = OpenOutputFile("test.dmd");
+ // Do all necessary allocations before setting gIsDMDRunning so those
+ // allocations don't show up in our results. Once gIsDMDRunning is set we
+ // are intercepting malloc et al. in earnest.
+ auto f1 = MakeUnique<FpWriteFunc>(OpenOutputFile("full1.json"));
+ auto f2 = MakeUnique<FpWriteFunc>(OpenOutputFile("full2.json"));
+ auto f3 = MakeUnique<FpWriteFunc>(OpenOutputFile("full3.json"));
+ auto f4 = MakeUnique<FpWriteFunc>(OpenOutputFile("full4.json"));
gIsDMDRunning = true;
StatusMsg("running test mode...\n");
- RunTestMode(fp);
+ RunTestMode(Move(f1), Move(f2), Move(f3), Move(f4));
StatusMsg("finished test mode\n");
- fclose(fp);
exit(0);
}
if (gOptions->IsStressMode()) {
- FILE* fp = OpenOutputFile("stress.dmd");
+ auto f = MakeUnique<FpWriteFunc>(OpenOutputFile("stress.json"));
gIsDMDRunning = true;
StatusMsg("running stress mode...\n");
- RunStressMode(fp);
+ RunStressMode(Move(f));
StatusMsg("finished stress mode\n");
- fclose(fp);
exit(0);
}
gIsDMDRunning = true;
}
//---------------------------------------------------------------------------
// DMD reporting and unreporting
@@ -1714,66 +1512,23 @@ ReportOnAlloc(const void* aPtr)
{
ReportHelper(aPtr, /* onAlloc */ true);
}
//---------------------------------------------------------------------------
// DMD output
//---------------------------------------------------------------------------
-static void
-PrintSortedRecords(const Writer& aWriter, CodeAddressService* aLocService,
- int (*aCmp)(const void*, const void*),
- const char* aStr, const char* astr,
- const RecordTable& aRecordTable,
- size_t aCategoryUsableSize, size_t aTotalUsableSize,
- bool aShowCategoryPercentage, bool aShowReportedAt)
-{
- StatusMsg(" creating and sorting %s heap block record array...\n", astr);
-
- // Convert the table into a sorted array.
- js::Vector<const Record*, 0, InfallibleAllocPolicy> recordArray;
- recordArray.reserve(aRecordTable.count());
- for (RecordTable::Range r = aRecordTable.all();
- !r.empty();
- r.popFront()) {
- recordArray.infallibleAppend(&r.front());
- }
- qsort(recordArray.begin(), recordArray.length(), sizeof(recordArray[0]),
- aCmp);
-
- WriteSeparator();
-
- if (recordArray.length() == 0) {
- W("# no %s heap blocks\n\n", astr);
- return;
- }
-
- StatusMsg(" printing %s heap block record array...\n", astr);
- size_t cumulativeUsableSize = 0;
-
- // Limit the number of records printed, because fix_linux_stack.py is too
- // damn slow. Note that we don't break out of this loop because we need to
- // keep adding to |cumulativeUsableSize|.
- uint32_t numRecords = recordArray.length();
- uint32_t maxRecords = gOptions->MaxRecords();
- for (uint32_t i = 0; i < numRecords; i++) {
- const Record* r = recordArray[i];
- cumulativeUsableSize += r->GetRecordSize().Usable();
- if (i < maxRecords) {
- r->Print(aWriter, aLocService, i+1, numRecords, aStr, astr,
- aCategoryUsableSize, cumulativeUsableSize, aTotalUsableSize,
- aShowCategoryPercentage, aShowReportedAt);
- } else if (i == maxRecords) {
- W("# %s: stopping after %s heap block records\n\n", aStr,
- Show(maxRecords, gBuf1, kBufLen));
- }
- }
- MOZ_ASSERT(aCategoryUsableSize == cumulativeUsableSize);
-}
+// The version number of the output format. Increment this if you make
+// backwards-incompatible changes to the format.
+//
+// Version history:
+// - 1: The original format (bug 1044709).
+//
+static const int kOutputVersionNumber = 1;
// Note that, unlike most SizeOf* functions, this function does not take a
// |mozilla::MallocSizeOf| argument. That's because those arguments are
// primarily to aid DMD track heap blocks... but DMD deliberately doesn't track
// heap blocks it allocated for itself!
//
// SizeOfInternal should be called while you're holding the state lock and
// while intercepts are blocked; SizeOf acquires the lock and blocks
@@ -1844,335 +1599,254 @@ ClearReports()
}
MOZ_EXPORT bool
IsRunning()
{
return gIsDMDRunning;
}
-// AnalyzeReports() and AnalyzeHeap() have a lot in common. This abstract class
-// encapsulates the operations that are not shared.
-class Analyzer
-{
-public:
- virtual const char* AnalyzeFunctionName() const = 0;
-
- virtual RecordTable* ProcessBlock(const Block& aBlock) = 0;
-
- virtual void PrintRecords(const Writer& aWriter,
- CodeAddressService* aLocService) const = 0;
- virtual void PrintSummary(const Writer& aWriter, bool aShowTilde) const = 0;
- virtual void PrintStats(const Writer& aWriter) const = 0;
-
- struct RecordKindData
- {
- RecordTable mRecordTable;
- size_t mUsableSize;
- size_t mNumBlocks;
-
- explicit RecordKindData(size_t aN)
- : mUsableSize(0), mNumBlocks(0)
- {
- mRecordTable.init(aN);
- }
-
- void processBlock(const Block& aBlock)
- {
- mUsableSize += aBlock.UsableSize();
- mNumBlocks++;
- }
- };
-};
-
-class ReportsAnalyzer MOZ_FINAL : public Analyzer
+// This function converts an integer to base-32. |aBuf| must have space for at
+// least eight chars, which is the space needed to hold 'Dffffff' (including
+// the terminating null char), which is the base-32 representation of
+// 0xffffffff.
+//
+// We use base-32 values for indexing into the traceTable and the frameTable,
+// for the following reasons.
+//
+// - Base-32 gives more compact indices than base-16.
+//
+// - 32 is a power-of-two, which makes the necessary div/mod calculations fast.
+//
+// - We can (and do) choose non-numeric digits for base-32. When
+// inspecting/debugging the JSON output, non-numeric indices are easier to
+// search for than numeric indices.
+//
+char* Base32(uint32_t aN, char* aBuf, size_t aBufLen)
{
- RecordKindData mUnreported;
- RecordKindData mOnceReported;
- RecordKindData mTwiceReported;
-
- size_t mTotalUsableSize;
- size_t mTotalNumBlocks;
-
-public:
- ReportsAnalyzer()
- : mUnreported(1024), mOnceReported(1024), mTwiceReported(0),
- mTotalUsableSize(0), mTotalNumBlocks(0)
- {}
-
- ~ReportsAnalyzer()
- {
- ClearReports();
- }
-
- virtual const char* AnalyzeFunctionName() const { return "AnalyzeReports"; }
-
- virtual RecordTable* ProcessBlock(const Block& aBlock)
- {
- RecordKindData* data;
- uint32_t numReports = aBlock.NumReports();
- if (numReports == 0) {
- data = &mUnreported;
- } else if (numReports == 1) {
- data = &mOnceReported;
- } else {
- MOZ_ASSERT(numReports == 2);
- data = &mTwiceReported;
- }
- data->processBlock(aBlock);
-
- mTotalUsableSize += aBlock.UsableSize();
- mTotalNumBlocks++;
-
- return &data->mRecordTable;
- }
-
- virtual void PrintRecords(const Writer& aWriter,
- CodeAddressService* aLocService) const
- {
- PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
- "Twice-reported", "twice-reported",
- mTwiceReported.mRecordTable,
- mTwiceReported.mUsableSize, mTotalUsableSize,
- /* showCategoryPercentage = */ true,
- /* showReportedAt = */ true);
-
- PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
- "Unreported", "unreported",
- mUnreported.mRecordTable,
- mUnreported.mUsableSize, mTotalUsableSize,
- /* showCategoryPercentage = */ true,
- /* showReportedAt = */ true);
+ static const char digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef";
- PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
- "Once-reported", "once-reported",
- mOnceReported.mRecordTable,
- mOnceReported.mUsableSize, mTotalUsableSize,
- /* showCategoryPercentage = */ true,
- /* showReportedAt = */ true);
- }
-
- virtual void PrintSummary(const Writer& aWriter, bool aShowTilde) const
- {
- W(" Total: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
- Show(mTotalUsableSize, gBuf1, kBufLen, aShowTilde),
- 100.0,
- Show(mTotalNumBlocks, gBuf2, kBufLen, aShowTilde),
- 100.0);
-
- W(" Unreported: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
- Show(mUnreported.mUsableSize, gBuf1, kBufLen, aShowTilde),
- Percent(mUnreported.mUsableSize, mTotalUsableSize),
- Show(mUnreported.mNumBlocks, gBuf2, kBufLen, aShowTilde),
- Percent(mUnreported.mNumBlocks, mTotalNumBlocks));
+ char* b = aBuf + aBufLen - 1;
+ *b = '\0';
+ do {
+ b--;
+ if (b == aBuf) {
+ MOZ_CRASH("Base32 buffer too small");
+ }
+ *b = digits[aN % 32];
+ aN /= 32;
+ } while (aN);
- W(" Once-reported: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
- Show(mOnceReported.mUsableSize, gBuf1, kBufLen, aShowTilde),
- Percent(mOnceReported.mUsableSize, mTotalUsableSize),
- Show(mOnceReported.mNumBlocks, gBuf2, kBufLen, aShowTilde),
- Percent(mOnceReported.mNumBlocks, mTotalNumBlocks));
-
- W(" Twice-reported: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n",
- Show(mTwiceReported.mUsableSize, gBuf1, kBufLen, aShowTilde),
- Percent(mTwiceReported.mUsableSize, mTotalUsableSize),
- Show(mTwiceReported.mNumBlocks, gBuf2, kBufLen, aShowTilde),
- Percent(mTwiceReported.mNumBlocks, mTotalNumBlocks));
- }
-
- virtual void PrintStats(const Writer& aWriter) const
- {
- size_t unreportedSize =
- mUnreported.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
- W(" Unreported table: %10s bytes (%s entries, %s used)\n",
- Show(unreportedSize, gBuf1, kBufLen),
- Show(mUnreported.mRecordTable.capacity(), gBuf2, kBufLen),
- Show(mUnreported.mRecordTable.count(), gBuf3, kBufLen));
-
- size_t onceReportedSize =
- mOnceReported.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
- W(" Once-reported table: %10s bytes (%s entries, %s used)\n",
- Show(onceReportedSize, gBuf1, kBufLen),
- Show(mOnceReported.mRecordTable.capacity(), gBuf2, kBufLen),
- Show(mOnceReported.mRecordTable.count(), gBuf3, kBufLen));
+ return b;
+}
- size_t twiceReportedSize =
- mTwiceReported.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
- W(" Twice-reported table: %10s bytes (%s entries, %s used)\n",
- Show(twiceReportedSize, gBuf1, kBufLen),
- Show(mTwiceReported.mRecordTable.capacity(), gBuf2, kBufLen),
- Show(mTwiceReported.mRecordTable.count(), gBuf3, kBufLen));
- }
-};
-
-class HeapAnalyzer MOZ_FINAL : public Analyzer
+// Converts a pointer to a unique ID. Reuses the existing ID for the pointer if
+// it's been seen before.
+static const char* Id(PointerIdMap& aIdMap, uint32_t& aNextId,
+ const void* aPtr, char* aBuf, size_t aBufLen)
{
- RecordKindData mLive;
-
-public:
- HeapAnalyzer() : mLive(1024) {}
-
- virtual const char* AnalyzeFunctionName() const { return "AnalyzeHeap"; }
-
- virtual RecordTable* ProcessBlock(const Block& aBlock)
- {
- mLive.processBlock(aBlock);
-
- return &mLive.mRecordTable;
+ uint32_t id;
+ PointerIdMap::AddPtr p = aIdMap.lookupForAdd(aPtr);
+ if (!p) {
+ id = aNextId++;
+ (void)aIdMap.add(p, aPtr, id);
+ } else {
+ id = p->value();
}
-
- virtual void PrintRecords(const Writer& aWriter,
- CodeAddressService* aLocService) const
- {
- size_t totalUsableSize = mLive.mUsableSize;
- PrintSortedRecords(aWriter, aLocService, Record::CmpByUsable,
- "Live", "live", mLive.mRecordTable, totalUsableSize,
- mLive.mUsableSize,
- /* showReportedAt = */ false,
- /* showCategoryPercentage = */ false);
- }
-
- virtual void PrintSummary(const Writer& aWriter, bool aShowTilde) const
- {
- W(" Total: %s bytes in %s blocks\n",
- Show(mLive.mUsableSize, gBuf1, kBufLen, aShowTilde),
- Show(mLive.mNumBlocks, gBuf2, kBufLen, aShowTilde));
- }
-
- virtual void PrintStats(const Writer& aWriter) const
- {
- size_t liveSize = mLive.mRecordTable.sizeOfIncludingThis(MallocSizeOf);
- W(" Live table: %10s bytes (%s entries, %s used)\n",
- Show(liveSize, gBuf1, kBufLen),
- Show(mLive.mRecordTable.capacity(), gBuf2, kBufLen),
- Show(mLive.mRecordTable.count(), gBuf3, kBufLen));
- }
-};
+ return Base32(id, aBuf, aBufLen);
+}
static void
-AnalyzeImpl(Analyzer *aAnalyzer, const Writer& aWriter)
+AnalyzeReportsImpl(JSONWriter& aWriter)
{
if (!gIsDMDRunning) {
return;
}
AutoBlockIntercepts block(Thread::Fetch());
AutoLockState lock;
- static int analysisCount = 1;
- StatusMsg("%s %d {\n", aAnalyzer->AnalyzeFunctionName(), analysisCount++);
+ // Allocate this on the heap instead of the stack because it's fairly large.
+ auto locService = InfallibleAllocPolicy::new_<CodeAddressService>();
- StatusMsg(" gathering heap block records...\n");
+ StackTraceSet usedStackTraces;
+ usedStackTraces.init(512);
+
+ PointerSet usedPcs;
+ usedPcs.init(512);
- bool anyBlocksSampled = false;
+ PointerIdMap idMap;
+ idMap.init(512);
- for (BlockTable::Range r = gBlockTable->all(); !r.empty(); r.popFront()) {
- const Block& b = r.front();
- RecordTable* table = aAnalyzer->ProcessBlock(b);
+ static int analysisCount = 1;
+ StatusMsg("Dump %d {\n", analysisCount++);
+
+ aWriter.Start();
+ {
+ #define ID(p) Id(idMap, id, p, idBuf, idBufLen)
- RecordKey key(b);
- RecordTable::AddPtr p = table->lookupForAdd(key);
- if (!p) {
- Record tr(key);
- (void)table->add(p, tr);
+ aWriter.IntProperty("version", kOutputVersionNumber);
+
+ aWriter.StartObjectProperty("invocation");
+ {
+ aWriter.StringProperty("dmdEnvVar", gOptions->DMDEnvVar());
+ aWriter.IntProperty("sampleBelowSize", gOptions->SampleBelowSize());
}
- p->Add(b);
+ aWriter.EndObject();
+
+ StatusMsg(" Constructing the heap block list...\n");
- anyBlocksSampled = anyBlocksSampled || b.IsSampled();
- }
+ static const size_t idBufLen = 16;
+ char idBuf[idBufLen];
+ uint32_t id = 0;
+
+ aWriter.StartArrayProperty("blockList");
+ {
+ for (BlockTable::Range r = gBlockTable->all(); !r.empty(); r.popFront()) {
+ const Block& b = r.front();
+ b.AddStackTracesToTable(usedStackTraces);
- WriteSeparator();
- W("Invocation {\n");
- W(" $DMD = '%s'\n", gOptions->DMDEnvVar());
- W(" Function = %s\n", aAnalyzer->AnalyzeFunctionName());
- W(" Sample-below size = %lld\n", (long long)(gOptions->SampleBelowSize()));
- W("}\n\n");
+ aWriter.StartObjectElement(aWriter.SingleLineStyle);
+ {
+ if (!b.IsSampled()) {
+ aWriter.IntProperty("req", b.ReqSize());
+ if (b.SlopSize() > 0) {
+ aWriter.IntProperty("slop", b.SlopSize());
+ }
+ }
+ aWriter.StringProperty("alloc", ID(b.AllocStackTrace()));
+ if (b.NumReports() > 0) {
+ aWriter.StartArrayProperty("reps");
+ {
+ if (b.ReportStackTrace1()) {
+ aWriter.StringElement(ID(b.ReportStackTrace1()));
+ }
+ if (b.ReportStackTrace2()) {
+ aWriter.StringElement(ID(b.ReportStackTrace2()));
+ }
+ }
+ aWriter.EndArray();
+ }
+ }
+ aWriter.EndObject();
+ }
+ }
+ aWriter.EndArray();
- // Allocate this on the heap instead of the stack because it's fairly large.
- CodeAddressService* locService = InfallibleAllocPolicy::new_<CodeAddressService>();
+ StatusMsg(" Constructing the stack trace table...\n");
- aAnalyzer->PrintRecords(aWriter, locService);
+ aWriter.StartObjectProperty("traceTable");
+ {
+ for (StackTraceSet::Enum e(usedStackTraces); !e.empty(); e.popFront()) {
+ const StackTrace* const st = e.front();
+ aWriter.StartArrayProperty(ID(st), aWriter.SingleLineStyle);
+ {
+ for (uint32_t i = 0; i < st->Length(); i++) {
+ const void* pc = st->Pc(i);
+ aWriter.StringElement(ID(pc));
+ usedPcs.put(pc);
+ }
+ }
+ aWriter.EndArray();
+ }
+ }
+ aWriter.EndObject();
- WriteSeparator();
- W("Summary {\n");
+ StatusMsg(" Constructing the stack frame table...\n");
- bool showTilde = anyBlocksSampled;
- aAnalyzer->PrintSummary(aWriter, showTilde);
+ aWriter.StartObjectProperty("frameTable");
+ {
+ static const size_t locBufLen = 1024;
+ char locBuf[locBufLen];
+
+ for (PointerSet::Enum e(usedPcs); !e.empty(); e.popFront()) {
+ const void* const pc = e.front();
- W("}\n\n");
+ // Use 0 for the frame number. See the JSON format description comment
+ // in DMD.h to understand why.
+ locService->GetLocation(0, pc, locBuf, locBufLen);
+ aWriter.StringProperty(ID(pc), locBuf);
+ }
+ }
+ aWriter.EndObject();
- // Stats are non-deterministic, so don't show them in test mode.
- if (!gOptions->IsTestMode()) {
+ #undef ID
+ }
+ aWriter.End();
+
+ if (gOptions->ShowDumpStats()) {
Sizes sizes;
SizeOfInternal(&sizes);
- WriteSeparator();
- W("Execution measurements {\n");
+ static const size_t kBufLen = 64;
+ char buf1[kBufLen];
+ char buf2[kBufLen];
+ char buf3[kBufLen];
- W(" Data structures that persist after Dump() ends {\n");
+ StatusMsg(" Execution measurements {\n");
- W(" Used stack traces: %10s bytes\n",
- Show(sizes.mStackTracesUsed, gBuf1, kBufLen));
+ StatusMsg(" Data structures that persist after Dump() ends {\n");
- W(" Unused stack traces: %10s bytes\n",
- Show(sizes.mStackTracesUnused, gBuf1, kBufLen));
+ StatusMsg(" Used stack traces: %10s bytes\n",
+ Show(sizes.mStackTracesUsed, buf1, kBufLen));
- W(" Stack trace table: %10s bytes (%s entries, %s used)\n",
- Show(sizes.mStackTraceTable, gBuf1, kBufLen),
- Show(gStackTraceTable->capacity(), gBuf2, kBufLen),
- Show(gStackTraceTable->count(), gBuf3, kBufLen));
+ StatusMsg(" Unused stack traces: %10s bytes\n",
+ Show(sizes.mStackTracesUnused, buf1, kBufLen));
+
+ StatusMsg(" Stack trace table: %10s bytes (%s entries, %s used)\n",
+ Show(sizes.mStackTraceTable, buf1, kBufLen),
+ Show(gStackTraceTable->capacity(), buf2, kBufLen),
+ Show(gStackTraceTable->count(), buf3, kBufLen));
- W(" Block table: %10s bytes (%s entries, %s used)\n",
- Show(sizes.mBlockTable, gBuf1, kBufLen),
- Show(gBlockTable->capacity(), gBuf2, kBufLen),
- Show(gBlockTable->count(), gBuf3, kBufLen));
+ StatusMsg(" Block table: %10s bytes (%s entries, %s used)\n",
+ Show(sizes.mBlockTable, buf1, kBufLen),
+ Show(gBlockTable->capacity(), buf2, kBufLen),
+ Show(gBlockTable->count(), buf3, kBufLen));
- W(" }\n");
- W(" Data structures that are destroyed after Dump() ends {\n");
+ StatusMsg(" }\n");
+ StatusMsg(" Data structures that are destroyed after Dump() ends {\n");
- aAnalyzer->PrintStats(aWriter);
+ StatusMsg(" Location service: %10s bytes\n",
+ Show(locService->SizeOfIncludingThis(MallocSizeOf), buf1, kBufLen));
+ StatusMsg(" Used stack traces set: %10s bytes\n",
+ Show(usedStackTraces.sizeOfExcludingThis(MallocSizeOf), buf1, kBufLen));
+ StatusMsg(" Used PCs set: %10s bytes\n",
+ Show(usedPcs.sizeOfExcludingThis(MallocSizeOf), buf1, kBufLen));
+ StatusMsg(" Pointer ID map: %10s bytes\n",
+ Show(idMap.sizeOfExcludingThis(MallocSizeOf), buf1, kBufLen));
- W(" Location service: %10s bytes\n",
- Show(locService->SizeOfIncludingThis(MallocSizeOf), gBuf1, kBufLen));
-
- W(" }\n");
- W(" Counts {\n");
+ StatusMsg(" }\n");
+ StatusMsg(" Counts {\n");
size_t hits = locService->NumCacheHits();
size_t misses = locService->NumCacheMisses();
size_t requests = hits + misses;
- W(" Location service: %10s requests\n",
- Show(requests, gBuf1, kBufLen));
+ StatusMsg(" Location service: %10s requests\n",
+ Show(requests, buf1, kBufLen));
size_t count = locService->CacheCount();
size_t capacity = locService->CacheCapacity();
- W(" Location service cache: "
+ StatusMsg(" Location service cache: "
"%4.1f%% hit rate, %.1f%% occupancy at end\n",
Percent(hits, requests), Percent(count, capacity));
- W(" }\n");
- W("}\n\n");
+ StatusMsg(" }\n");
+ StatusMsg(" }\n");
}
InfallibleAllocPolicy::delete_(locService);
StatusMsg("}\n");
}
MOZ_EXPORT void
-AnalyzeReports(const Writer& aWriter)
+AnalyzeReports(JSONWriter& aWriter)
{
- ReportsAnalyzer aAnalyzer;
- AnalyzeImpl(&aAnalyzer, aWriter);
-}
-
-MOZ_EXPORT void
-AnalyzeHeap(const Writer& aWriter)
-{
- HeapAnalyzer analyzer;
- AnalyzeImpl(&analyzer, aWriter);
+ AnalyzeReportsImpl(aWriter);
+ ClearReports();
}
//---------------------------------------------------------------------------
// Testing
//---------------------------------------------------------------------------
// This function checks that heap blocks that have the same stack trace but
// different (or no) reporters get aggregated separately.
@@ -2194,30 +1868,33 @@ void foo()
static void
UseItOrLoseIt(void* a)
{
char buf[64];
sprintf(buf, "%p\n", a);
fwrite(buf, 1, strlen(buf) + 1, stderr);
}
-// The output from this should be compared against test-expected.dmd. It's
-// been tested on Linux64, and probably will give different results on other
+// The output from this should be tested with check_test_output.py. It's been
+// tested on Linux64, and probably will give different results on other
// platforms.
static void
-RunTestMode(FILE* fp)
+RunTestMode(UniquePtr<FpWriteFunc> aF1, UniquePtr<FpWriteFunc> aF2,
+ UniquePtr<FpWriteFunc> aF3, UniquePtr<FpWriteFunc> aF4)
{
- Writer writer(FpWrite, fp);
-
// The first part of this test requires sampling to be disabled.
gOptions->SetSampleBelowSize(1);
+ //---------
+
// AnalyzeReports 1. Zero for everything.
- AnalyzeReports(writer);
- AnalyzeHeap(writer);
+ JSONWriter writer1(Move(aF1));
+ AnalyzeReports(writer1);
+
+ //---------
// AnalyzeReports 2: 1 freed, 9 out of 10 unreported.
// AnalyzeReports 3: still present and unreported.
int i;
char* a;
for (i = 0; i < 10; i++) {
a = (char*) malloc(100);
UseItOrLoseIt(a);
@@ -2321,34 +1998,34 @@ RunTestMode(FILE* fp)
//posix_memalign(&y, 128, 129); // rounds up to 256
//UseItOrLoseIt(y);
// XXX: valloc doesn't work on Windows.
//void* z = valloc(1); // rounds up to 4096
//UseItOrLoseIt(z);
//aligned_alloc(64, 256); // XXX: C11 only
// AnalyzeReports 2.
- AnalyzeReports(writer);
- AnalyzeHeap(writer);
+ JSONWriter writer2(Move(aF2));
+ AnalyzeReports(writer2);
//---------
Report(a2);
Report(a2);
free(c);
free(e);
Report(e2);
free(e3);
//free(x);
//free(y);
//free(z);
// AnalyzeReports 3.
- AnalyzeReports(writer);
- AnalyzeHeap(writer);
+ JSONWriter writer3(Move(aF3));
+ AnalyzeReports(writer3);
//---------
// Clear all knowledge of existing blocks to give us a clean slate.
gBlockTable->clear();
gOptions->SetSampleBelowSize(128);
@@ -2402,18 +2079,18 @@ RunTestMode(FILE* fp)
UseItOrLoseIt(s);
}
MOZ_ASSERT(gSmallBlockActualSizeCounter == 64);
// At the end we're 64 bytes into the current sample so we report ~1,424
// bytes of allocation overall, which is 64 less than the real value 1,488.
// AnalyzeReports 4.
- AnalyzeReports(writer);
- AnalyzeHeap(writer);
+ JSONWriter writer4(Move(aF4));
+ AnalyzeReports(writer4);
}
//---------------------------------------------------------------------------
// Stress testing microbenchmark
//---------------------------------------------------------------------------
// This stops otherwise-unused variables from being optimized away.
static void
@@ -2469,19 +2146,19 @@ stress1()
// This stress test does lots of allocations and frees, which is where most of
// DMD's overhead occurs. It allocates 1,000,000 64-byte blocks, spread evenly
// across 1,000 distinct stack traces. It frees every second one immediately
// after allocating it.
//
// It's highly artificial, but it's deterministic and easy to run. It can be
// timed under different conditions to glean performance data.
static void
-RunStressMode(FILE* fp)
+RunStressMode(UniquePtr<FpWriteFunc> aF)
{
- Writer writer(FpWrite, fp);
+ JSONWriter writer(Move(aF));
// Disable sampling for maximum stress.
gOptions->SetSampleBelowSize(1);
stress1(); stress1(); stress1(); stress1(); stress1();
stress1(); stress1(); stress1(); stress1(); stress1();
AnalyzeReports(writer);
--- a/memory/replace/dmd/DMD.h
+++ b/memory/replace/dmd/DMD.h
@@ -2,76 +2,131 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DMD_h___
#define DMD_h___
-#include <stdarg.h>
#include <string.h>
#include "mozilla/Types.h"
namespace mozilla {
+
+class JSONWriter;
+
namespace dmd {
// Mark a heap block as reported by a memory reporter.
MOZ_EXPORT void
Report(const void* aPtr);
// Mark a heap block as reported immediately on allocation.
MOZ_EXPORT void
ReportOnAlloc(const void* aPtr);
-class Writer
-{
-public:
- typedef void (*WriterFun)(void* aWriteState, const char* aFmt, va_list aAp);
-
- Writer(WriterFun aWriterFun, void* aWriteState)
- : mWriterFun(aWriterFun), mWriteState(aWriteState)
- {}
-
- void Write(const char* aFmt, ...) const;
-
-private:
- WriterFun mWriterFun;
- void* mWriteState;
-};
-
// Clears existing reportedness data from any prior runs of the memory
// reporters. The following sequence should be used.
// - ClearReports()
// - run the memory reporters
// - AnalyzeReports()
// This sequence avoids spurious twice-reported warnings.
MOZ_EXPORT void
ClearReports();
-// Determines which heap blocks have been reported, and dumps a human-readable
-// summary (via |aWrite|). If |aWrite| is nullptr it will dump to stderr.
-// Beware: this output may have very long lines.
-MOZ_EXPORT void
-AnalyzeReports(const Writer& aWriter);
-
-// Measures all heap blocks, and dumps a human-readable summary (via |aWrite|).
-// If |aWrite| is nullptr it will dump to stderr. Beware: this output may
-// have very long lines.
+// Determines which heap blocks have been reported, and dumps JSON output
+// (via |aWriter|) describing the heap.
+//
+// The following sample output contains comments that explain the format and
+// design choices. The output files can be quite large, so a number of
+// decisions were made to minimize size, such as using short property names and
+// omitting properties whenever possible.
+//
+// {
+// // The version number of the format, which will be incremented each time
+// // backwards-incompatible changes are made. A mandatory integer.
+// "version": 1,
+//
+// // Information about how DMD was invoked. A mandatory object.
+// "invocation": {
+// // The contents of the $DMD environment variable. A mandatory string.
+// "dmdEnvVar": "1",
+//
+// // The value of the --sample-below-size option. A mandatory integer.
+// "sampleBelowSize": 4093
+// },
+//
+// // Details of all analyzed heap blocks. A mandatory array.
+// "blockList": [
+// // An example of a non-sampled heap block.
+// {
+// // Requested size, in bytes. In non-sampled blocks this is a
+// // mandatory integer. In sampled blocks this is not present, and the
+// // requested size is equal to the "sampleBelowSize" value. Therefore,
+// // the block is sampled if and only if this property is absent.
+// "req": 3584,
+//
+// // Requested slop size, in bytes. This is mandatory if it is non-zero,
+// // but omitted otherwise. Because sampled blocks never have slop, this
+// // property is never present for non-sampled blocks.
+// "slop": 512,
+//
+// // The stack trace at which the block was allocated. A mandatory
+// // string which indexes into the "traceTable" object.
+// "alloc": "A"
+// },
+//
+// // An example of a sampled heap block.
+// {
+// "alloc": "B",
+//
+// // One or more stack traces at which this heap block was reported by a
+// // memory reporter. An optional array. The elements are strings that
+// // index into the "traceTable" object.
+// "reps": ["C"]
+// }
+// ],
+//
+// // The stack traces referenced by elements of the "blockList" array. This
+// // could be an array, but making it an object makes it easier to see
+// // which stacks correspond to which references in the "blockList" array.
+// "traceTable": {
+// // Each property corresponds to a stack trace mentioned in the "blocks"
+// // object. Each element is an index into the "frameTable" object.
+// "A": ["D", "E"],
+// "B": ["D", "F"],
+// "C": ["G", "H"]
+// },
+//
+// // The stack frames referenced by the "traceTable" object. The
+// // descriptions can be quite long, so they are stored separately from the
+// // "traceTable" object so that each one only has to be written once.
+// // This could also be an array, but again, making it an object makes it
+// // easier to see which frames correspond to which references in the
+// // "traceTable" object.
+// "frameTable": {
+// // Each property key is a frame key mentioned in the "traceTable" object.
+// // Each property value is a string containing a frame description. Each
+// // frame description must be in a format recognized by the stack-fixing
+// // scripts (e.g. fix_linux_stack.py), which require a frame number at
+// // the start. Because each stack frame description in this table can
+// // be shared between multiple stack traces, we use a dummy value of
+// // #00. The proper frame number can be reconstructed later by scripts
+// // that output stack traces in a conventional non-shared format.
+// "D": "#00: foo (Foo.cpp:123)",
+// "E": "#00: bar (Bar.cpp:234)",
+// "F": "#00: baz (Baz.cpp:345)",
+// "G": "#00: quux (Quux.cpp:456)",
+// "H": "#00: quuux (Quux.cpp:567)"
+// }
+// }
MOZ_EXPORT void
-AnalyzeHeap(const Writer& aWriter);
-
-// A useful |WriterFun|. For example, if |fp| is a FILE* you want
-// |AnalyzeReports|'s output to be written to, call:
-//
-// dmd::Writer writer(FpWrite, fp);
-// dmd::AnalyzeReports(writer);
-MOZ_EXPORT void
-FpWrite(void* aFp, const char* aFmt, va_list aAp);
+AnalyzeReports(mozilla::JSONWriter& aWriter);
struct Sizes
{
size_t mStackTracesUsed;
size_t mStackTracesUnused;
size_t mStackTraceTable;
size_t mBlockTable;
--- a/memory/replace/dmd/check_test_output.py
+++ b/memory/replace/dmd/check_test_output.py
@@ -1,14 +1,18 @@
#! /usr/bin/python
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""This script takes the file produced by DMD's test mode and checks its
correctness.
-It produces the following output files: $TMP/test-{fixed,filtered,diff}.dmd.
+It produces the following output files: $TMP/full-{fixed,filtered,diff}.dmd.
It runs the appropriate fix* script to get nice stack traces. It also
filters out platform-specific details from the test output file.
Note: you must run this from the same directory that you invoked DMD's test
mode, otherwise the fix* script will not work properly, because some of the
paths in the test output are relative.
@@ -18,63 +22,55 @@ from __future__ import print_function
import os
import platform
import re
import subprocess
import sys
import tempfile
-
-def main():
-
- # Arguments
-
- if (len(sys.argv) != 3):
- print("usage:", sys.argv[0], "<topsrcdir> <test.dmd>")
- sys.exit(1)
-
- srcdir = sys.argv[1]
-
+def test(src_dir, kind, options, i):
# Filenames
-
- tempdir = tempfile.gettempdir()
- in_name = sys.argv[2]
- fixed_name = tempdir + os.sep + "test-fixed.dmd"
- filtered_name = tempdir + os.sep + "test-filtered.dmd"
- diff_name = tempdir + os.sep + "test-diff.dmd"
- expected_name = srcdir + os.sep + \
- "memory/replace/dmd/test-expected.dmd"
+ tmp_dir = tempfile.gettempdir()
+ in_name = os.path.join(src_dir, "full{:d}.json".format(i))
+ fixed_name = os.path.join(tmp_dir, "full-{:}-fixed{:d}.json".format(kind, i))
+ converted_name = os.path.join(tmp_dir, "full-{:}-converted{:d}.txt".format(kind, i))
+ filtered_name = os.path.join(tmp_dir, "full-{:}-filtered{:d}.txt".format(kind, i))
+ diff_name = os.path.join(tmp_dir, "full-{:}-diff{:d}.txt".format(kind, i))
+ expected_name = os.path.join(src_dir, "memory", "replace", "dmd", "test", "full-{:}-expected{:d}.txt".format(kind, i))
# Fix stack traces
- print("fixing output to", fixed_name)
-
- sysname = platform.system()
- if sysname == "Linux":
- fix = srcdir + os.sep + "tools/rb/fix_linux_stack.py"
- elif sysname == "Darwin":
- fix = srcdir + os.sep + "tools/rb/fix_macosx_stack.py"
+ sys_name = platform.system()
+ fix = os.path.join(src_dir, "tools", "rb")
+ if sys_name == "Linux":
+ fix = os.path.join(fix, "fix_linux_stack.py")
+ elif sys_name == "Darwin":
+ fix = os.path.join(fix, "fix_macosx_stack.py")
else:
- print("unhandled platform: " + sysname, file=sys.stderr)
+ print("unhandled platform: " + sys_name, file=sys.stderr)
sys.exit(1)
subprocess.call(fix, stdin=open(in_name, "r"),
stdout=open(fixed_name, "w"))
+ # Convert from JSON
+
+ convert = [os.path.join(src_dir, "memory", "replace", "dmd", "dmd.py")] + \
+ options + ['--no-fix-stacks', fixed_name]
+ subprocess.call(convert, stdout=open(converted_name, "w"))
+
# Filter output
# In heap block records we filter out most stack frames. The only thing
# we leave behind is a "DMD.cpp" entry if we see one or more frames that
# have DMD.cpp in them. There is simply too much variation to do anything
# better than that.
- print("filtering output to", filtered_name)
-
- with open(fixed_name, "r") as fin, \
+ with open(converted_name, "r") as fin, \
open(filtered_name, "w") as fout:
test_frame_re = re.compile(r".*(DMD.cpp)")
for line in fin:
if re.match(r" (Allocated at {|Reported( again)? at {)", line):
# It's a heap block record.
print(line, end='', file=fout)
@@ -95,22 +91,37 @@ def main():
break
else:
# A line that needs no special handling. Copy it through.
print(line, end='', file=fout)
# Compare with expected output
- print("diffing output to", diff_name)
-
ret = subprocess.call(["diff", "-u", expected_name, filtered_name],
stdout=open(diff_name, "w"))
if ret == 0:
- print("test PASSED")
+ print("TEST-PASS | {:} {:d} | ok".format(kind, i))
else:
- print("test FAILED (did you remember to run this script and Firefox "
- "in the same directory?)")
+ print("TEST-UNEXPECTED-FAIL | {:} {:d} | mismatch".format(kind, i))
+ print("Output files:")
+ print("- " + fixed_name);
+ print("- " + converted_name);
+ print("- " + filtered_name);
+ print("- " + diff_name);
+
+
+def main():
+ if (len(sys.argv) != 2):
+ print("usage:", sys.argv[0], "<topsrcdir>")
+ sys.exit(1)
+
+ src_dir = sys.argv[1]
+
+ ntests = 4
+ for i in range(1, ntests+1):
+ test(src_dir, "reports", [], i)
+ test(src_dir, "heap", ["--ignore-reports"], i)
if __name__ == "__main__":
main()
new file mode 100755
--- /dev/null
+++ b/memory/replace/dmd/dmd.py
@@ -0,0 +1,451 @@
+#! /usr/bin/python
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+'''This script analyzes a JSON file emitted by DMD.'''
+
+from __future__ import print_function, division
+
+import argparse
+import collections
+import json
+import os
+import platform
+import re
+import shutil
+import sys
+import tempfile
+
+# The DMD output version this script handles.
+outputVersion = 1
+
+# If --ignore-alloc-fns is specified, stack frames containing functions that
+# match these strings will be removed.
+allocatorFns = [
+ 'replace_malloc',
+ 'replace_calloc',
+ 'replace_realloc',
+ 'replace_memalign',
+ 'replace_posix_memalign',
+ 'moz_xmalloc',
+ 'moz_xcalloc',
+ 'moz_xrealloc',
+ 'operator new(',
+ 'operator new[](',
+ 'g_malloc',
+ 'g_slice_alloc',
+ 'callocCanGC',
+ 'reallocCanGC',
+ 'vpx_malloc',
+ 'vpx_calloc',
+ 'vpx_realloc',
+ 'vpx_memalign',
+ 'js_malloc',
+ 'js_calloc',
+ 'js_realloc',
+ 'pod_malloc',
+ 'pod_calloc',
+ 'pod_realloc',
+]
+
+class Record(object):
+ def __init__(self):
+ self.numBlocks = 0
+ self.reqSize = 0
+ self.slopSize = 0
+ self.usableSize = 0
+ self.isSampled = False
+ self.usableSizes = collections.defaultdict(int)
+
+ @staticmethod
+ def cmpByIsSampled(r1, r2):
+ # Treat sampled as smaller than non-sampled.
+ return cmp(r2.isSampled, r1.isSampled)
+
+ @staticmethod
+ def cmpByUsableSize(r1, r2):
+ # Sort by usable size, then req size, then by isSampled.
+ return cmp(r1.usableSize, r2.usableSize) or Record.cmpByReqSize(r1, r2)
+
+ @staticmethod
+ def cmpByReqSize(r1, r2):
+ # Sort by req size, then by isSampled.
+ return cmp(r1.reqSize, r2.reqSize) or Record.cmpByIsSampled(r1, r2)
+
+ @staticmethod
+ def cmpBySlopSize(r1, r2):
+ # Sort by slop size, then by isSampled.
+ return cmp(r1.slopSize, r2.slopSize) or Record.cmpByIsSampled(r1, r2)
+
+
+sortByChoices = {
+ 'usable': Record.cmpByUsableSize, # the default
+ 'req': Record.cmpByReqSize,
+ 'slop': Record.cmpBySlopSize,
+}
+
+
+def parseCommandLine():
+ # 24 is the maximum number of frames that DMD will produce.
+ def range_1_24(string):
+ value = int(string)
+ if value < 1 or value > 24:
+ msg = '{:s} is not in the range 1..24'.format(string)
+ raise argparse.ArgumentTypeError(msg)
+ return value
+
+ description = '''
+Analyze heap data produced by DMD.
+If no files are specified, read from stdin.
+Write to stdout unless -o/--output is specified.
+Stack traces are fixed to show function names, filenames and line numbers
+unless --no-fix-stacks is specified; stack fixing modifies the original file
+and may take some time.
+'''
+ p = argparse.ArgumentParser(description=description)
+
+ p.add_argument('-o', '--output', type=argparse.FileType('w'),
+ help='output file; stdout if unspecified')
+
+ p.add_argument('-f', '--max-frames', type=range_1_24,
+ help='maximum number of frames to consider in each trace')
+
+ p.add_argument('-r', '--ignore-reports', action='store_true',
+ help='ignore memory reports data; useful if you just ' +
+ 'want basic heap profiling')
+
+ p.add_argument('-s', '--sort-by', choices=sortByChoices.keys(),
+ default=sortByChoices.keys()[0],
+ help='sort the records by a particular metric')
+
+ p.add_argument('-a', '--ignore-alloc-fns', action='store_true',
+ help='ignore allocation functions at the start of traces')
+
+ p.add_argument('-b', '--show-all-block-sizes', action='store_true',
+ help='show individual block sizes for each record')
+
+ p.add_argument('--no-fix-stacks', action='store_true',
+ help='do not fix stacks')
+
+ p.add_argument('input_file', type=argparse.FileType('r'))
+
+ return p.parse_args(sys.argv[1:])
+
+
+# Fix stacks if necessary: first write the output to a tempfile, then replace
+# the original file with it.
+def fixStackTraces(args):
+ # This append() call is needed to make the import statements work when this
+ # script is installed as a symlink.
+ sys.path.append(os.path.dirname(__file__))
+
+ # XXX: should incorporate fix_stack_using_bpsyms.py here as well, like in
+ # testing/mochitests/runtests.py
+ sysname = platform.system()
+ if sysname == 'Linux':
+ import fix_linux_stack as fixModule
+ fix = lambda line: fixModule.fixSymbols(line)
+ elif sysname == 'Darwin':
+ import fix_macosx_stack as fixModule
+ fix = lambda line: fixModule.fixSymbols(line)
+ else:
+ fix = None # there is no fix script for Windows
+
+ if fix:
+ # Fix stacks, writing output to a temporary file, and then
+ # overwrite the original file.
+ with tempfile.NamedTemporaryFile(delete=False) as tmp:
+ for line in args.input_file:
+ tmp.write(fix(line))
+ shutil.move(tmp.name, args.input_file.name)
+
+ args.input_file = open(args.input_file.name)
+
+
+def main():
+ args = parseCommandLine()
+
+ # Fix stack traces unless otherwise instructed.
+ if not args.no_fix_stacks:
+ fixStackTraces(args)
+
+ j = json.load(args.input_file)
+
+ if j['version'] != outputVersion:
+ raise Exception("'version' property isn't '{:d}'".format(outputVersion))
+
+ # Extract the main parts of the JSON object.
+ invocation = j['invocation']
+ dmdEnvVar = invocation['dmdEnvVar']
+ sampleBelowSize = invocation['sampleBelowSize']
+ blockList = j['blockList']
+ traceTable = j['traceTable']
+ frameTable = j['frameTable']
+
+ heapIsSampled = sampleBelowSize > 1 # is sampling present?
+
+ # Remove allocation functions at the start of traces.
+ if args.ignore_alloc_fns:
+ # Build a regexp that matches every function in allocatorFns.
+ escapedAllocatorFns = map(re.escape, allocatorFns)
+ fn_re = re.compile('|'.join(escapedAllocatorFns))
+
+ # Remove allocator fns from each stack trace.
+ for traceKey, frameKeys in traceTable.items():
+ numSkippedFrames = 0
+ for frameKey in frameKeys:
+ frameDesc = frameTable[frameKey]
+ if re.search(fn_re, frameDesc):
+ numSkippedFrames += 1
+ else:
+ break
+ if numSkippedFrames > 0:
+ traceTable[traceKey] = frameKeys[numSkippedFrames:]
+
+ # Trim the number of frames.
+ for traceKey, frameKeys in traceTable.items():
+ if len(frameKeys) > args.max_frames:
+ traceTable[traceKey] = frameKeys[:args.max_frames]
+
+ # Aggregate blocks into records. All sufficiently similar blocks go into a
+ # single record.
+
+ if args.ignore_reports:
+ liveRecords = collections.defaultdict(Record)
+ else:
+ unreportedRecords = collections.defaultdict(Record)
+ onceReportedRecords = collections.defaultdict(Record)
+ twiceReportedRecords = collections.defaultdict(Record)
+
+ heapUsableSize = 0
+ heapBlocks = 0
+
+ for block in blockList:
+ # For each block we compute a |recordKey|, and all blocks with the same
+ # |recordKey| are aggregated into a single record. The |recordKey| is
+ # derived from the block's 'alloc' and 'reps' (if present) stack
+ # traces.
+ #
+ # Each stack trace has a key in the JSON file. But we don't use that
+ # key to construct |recordKey|; instead we use the frame keys.
+ # This is because the stack trimming done for --max-frames can cause
+ # stack traces with distinct trace keys to end up with the same frame
+ # keys, and these should be considered equivalent. E.g. if we have
+ # distinct traces T1:[A,B,C] and T2:[A,B,D] and we trim the final frame
+ # of each they should be considered equivalent.
+ allocatedAt = block['alloc']
+ if args.ignore_reports:
+ recordKey = str(traceTable[allocatedAt])
+ records = liveRecords
+ else:
+ recordKey = str(traceTable[allocatedAt])
+ if 'reps' in block:
+ reportedAts = block['reps']
+ for reportedAt in reportedAts:
+ recordKey += str(traceTable[reportedAt])
+ if len(reportedAts) == 1:
+ records = onceReportedRecords
+ else:
+ records = twiceReportedRecords
+ else:
+ records = unreportedRecords
+
+ record = records[recordKey]
+
+ if 'req' in block:
+ # not sampled
+ reqSize = block['req']
+ slopSize = block.get('slop', 0)
+ isSampled = False
+ else:
+ # sampled
+ reqSize = sampleBelowSize
+ if 'slop' in block:
+ raise Exception("'slop' property in sampled block'")
+ slopSize = 0
+ isSampled = True
+
+ usableSize = reqSize + slopSize
+ heapUsableSize += usableSize
+ heapBlocks += 1
+
+ record.numBlocks += 1
+ record.reqSize += reqSize
+ record.slopSize += slopSize
+ record.usableSize += usableSize
+ record.isSampled = record.isSampled or isSampled
+ record.allocatedAt = block['alloc']
+ if args.ignore_reports:
+ pass
+ else:
+ if 'reps' in block:
+ record.reportedAts = block['reps']
+ record.usableSizes[(usableSize, isSampled)] += 1
+
+ # Print records.
+
+ separator = '#' + '-' * 65 + '\n'
+
+ def number(n, isSampled):
+ '''Format a number, with comma as a separator and a '~' prefix if it's
+ sampled.'''
+ return '{:}{:,d}'.format('~' if isSampled else '', n)
+
+ def perc(m, n):
+ return 0 if n == 0 else (100 * m / n)
+
+ def plural(n):
+ return '' if n == 1 else 's'
+
+ # Prints to stdout, or to file if -o/--output was specified.
+ def out(*arguments, **kwargs):
+ print(*arguments, file=args.output, **kwargs)
+
+ def printStack(traceTable, frameTable, traceKey):
+ # The frame number is always '#00' (see DMD.h for why), so we have to
+ # replace that with the correct frame number.
+ for n, frameKey in enumerate(traceTable[traceKey], start=1):
+ out(' #{:02d}{:}'.format(n, frameTable[frameKey][3:]))
+
+ def printRecords(recordKind, records, heapUsableSize):
+ RecordKind = recordKind.capitalize()
+ out(separator)
+ numRecords = len(records)
+ cmpRecords = sortByChoices[args.sort_by]
+ sortedRecords = sorted(records.values(), cmp=cmpRecords, reverse=True)
+ kindBlocks = 0
+ kindUsableSize = 0
+ maxRecord = 1000
+
+ # First iteration: get totals, etc.
+ for record in sortedRecords:
+ kindBlocks += record.numBlocks
+ kindUsableSize += record.usableSize
+
+ # Second iteration: print.
+ if numRecords == 0:
+ out('# no {:} heap blocks\n'.format(recordKind))
+
+ kindCumulativeUsableSize = 0
+ for i, record in enumerate(sortedRecords, start=1):
+ # Stop printing at the |maxRecord|th record.
+ if i == maxRecord:
+ out('# {:}: stopping after {:,d} heap block records\n'.
+ format(RecordKind, i))
+ break
+
+ kindCumulativeUsableSize += record.usableSize
+
+ isSampled = record.isSampled
+
+ out(RecordKind + ' {')
+ out(' {:} block{:} in heap block record {:,d} of {:,d}'.
+ format(number(record.numBlocks, isSampled),
+ plural(record.numBlocks), i, numRecords))
+ out(' {:} bytes ({:} requested / {:} slop)'.
+ format(number(record.usableSize, isSampled),
+ number(record.reqSize, isSampled),
+ number(record.slopSize, isSampled)))
+ out(' {:4.2f}% of the heap ({:4.2f}% cumulative)'.
+ format(perc(record.usableSize, heapUsableSize),
+ perc(kindCumulativeUsableSize, heapUsableSize)))
+ if args.ignore_reports:
+ pass
+ else:
+ out(' {:4.2f}% of {:} ({:4.2f}% cumulative)'.
+ format(perc(record.usableSize, kindUsableSize),
+ recordKind,
+ perc(kindCumulativeUsableSize, kindUsableSize)))
+
+ if args.show_all_block_sizes:
+ usableSizes = sorted(record.usableSizes.items(), reverse=True)
+
+ out(' Individual block sizes: ', end='')
+ isFirst = True
+ for (usableSize, isSampled), count in usableSizes:
+ if not isFirst:
+ out('; ', end='')
+ out('{:}'.format(number(usableSize, isSampled)), end='')
+ if count > 1:
+ out(' x {:,d}'.format(count), end='')
+ isFirst = False
+ out()
+
+ out(' Allocated at {')
+ printStack(traceTable, frameTable, record.allocatedAt)
+ out(' }')
+ if args.ignore_reports:
+ pass
+ else:
+ if hasattr(record, 'reportedAts'):
+ for n, reportedAt in enumerate(record.reportedAts):
+ again = 'again ' if n > 0 else ''
+ out(' Reported {:}at {{'.format(again))
+ printStack(traceTable, frameTable, reportedAt)
+ out(' }')
+ out('}\n')
+
+ return (kindUsableSize, kindBlocks)
+
+
+ # Print header.
+ out(separator)
+ out('Invocation {')
+ out(' $DMD = \'' + dmdEnvVar + '\'')
+ out(' Sample-below size = ' + str(sampleBelowSize))
+ out('}\n')
+
+ # Print records.
+ if args.ignore_reports:
+ liveUsableSize, liveBlocks = \
+ printRecords('live', liveRecords, heapUsableSize)
+ else:
+ twiceReportedUsableSize, twiceReportedBlocks = \
+ printRecords('twice-reported', twiceReportedRecords, heapUsableSize)
+
+ unreportedUsableSize, unreportedBlocks = \
+ printRecords('unreported', unreportedRecords, heapUsableSize)
+
+ onceReportedUsableSize, onceReportedBlocks = \
+ printRecords('once-reported', onceReportedRecords, heapUsableSize)
+
+ # Print summary.
+ out(separator)
+ out('Summary {')
+ if args.ignore_reports:
+ out(' Total: {:} bytes in {:} blocks'.
+ format(number(liveUsableSize, heapIsSampled),
+ number(liveBlocks, heapIsSampled)))
+ else:
+ fmt = ' {:15} {:>12} bytes ({:6.2f}%) in {:>7} blocks ({:6.2f}%)'
+ out(fmt.
+ format('Total:',
+ number(heapUsableSize, heapIsSampled),
+ 100,
+ number(heapBlocks, heapIsSampled),
+ 100))
+ out(fmt.
+ format('Unreported:',
+ number(unreportedUsableSize, heapIsSampled),
+ perc(unreportedUsableSize, heapUsableSize),
+ number(unreportedBlocks, heapIsSampled),
+ perc(unreportedBlocks, heapBlocks)))
+ out(fmt.
+ format('Once-reported:',
+ number(onceReportedUsableSize, heapIsSampled),
+ perc(onceReportedUsableSize, heapUsableSize),
+ number(onceReportedBlocks, heapIsSampled),
+ perc(onceReportedBlocks, heapBlocks)))
+ out(fmt.
+ format('Twice-reported:',
+ number(twiceReportedUsableSize, heapIsSampled),
+ perc(twiceReportedUsableSize, heapUsableSize),
+ number(twiceReportedBlocks, heapIsSampled),
+ perc(twiceReportedBlocks, heapBlocks)))
+ out('}\n')
+
+
+if __name__ == '__main__':
+ main()
deleted file mode 100644
--- a/memory/replace/dmd/test-expected.dmd
+++ /dev/null
@@ -1,841 +0,0 @@
-#-----------------------------------------------------------------
-
-Invocation {
- $DMD = '--mode=test'
- Function = AnalyzeReports
- Sample-below size = 1
-}
-
-#-----------------------------------------------------------------
-
-# no twice-reported heap blocks
-
-#-----------------------------------------------------------------
-
-# no unreported heap blocks
-
-#-----------------------------------------------------------------
-
-# no once-reported heap blocks
-
-#-----------------------------------------------------------------
-
-Summary {
- Total: 0 bytes (100.00%) in 0 blocks (100.00%)
- Unreported: 0 bytes ( 0.00%) in 0 blocks ( 0.00%)
- Once-reported: 0 bytes ( 0.00%) in 0 blocks ( 0.00%)
- Twice-reported: 0 bytes ( 0.00%) in 0 blocks ( 0.00%)
-}
-
-#-----------------------------------------------------------------
-
-Invocation {
- $DMD = '--mode=test'
- Function = AnalyzeHeap
- Sample-below size = 1
-}
-
-#-----------------------------------------------------------------
-
-# no live heap blocks
-
-#-----------------------------------------------------------------
-
-Summary {
- Total: 0 bytes in 0 blocks
-}
-
-#-----------------------------------------------------------------
-
-Invocation {
- $DMD = '--mode=test'
- Function = AnalyzeReports
- Sample-below size = 1
-}
-
-#-----------------------------------------------------------------
-
-Twice-reported {
- 1 block in heap block record 1 of 4
- 80 bytes (79 requested / 1 slop)
- 0.66% of the heap (0.66% cumulative)
- 29.41% of twice-reported (29.41% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
- Reported again at {
- ... DMD.cpp
- }
-}
-
-Twice-reported {
- 1 block in heap block record 2 of 4
- 80 bytes (78 requested / 2 slop)
- 0.66% of the heap (1.32% cumulative)
- 29.41% of twice-reported (58.82% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
- Reported again at {
- ... DMD.cpp
- }
-}
-
-Twice-reported {
- 1 block in heap block record 3 of 4
- 80 bytes (77 requested / 3 slop)
- 0.66% of the heap (1.99% cumulative)
- 29.41% of twice-reported (88.24% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
- Reported again at {
- ... DMD.cpp
- }
-}
-
-Twice-reported {
- 1 block in heap block record 4 of 4
- 32 bytes (30 requested / 2 slop)
- 0.26% of the heap (2.25% cumulative)
- 11.76% of twice-reported (100.00% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
- Reported again at {
- ... DMD.cpp
- }
-}
-
-#-----------------------------------------------------------------
-
-Unreported {
- 9 blocks in heap block record 1 of 3
- 1,008 bytes (900 requested / 108 slop)
- 8.34% of the heap (8.34% cumulative)
- 81.82% of unreported (81.82% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Unreported {
- 2 blocks in heap block record 2 of 3
- 112 bytes (112 requested / 0 slop)
- 0.93% of the heap (9.27% cumulative)
- 9.09% of unreported (90.91% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Unreported {
- 2 blocks in heap block record 3 of 3
- 112 bytes (112 requested / 0 slop)
- 0.93% of the heap (10.19% cumulative)
- 9.09% of unreported (100.00% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-#-----------------------------------------------------------------
-
-Once-reported {
- 1 block in heap block record 1 of 11
- 8,192 bytes (4,097 requested / 4,095 slop)
- 67.77% of the heap (67.77% cumulative)
- 77.40% of once-reported (77.40% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
-}
-
-Once-reported {
- 1 block in heap block record 2 of 11
- 1,024 bytes (1,023 requested / 1 slop)
- 8.47% of the heap (76.24% cumulative)
- 9.67% of once-reported (87.07% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
-}
-
-Once-reported {
- 1 block in heap block record 3 of 11
- 512 bytes (512 requested / 0 slop)
- 4.24% of the heap (80.48% cumulative)
- 4.84% of once-reported (91.91% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
-}
-
-Once-reported {
- 2 blocks in heap block record 4 of 11
- 240 bytes (240 requested / 0 slop)
- 1.99% of the heap (82.46% cumulative)
- 2.27% of once-reported (94.18% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
-}
-
-Once-reported {
- 2 blocks in heap block record 5 of 11
- 240 bytes (240 requested / 0 slop)
- 1.99% of the heap (84.45% cumulative)
- 2.27% of once-reported (96.45% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
-}
-
-Once-reported {
- 1 block in heap block record 6 of 11
- 96 bytes (96 requested / 0 slop)
- 0.79% of the heap (85.24% cumulative)
- 0.91% of once-reported (97.35% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
-}
-
-Once-reported {
- 1 block in heap block record 7 of 11
- 96 bytes (96 requested / 0 slop)
- 0.79% of the heap (86.04% cumulative)
- 0.91% of once-reported (98.26% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
-}
-
-Once-reported {
- 1 block in heap block record 8 of 11
- 80 bytes (80 requested / 0 slop)
- 0.66% of the heap (86.70% cumulative)
- 0.76% of once-reported (99.02% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
-}
-
-Once-reported {
- 1 block in heap block record 9 of 11
- 80 bytes (80 requested / 0 slop)
- 0.66% of the heap (87.36% cumulative)
- 0.76% of once-reported (99.77% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
-}
-
-Once-reported {
- 1 block in heap block record 10 of 11
- 16 bytes (10 requested / 6 slop)
- 0.13% of the heap (87.49% cumulative)
- 0.15% of once-reported (99.92% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
-}
-
-Once-reported {
- 1 block in heap block record 11 of 11
- 8 bytes (0 requested / 8 slop)
- 0.07% of the heap (87.56% cumulative)
- 0.08% of once-reported (100.00% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
-}
-
-#-----------------------------------------------------------------
-
-Summary {
- Total: 12,088 bytes (100.00%) in 30 blocks (100.00%)
- Unreported: 1,232 bytes ( 10.19%) in 13 blocks ( 43.33%)
- Once-reported: 10,584 bytes ( 87.56%) in 13 blocks ( 43.33%)
- Twice-reported: 272 bytes ( 2.25%) in 4 blocks ( 13.33%)
-}
-
-#-----------------------------------------------------------------
-
-Invocation {
- $DMD = '--mode=test'
- Function = AnalyzeHeap
- Sample-below size = 1
-}
-
-#-----------------------------------------------------------------
-
-Live {
- 1 block in heap block record 1 of 12
- 8,192 bytes (4,097 requested / 4,095 slop)
- 67.77% of the heap (67.77% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 2 of 12
- 1,024 bytes (1,023 requested / 1 slop)
- 8.47% of the heap (76.24% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 9 blocks in heap block record 3 of 12
- 1,008 bytes (900 requested / 108 slop)
- 8.34% of the heap (84.58% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 6 blocks in heap block record 4 of 12
- 528 bytes (528 requested / 0 slop)
- 4.37% of the heap (88.95% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 6 blocks in heap block record 5 of 12
- 528 bytes (528 requested / 0 slop)
- 4.37% of the heap (93.32% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 6 of 12
- 512 bytes (512 requested / 0 slop)
- 4.24% of the heap (97.55% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 7 of 12
- 80 bytes (79 requested / 1 slop)
- 0.66% of the heap (98.21% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 8 of 12
- 80 bytes (78 requested / 2 slop)
- 0.66% of the heap (98.87% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 9 of 12
- 80 bytes (77 requested / 3 slop)
- 0.66% of the heap (99.54% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 10 of 12
- 32 bytes (30 requested / 2 slop)
- 0.26% of the heap (99.80% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 11 of 12
- 16 bytes (10 requested / 6 slop)
- 0.13% of the heap (99.93% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 12 of 12
- 8 bytes (0 requested / 8 slop)
- 0.07% of the heap (100.00% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-#-----------------------------------------------------------------
-
-Summary {
- Total: 12,088 bytes in 30 blocks
-}
-
-#-----------------------------------------------------------------
-
-Invocation {
- $DMD = '--mode=test'
- Function = AnalyzeReports
- Sample-below size = 1
-}
-
-#-----------------------------------------------------------------
-
-Twice-reported {
- 1 block in heap block record 1 of 2
- 80 bytes (77 requested / 3 slop)
- 2.82% of the heap (2.82% cumulative)
- 90.91% of twice-reported (90.91% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
- Reported again at {
- ... DMD.cpp
- }
-}
-
-Twice-reported {
- 1 block in heap block record 2 of 2
- 8 bytes (0 requested / 8 slop)
- 0.28% of the heap (3.10% cumulative)
- 9.09% of twice-reported (100.00% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
- Reported again at {
- ... DMD.cpp
- }
-}
-
-#-----------------------------------------------------------------
-
-Unreported {
- 9 blocks in heap block record 1 of 3
- 1,008 bytes (900 requested / 108 slop)
- 35.49% of the heap (35.49% cumulative)
- 48.84% of unreported (48.84% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Unreported {
- 6 blocks in heap block record 2 of 3
- 528 bytes (528 requested / 0 slop)
- 18.59% of the heap (54.08% cumulative)
- 25.58% of unreported (74.42% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Unreported {
- 6 blocks in heap block record 3 of 3
- 528 bytes (528 requested / 0 slop)
- 18.59% of the heap (72.68% cumulative)
- 25.58% of unreported (100.00% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-#-----------------------------------------------------------------
-
-Once-reported {
- 1 block in heap block record 1 of 4
- 512 bytes (512 requested / 0 slop)
- 18.03% of the heap (18.03% cumulative)
- 74.42% of once-reported (74.42% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
-}
-
-Once-reported {
- 1 block in heap block record 2 of 4
- 80 bytes (79 requested / 1 slop)
- 2.82% of the heap (20.85% cumulative)
- 11.63% of once-reported (86.05% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
-}
-
-Once-reported {
- 1 block in heap block record 3 of 4
- 80 bytes (78 requested / 2 slop)
- 2.82% of the heap (23.66% cumulative)
- 11.63% of once-reported (97.67% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
-}
-
-Once-reported {
- 1 block in heap block record 4 of 4
- 16 bytes (10 requested / 6 slop)
- 0.56% of the heap (24.23% cumulative)
- 2.33% of once-reported (100.00% cumulative)
- Allocated at {
- ... DMD.cpp
- }
- Reported at {
- ... DMD.cpp
- }
-}
-
-#-----------------------------------------------------------------
-
-Summary {
- Total: 2,840 bytes (100.00%) in 27 blocks (100.00%)
- Unreported: 2,064 bytes ( 72.68%) in 21 blocks ( 77.78%)
- Once-reported: 688 bytes ( 24.23%) in 4 blocks ( 14.81%)
- Twice-reported: 88 bytes ( 3.10%) in 2 blocks ( 7.41%)
-}
-
-#-----------------------------------------------------------------
-
-Invocation {
- $DMD = '--mode=test'
- Function = AnalyzeHeap
- Sample-below size = 1
-}
-
-#-----------------------------------------------------------------
-
-Live {
- 9 blocks in heap block record 1 of 9
- 1,008 bytes (900 requested / 108 slop)
- 35.49% of the heap (35.49% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 6 blocks in heap block record 2 of 9
- 528 bytes (528 requested / 0 slop)
- 18.59% of the heap (54.08% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 6 blocks in heap block record 3 of 9
- 528 bytes (528 requested / 0 slop)
- 18.59% of the heap (72.68% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 4 of 9
- 512 bytes (512 requested / 0 slop)
- 18.03% of the heap (90.70% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 5 of 9
- 80 bytes (79 requested / 1 slop)
- 2.82% of the heap (93.52% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 6 of 9
- 80 bytes (78 requested / 2 slop)
- 2.82% of the heap (96.34% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 7 of 9
- 80 bytes (77 requested / 3 slop)
- 2.82% of the heap (99.15% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 8 of 9
- 16 bytes (10 requested / 6 slop)
- 0.56% of the heap (99.72% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 9 of 9
- 8 bytes (0 requested / 8 slop)
- 0.28% of the heap (100.00% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-#-----------------------------------------------------------------
-
-Summary {
- Total: 2,840 bytes in 27 blocks
-}
-
-#-----------------------------------------------------------------
-
-Invocation {
- $DMD = '--mode=test'
- Function = AnalyzeReports
- Sample-below size = 128
-}
-
-#-----------------------------------------------------------------
-
-# no twice-reported heap blocks
-
-#-----------------------------------------------------------------
-
-Unreported {
- ~4 blocks in heap block record 1 of 7
- ~512 bytes (~512 requested / ~0 slop)
- 35.96% of the heap (35.96% cumulative)
- 35.96% of unreported (35.96% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Unreported {
- 1 block in heap block record 2 of 7
- 256 bytes (256 requested / 0 slop)
- 17.98% of the heap (53.93% cumulative)
- 17.98% of unreported (53.93% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Unreported {
- 1 block in heap block record 3 of 7
- 144 bytes (144 requested / 0 slop)
- 10.11% of the heap (64.04% cumulative)
- 10.11% of unreported (64.04% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Unreported {
- 1 block in heap block record 4 of 7
- 128 bytes (128 requested / 0 slop)
- 8.99% of the heap (73.03% cumulative)
- 8.99% of unreported (73.03% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Unreported {
- ~1 block in heap block record 5 of 7
- ~128 bytes (~128 requested / ~0 slop)
- 8.99% of the heap (82.02% cumulative)
- 8.99% of unreported (82.02% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Unreported {
- ~1 block in heap block record 6 of 7
- ~128 bytes (~128 requested / ~0 slop)
- 8.99% of the heap (91.01% cumulative)
- 8.99% of unreported (91.01% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Unreported {
- ~1 block in heap block record 7 of 7
- ~128 bytes (~128 requested / ~0 slop)
- 8.99% of the heap (100.00% cumulative)
- 8.99% of unreported (100.00% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-#-----------------------------------------------------------------
-
-# no once-reported heap blocks
-
-#-----------------------------------------------------------------
-
-Summary {
- Total: ~1,424 bytes (100.00%) in ~10 blocks (100.00%)
- Unreported: ~1,424 bytes (100.00%) in ~10 blocks (100.00%)
- Once-reported: ~0 bytes ( 0.00%) in ~0 blocks ( 0.00%)
- Twice-reported: ~0 bytes ( 0.00%) in ~0 blocks ( 0.00%)
-}
-
-#-----------------------------------------------------------------
-
-Invocation {
- $DMD = '--mode=test'
- Function = AnalyzeHeap
- Sample-below size = 128
-}
-
-#-----------------------------------------------------------------
-
-Live {
- ~4 blocks in heap block record 1 of 7
- ~512 bytes (~512 requested / ~0 slop)
- 35.96% of the heap (35.96% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 2 of 7
- 256 bytes (256 requested / 0 slop)
- 17.98% of the heap (53.93% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 3 of 7
- 144 bytes (144 requested / 0 slop)
- 10.11% of the heap (64.04% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- 1 block in heap block record 4 of 7
- 128 bytes (128 requested / 0 slop)
- 8.99% of the heap (73.03% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- ~1 block in heap block record 5 of 7
- ~128 bytes (~128 requested / ~0 slop)
- 8.99% of the heap (82.02% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- ~1 block in heap block record 6 of 7
- ~128 bytes (~128 requested / ~0 slop)
- 8.99% of the heap (91.01% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-Live {
- ~1 block in heap block record 7 of 7
- ~128 bytes (~128 requested / ~0 slop)
- 8.99% of the heap (100.00% cumulative)
- Allocated at {
- ... DMD.cpp
- }
-}
-
-#-----------------------------------------------------------------
-
-Summary {
- Total: ~1,424 bytes in ~10 blocks
-}
-
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-heap-expected1.txt
@@ -0,0 +1,17 @@
+#-----------------------------------------------------------------
+
+Invocation {
+ $DMD = '--mode=test'
+ Sample-below size = 1
+}
+
+#-----------------------------------------------------------------
+
+# no live heap blocks
+
+#-----------------------------------------------------------------
+
+Summary {
+ Total: 0 bytes in 0 blocks
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-heap-expected2.txt
@@ -0,0 +1,123 @@
+#-----------------------------------------------------------------
+
+Invocation {
+ $DMD = '--mode=test'
+ Sample-below size = 1
+}
+
+#-----------------------------------------------------------------
+
+Live {
+ 1 block in heap block record 1 of 12
+ 8,192 bytes (4,097 requested / 4,095 slop)
+ 67.77% of the heap (67.77% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 2 of 12
+ 1,024 bytes (1,023 requested / 1 slop)
+ 8.47% of the heap (76.24% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 9 blocks in heap block record 3 of 12
+ 1,008 bytes (900 requested / 108 slop)
+ 8.34% of the heap (84.58% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 6 blocks in heap block record 4 of 12
+ 528 bytes (528 requested / 0 slop)
+ 4.37% of the heap (88.95% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 6 blocks in heap block record 5 of 12
+ 528 bytes (528 requested / 0 slop)
+ 4.37% of the heap (93.32% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 6 of 12
+ 512 bytes (512 requested / 0 slop)
+ 4.24% of the heap (97.55% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 7 of 12
+ 80 bytes (79 requested / 1 slop)
+ 0.66% of the heap (98.21% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 8 of 12
+ 80 bytes (78 requested / 2 slop)
+ 0.66% of the heap (98.87% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 9 of 12
+ 80 bytes (77 requested / 3 slop)
+ 0.66% of the heap (99.54% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 10 of 12
+ 32 bytes (30 requested / 2 slop)
+ 0.26% of the heap (99.80% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 11 of 12
+ 16 bytes (10 requested / 6 slop)
+ 0.13% of the heap (99.93% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 12 of 12
+ 8 bytes (0 requested / 8 slop)
+ 0.07% of the heap (100.00% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+ Total: 12,088 bytes in 30 blocks
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-heap-expected3.txt
@@ -0,0 +1,96 @@
+#-----------------------------------------------------------------
+
+Invocation {
+ $DMD = '--mode=test'
+ Sample-below size = 1
+}
+
+#-----------------------------------------------------------------
+
+Live {
+ 9 blocks in heap block record 1 of 9
+ 1,008 bytes (900 requested / 108 slop)
+ 35.49% of the heap (35.49% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 6 blocks in heap block record 2 of 9
+ 528 bytes (528 requested / 0 slop)
+ 18.59% of the heap (54.08% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 6 blocks in heap block record 3 of 9
+ 528 bytes (528 requested / 0 slop)
+ 18.59% of the heap (72.68% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 4 of 9
+ 512 bytes (512 requested / 0 slop)
+ 18.03% of the heap (90.70% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 5 of 9
+ 80 bytes (79 requested / 1 slop)
+ 2.82% of the heap (93.52% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 6 of 9
+ 80 bytes (78 requested / 2 slop)
+ 2.82% of the heap (96.34% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 7 of 9
+ 80 bytes (77 requested / 3 slop)
+ 2.82% of the heap (99.15% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 8 of 9
+ 16 bytes (10 requested / 6 slop)
+ 0.56% of the heap (99.72% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 9 of 9
+ 8 bytes (0 requested / 8 slop)
+ 0.28% of the heap (100.00% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+ Total: 2,840 bytes in 27 blocks
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-heap-expected4.txt
@@ -0,0 +1,78 @@
+#-----------------------------------------------------------------
+
+Invocation {
+ $DMD = '--mode=test'
+ Sample-below size = 128
+}
+
+#-----------------------------------------------------------------
+
+Live {
+ ~4 blocks in heap block record 1 of 7
+ ~512 bytes (~512 requested / ~0 slop)
+ 35.96% of the heap (35.96% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 2 of 7
+ 256 bytes (256 requested / 0 slop)
+ 17.98% of the heap (53.93% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 3 of 7
+ 144 bytes (144 requested / 0 slop)
+ 10.11% of the heap (64.04% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ 1 block in heap block record 4 of 7
+ 128 bytes (128 requested / 0 slop)
+ 8.99% of the heap (73.03% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ ~1 block in heap block record 5 of 7
+ ~128 bytes (~128 requested / ~0 slop)
+ 8.99% of the heap (82.02% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ ~1 block in heap block record 6 of 7
+ ~128 bytes (~128 requested / ~0 slop)
+ 8.99% of the heap (91.01% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Live {
+ ~1 block in heap block record 7 of 7
+ ~128 bytes (~128 requested / ~0 slop)
+ 8.99% of the heap (100.00% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+ Total: ~1,424 bytes in ~10 blocks
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-reports-expected1.txt
@@ -0,0 +1,28 @@
+#-----------------------------------------------------------------
+
+Invocation {
+ $DMD = '--mode=test'
+ Sample-below size = 1
+}
+
+#-----------------------------------------------------------------
+
+# no twice-reported heap blocks
+
+#-----------------------------------------------------------------
+
+# no unreported heap blocks
+
+#-----------------------------------------------------------------
+
+# no once-reported heap blocks
+
+#-----------------------------------------------------------------
+
+Summary {
+ Total: 0 bytes (100.00%) in 0 blocks (100.00%)
+ Unreported: 0 bytes ( 0.00%) in 0 blocks ( 0.00%)
+ Once-reported: 0 bytes ( 0.00%) in 0 blocks ( 0.00%)
+ Twice-reported: 0 bytes ( 0.00%) in 0 blocks ( 0.00%)
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-reports-expected2.txt
@@ -0,0 +1,259 @@
+#-----------------------------------------------------------------
+
+Invocation {
+ $DMD = '--mode=test'
+ Sample-below size = 1
+}
+
+#-----------------------------------------------------------------
+
+Twice-reported {
+ 1 block in heap block record 1 of 4
+ 80 bytes (79 requested / 1 slop)
+ 0.66% of the heap (0.66% cumulative)
+ 29.41% of twice-reported (29.41% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+ Reported again at {
+ ... DMD.cpp
+ }
+}
+
+Twice-reported {
+ 1 block in heap block record 2 of 4
+ 80 bytes (78 requested / 2 slop)
+ 0.66% of the heap (1.32% cumulative)
+ 29.41% of twice-reported (58.82% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+ Reported again at {
+ ... DMD.cpp
+ }
+}
+
+Twice-reported {
+ 1 block in heap block record 3 of 4
+ 80 bytes (77 requested / 3 slop)
+ 0.66% of the heap (1.99% cumulative)
+ 29.41% of twice-reported (88.24% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+ Reported again at {
+ ... DMD.cpp
+ }
+}
+
+Twice-reported {
+ 1 block in heap block record 4 of 4
+ 32 bytes (30 requested / 2 slop)
+ 0.26% of the heap (2.25% cumulative)
+ 11.76% of twice-reported (100.00% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+ Reported again at {
+ ... DMD.cpp
+ }
+}
+
+#-----------------------------------------------------------------
+
+Unreported {
+ 9 blocks in heap block record 1 of 3
+ 1,008 bytes (900 requested / 108 slop)
+ 8.34% of the heap (8.34% cumulative)
+ 81.82% of unreported (81.82% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Unreported {
+ 2 blocks in heap block record 2 of 3
+ 112 bytes (112 requested / 0 slop)
+ 0.93% of the heap (9.27% cumulative)
+ 9.09% of unreported (90.91% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Unreported {
+ 2 blocks in heap block record 3 of 3
+ 112 bytes (112 requested / 0 slop)
+ 0.93% of the heap (10.19% cumulative)
+ 9.09% of unreported (100.00% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+#-----------------------------------------------------------------
+
+Once-reported {
+ 1 block in heap block record 1 of 11
+ 8,192 bytes (4,097 requested / 4,095 slop)
+ 67.77% of the heap (67.77% cumulative)
+ 77.40% of once-reported (77.40% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+}
+
+Once-reported {
+ 1 block in heap block record 2 of 11
+ 1,024 bytes (1,023 requested / 1 slop)
+ 8.47% of the heap (76.24% cumulative)
+ 9.67% of once-reported (87.07% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+}
+
+Once-reported {
+ 1 block in heap block record 3 of 11
+ 512 bytes (512 requested / 0 slop)
+ 4.24% of the heap (80.48% cumulative)
+ 4.84% of once-reported (91.91% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+}
+
+Once-reported {
+ 2 blocks in heap block record 4 of 11
+ 240 bytes (240 requested / 0 slop)
+ 1.99% of the heap (82.46% cumulative)
+ 2.27% of once-reported (94.18% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+}
+
+Once-reported {
+ 2 blocks in heap block record 5 of 11
+ 240 bytes (240 requested / 0 slop)
+ 1.99% of the heap (84.45% cumulative)
+ 2.27% of once-reported (96.45% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+}
+
+Once-reported {
+ 1 block in heap block record 6 of 11
+ 96 bytes (96 requested / 0 slop)
+ 0.79% of the heap (85.24% cumulative)
+ 0.91% of once-reported (97.35% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+}
+
+Once-reported {
+ 1 block in heap block record 7 of 11
+ 96 bytes (96 requested / 0 slop)
+ 0.79% of the heap (86.04% cumulative)
+ 0.91% of once-reported (98.26% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+}
+
+Once-reported {
+ 1 block in heap block record 8 of 11
+ 80 bytes (80 requested / 0 slop)
+ 0.66% of the heap (86.70% cumulative)
+ 0.76% of once-reported (99.02% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+}
+
+Once-reported {
+ 1 block in heap block record 9 of 11
+ 80 bytes (80 requested / 0 slop)
+ 0.66% of the heap (87.36% cumulative)
+ 0.76% of once-reported (99.77% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+}
+
+Once-reported {
+ 1 block in heap block record 10 of 11
+ 16 bytes (10 requested / 6 slop)
+ 0.13% of the heap (87.49% cumulative)
+ 0.15% of once-reported (99.92% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+}
+
+Once-reported {
+ 1 block in heap block record 11 of 11
+ 8 bytes (0 requested / 8 slop)
+ 0.07% of the heap (87.56% cumulative)
+ 0.08% of once-reported (100.00% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+ Total: 12,088 bytes (100.00%) in 30 blocks (100.00%)
+ Unreported: 1,232 bytes ( 10.19%) in 13 blocks ( 43.33%)
+ Once-reported: 10,584 bytes ( 87.56%) in 13 blocks ( 43.33%)
+ Twice-reported: 272 bytes ( 2.25%) in 4 blocks ( 13.33%)
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-reports-expected3.txt
@@ -0,0 +1,136 @@
+#-----------------------------------------------------------------
+
+Invocation {
+ $DMD = '--mode=test'
+ Sample-below size = 1
+}
+
+#-----------------------------------------------------------------
+
+Twice-reported {
+ 1 block in heap block record 1 of 2
+ 80 bytes (77 requested / 3 slop)
+ 2.82% of the heap (2.82% cumulative)
+ 90.91% of twice-reported (90.91% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+ Reported again at {
+ ... DMD.cpp
+ }
+}
+
+Twice-reported {
+ 1 block in heap block record 2 of 2
+ 8 bytes (0 requested / 8 slop)
+ 0.28% of the heap (3.10% cumulative)
+ 9.09% of twice-reported (100.00% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+ Reported again at {
+ ... DMD.cpp
+ }
+}
+
+#-----------------------------------------------------------------
+
+Unreported {
+ 9 blocks in heap block record 1 of 3
+ 1,008 bytes (900 requested / 108 slop)
+ 35.49% of the heap (35.49% cumulative)
+ 48.84% of unreported (48.84% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Unreported {
+ 6 blocks in heap block record 2 of 3
+ 528 bytes (528 requested / 0 slop)
+ 18.59% of the heap (54.08% cumulative)
+ 25.58% of unreported (74.42% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Unreported {
+ 6 blocks in heap block record 3 of 3
+ 528 bytes (528 requested / 0 slop)
+ 18.59% of the heap (72.68% cumulative)
+ 25.58% of unreported (100.00% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+#-----------------------------------------------------------------
+
+Once-reported {
+ 1 block in heap block record 1 of 4
+ 512 bytes (512 requested / 0 slop)
+ 18.03% of the heap (18.03% cumulative)
+ 74.42% of once-reported (74.42% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+}
+
+Once-reported {
+ 1 block in heap block record 2 of 4
+ 80 bytes (79 requested / 1 slop)
+ 2.82% of the heap (20.85% cumulative)
+ 11.63% of once-reported (86.05% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+}
+
+Once-reported {
+ 1 block in heap block record 3 of 4
+ 80 bytes (78 requested / 2 slop)
+ 2.82% of the heap (23.66% cumulative)
+ 11.63% of once-reported (97.67% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+}
+
+Once-reported {
+ 1 block in heap block record 4 of 4
+ 16 bytes (10 requested / 6 slop)
+ 0.56% of the heap (24.23% cumulative)
+ 2.33% of once-reported (100.00% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+ Reported at {
+ ... DMD.cpp
+ }
+}
+
+#-----------------------------------------------------------------
+
+Summary {
+ Total: 2,840 bytes (100.00%) in 27 blocks (100.00%)
+ Unreported: 2,064 bytes ( 72.68%) in 21 blocks ( 77.78%)
+ Once-reported: 688 bytes ( 24.23%) in 4 blocks ( 14.81%)
+ Twice-reported: 88 bytes ( 3.10%) in 2 blocks ( 7.41%)
+}
+
new file mode 100644
--- /dev/null
+++ b/memory/replace/dmd/test/full-reports-expected4.txt
@@ -0,0 +1,96 @@
+#-----------------------------------------------------------------
+
+Invocation {
+ $DMD = '--mode=test'
+ Sample-below size = 128
+}
+
+#-----------------------------------------------------------------
+
+# no twice-reported heap blocks
+
+#-----------------------------------------------------------------
+
+Unreported {
+ ~4 blocks in heap block record 1 of 7
+ ~512 bytes (~512 requested / ~0 slop)
+ 35.96% of the heap (35.96% cumulative)
+ 35.96% of unreported (35.96% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Unreported {
+ 1 block in heap block record 2 of 7
+ 256 bytes (256 requested / 0 slop)
+ 17.98% of the heap (53.93% cumulative)
+ 17.98% of unreported (53.93% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Unreported {
+ 1 block in heap block record 3 of 7
+ 144 bytes (144 requested / 0 slop)
+ 10.11% of the heap (64.04% cumulative)
+ 10.11% of unreported (64.04% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Unreported {
+ 1 block in heap block record 4 of 7
+ 128 bytes (128 requested / 0 slop)
+ 8.99% of the heap (73.03% cumulative)
+ 8.99% of unreported (73.03% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Unreported {
+ ~1 block in heap block record 5 of 7
+ ~128 bytes (~128 requested / ~0 slop)
+ 8.99% of the heap (82.02% cumulative)
+ 8.99% of unreported (82.02% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Unreported {
+ ~1 block in heap block record 6 of 7
+ ~128 bytes (~128 requested / ~0 slop)
+ 8.99% of the heap (91.01% cumulative)
+ 8.99% of unreported (91.01% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+Unreported {
+ ~1 block in heap block record 7 of 7
+ ~128 bytes (~128 requested / ~0 slop)
+ 8.99% of the heap (100.00% cumulative)
+ 8.99% of unreported (100.00% cumulative)
+ Allocated at {
+ ... DMD.cpp
+ }
+}
+
+#-----------------------------------------------------------------
+
+# no once-reported heap blocks
+
+#-----------------------------------------------------------------
+
+Summary {
+ Total: ~1,424 bytes (100.00%) in ~10 blocks (100.00%)
+ Unreported: ~1,424 bytes (100.00%) in ~10 blocks (100.00%)
+ Once-reported: ~0 bytes ( 0.00%) in ~0 blocks ( 0.00%)
+ Twice-reported: ~0 bytes ( 0.00%) in ~0 blocks ( 0.00%)
+}
+
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -929,42 +929,44 @@ class RunDmd(MachCommandBase):
help=('Command-line arguments to be passed through to the program. '
'Not specifying a -profile or -P option will result in a '
'temporary profile being used. If passing -params use a "--" to '
'indicate the start of params to pass to firefox.'))
@CommandArgument('--remote', '-r', action='store_true',
help='Do not pass the -no-remote argument by default.')
@CommandArgument('--background', '-b', action='store_true',
help='Do not pass the -foreground argument by default on Mac')
+ @CommandArgument('--noprofile', '-n', action='store_true',
+ help='Do not pass the -profile argument by default.')
@CommandArgument('--sample-below', default=None, type=str,
help='The sample size to use, [1..n]. Default is 4093.')
@CommandArgument('--max-frames', default=None, type=str,
help='The max number of stack frames to capture in allocation traces, [1..24] Default is 24.')
- @CommandArgument('--max-records', default=None, type=str,
- help='Number of stack trace records to print of each kind, [1..1000000]. Default is 1000.')
- def dmd(self, params, remote, background, sample_below, max_frames, max_records):
- args = get_run_args(self, params, remote, background)
+ @CommandArgument('--show-dump-stats', action='store_true',
+ help='Show stats when doing dumps.')
+ def dmd(self, params, remote, background, noprofile, sample_below, max_frames, show_dump_stats):
+ args = get_run_args(self, params, remote, background, noprofile)
if not args:
return 1
lib_dir = os.path.join(self.distdir, 'lib')
lib_name = self.substs['DLL_PREFIX'] + 'dmd' + self.substs['DLL_SUFFIX']
dmd_lib = os.path.join(lib_dir, lib_name)
if not os.path.exists(dmd_lib):
print("You need to build with |--enable-dmd| to use dmd.")
return 1
dmd_params = []
if sample_below:
dmd_params.append('--sample-below=' + sample_below)
if max_frames:
dmd_params.append('--max-frames=' + max_frames)
- if max_records:
- dmd_params.append('--max-records=' + max_records)
+ if show_dump_stats:
+ dmd_params.append('--show-dump-stats=yes')
if dmd_params:
dmd_str = " ".join(dmd_params)
else:
dmd_str = "1"
env_vars = {
"Darwin": {
--- a/security/manager/ssl/src/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/src/SSLServerCertVerification.cpp
@@ -398,18 +398,17 @@ CertErrorRunnable::CheckCertOverrides()
NS_ERROR("CertErrorRunnable::CheckCertOverrides called off main thread");
return new SSLServerCertVerificationResult(mInfoObject,
mDefaultErrorCodeToReport);
}
int32_t port;
mInfoObject->GetPort(&port);
- nsCString hostWithPortString;
- hostWithPortString.AppendASCII(mInfoObject->GetHostNameRaw());
+ nsAutoCString hostWithPortString(mInfoObject->GetHostName());
hostWithPortString.Append(':');
hostWithPortString.AppendInt(port);
uint32_t remaining_display_errors = mCollectedErrors;
nsresult nsrv;
// Enforce Strict-Transport-Security for hosts that are "STS" hosts:
@@ -435,17 +434,17 @@ CertErrorRunnable::CheckCertOverrides()
// it is fine to continue without the nsICertOverrideService
uint32_t overrideBits = 0;
if (overrideService)
{
bool haveOverride;
bool isTemporaryOverride; // we don't care
- nsCString hostString(mInfoObject->GetHostName());
+ const nsACString& hostString(mInfoObject->GetHostName());
nsrv = overrideService->HasMatchingOverride(hostString, port,
mCert,
&overrideBits,
&isTemporaryOverride,
&haveOverride);
if (NS_SUCCEEDED(nsrv) && haveOverride)
{
// remove the errors that are already overriden
@@ -762,35 +761,20 @@ AccumulateSubjectCommonNameTelemetry(con
}
}
// Returns true if and only if commonName ends with altName (minus its leading
// "*"). altName has already been checked to be of the form "*.<something>".
// commonName may be NULL.
static bool
TryMatchingWildcardSubjectAltName(const char* commonName,
- nsDependentCString altName)
+ const nsACString& altName)
{
- if (!commonName) {
- return false;
- }
- // altNameSubstr is now ".<something>"
- nsDependentCString altNameSubstr(altName.get() + 1, altName.Length() - 1);
- nsDependentCString commonNameStr(commonName, strlen(commonName));
- int32_t altNameIndex = commonNameStr.Find(altNameSubstr);
- // This only matches if the end of commonNameStr is the altName without
- // the '*'.
- // Consider this.example.com and *.example.com:
- // "this.example.com".Find(".example.com") is 4
- // 4 + ".example.com".Length() == 4 + 12 == 16 == "this.example.com".Length()
- // Now this.example.com and *.example:
- // "this.example.com".Find(".example") is 4
- // 4 + ".example".Length() == 4 + 8 == 12 != "this.example.com".Length()
- return altNameIndex >= 0 &&
- altNameIndex + altNameSubstr.Length() == commonNameStr.Length();
+ return commonName &&
+ StringEndsWith(nsDependentCString(commonName), Substring(altName, 1));
}
// Gathers telemetry on Baseline Requirements 9.2.1 (Subject Alternative
// Names Extension) and 9.2.2 (Subject Common Name Field).
// Specifically:
// - whether or not the subject common name field is present
// - whether or not the subject alternative names extension is present
// - if there is a malformed entry in the subject alt. names extension
@@ -854,23 +838,23 @@ GatherBaselineRequirementsTelemetry(cons
}
CERTGeneralName* currentName = subjectAltNames;
bool commonNameInSubjectAltNames = false;
bool nonDNSNameOrIPAddressPresent = false;
bool malformedDNSNameOrIPAddressPresent = false;
bool nonFQDNPresent = false;
do {
- nsDependentCString altName;
+ nsAutoCString altName;
if (currentName->type == certDNSName) {
altName.Assign(reinterpret_cast<char*>(currentName->name.other.data),
currentName->name.other.len);
- nsDependentCString altNameWithoutWildcard(altName);
- if (altNameWithoutWildcard.Find("*.") == 0) {
- altNameWithoutWildcard.Assign(altName.get() + 2, altName.Length() - 2);
+ nsDependentCString altNameWithoutWildcard(altName, 0);
+ if (StringBeginsWith(altNameWithoutWildcard, NS_LITERAL_CSTRING("*."))) {
+ altNameWithoutWildcard.Rebind(altName, 2);
commonNameInSubjectAltNames |=
TryMatchingWildcardSubjectAltName(commonName.get(), altName);
}
// net_IsValidHostName appears to return true for valid IP addresses,
// which would be invalid for a DNS name.
// Note that the net_IsValidHostName check will catch things like
// "a.*.example.com".
if (!net_IsValidHostName(altNameWithoutWildcard) ||
@@ -893,29 +877,29 @@ GatherBaselineRequirementsTelemetry(cons
memcpy(&addr.inet.ip, currentName->name.other.data,
currentName->name.other.len);
if (PR_NetAddrToString(&addr, buf, sizeof(buf) - 1) != PR_SUCCESS) {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
("BR telemetry: IPAddress (v4) not valid (for '%s')\n",
commonName.get()));
malformedDNSNameOrIPAddressPresent = true;
} else {
- altName.Assign(buf, strlen(buf));
+ altName.Assign(buf);
}
} else if (currentName->name.other.len == 16) {
addr.inet.family = PR_AF_INET6;
memcpy(&addr.ipv6.ip, currentName->name.other.data,
currentName->name.other.len);
if (PR_NetAddrToString(&addr, buf, sizeof(buf) - 1) != PR_SUCCESS) {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
("BR telemetry: IPAddress (v6) not valid (for '%s')\n",
commonName.get()));
malformedDNSNameOrIPAddressPresent = true;
} else {
- altName.Assign(buf, strlen(buf));
+ altName.Assign(buf);
}
} else {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
("BR telemetry: IPAddress not valid (for '%s')\n",
commonName.get()));
malformedDNSNameOrIPAddressPresent = true;
}
} else {
--- a/security/sandbox/win/src/warnonlysandbox/wosCallbacks.h
+++ b/security/sandbox/win/src/warnonlysandbox/wosCallbacks.h
@@ -54,24 +54,26 @@ SetProvideLogFunctionCb(ProvideLogFuncti
sProvideLogFunctionCb = aProvideLogFunctionCb;
}
#ifdef MOZ_STACKWALKING
static uint32_t sStackTraceDepth = 0;
// NS_WalkStackCallback to write a formatted stack frame to an ostringstream.
static void
-StackFrameToOStringStream(void* aPC, void* aSP, void* aClosure)
+StackFrameToOStringStream(uint32_t aFrameNumber, void* aPC, void* aSP,
+ void* aClosure)
{
std::ostringstream* stream = static_cast<std::ostringstream*>(aClosure);
nsCodeAddressDetails details;
char buf[1024];
NS_DescribeCodeAddress(aPC, &details);
- NS_FormatCodeAddressDetails(aPC, &details, buf, sizeof(buf));
- *stream << "--" << buf;
+ NS_FormatCodeAddressDetails(buf, sizeof(buf), aFrameNumber, aPC, &details);
+ *stream << "--" << buf << '\n';
+ stream->flush();
}
#endif
// Log to the browser console and, if DEBUG build, stderr.
static void
Log(const char* aMessageType,
const char* aFunctionName,
const char* aContext,
--- a/toolkit/content/tests/chrome/findbar_events_window.xul
+++ b/toolkit/content/tests/chrome/findbar_events_window.xul
@@ -16,17 +16,17 @@
<script type="application/javascript"><![CDATA[
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;
var gFindBar = null;
var gBrowser;
- var imports = ["SimpleTest", "ok"];
+ var imports = ["SimpleTest", "ok", "is"];
for each (var name in imports) {
window[name] = window.opener.wrappedJSObject[name];
}
function finish() {
window.close();
SimpleTest.finish();
}
@@ -66,31 +66,38 @@
gFindBar.open();
gFindBar.onFindCommand();
nextTest();
}
function checkSelection(done) {
SimpleTest.executeSoon(function() {
var selected = gBrowser.contentWindow.getSelection();
- ok(selected == "", "No text is selected");
+ is(selected, "", "No text is selected");
var controller = gFindBar.browser.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController);
var selection = controller.getSelection(controller.SELECTION_FIND);
- ok(selection.rangeCount == 0, "No text is highlighted");
+ is(selection.rangeCount, 0, "No text is highlighted");
done();
});
}
+ function once(node, eventName, callback) {
+ node.addEventListener(eventName, function clb(e) {
+ node.removeEventListener(eventName, clb);
+ callback(e);
+ })
+ }
+
function testFind(done) {
- var findTriggered = false;
+ var eventTriggered = false;
var query = "t";
- gFindBar.addEventListener("find", function(e) {
+ once(gFindBar, "find", function(e) {
eventTriggered = true;
ok(e.detail.query === query, "find event query should match '" + query + "'");
e.preventDefault();
// Since we're preventing the default make sure nothing was selected.
checkSelection(done);
});
// Put some text in the find box.
@@ -98,44 +105,48 @@
event.initKeyEvent("keypress", true, true, null, false, false,
false, false, 0, query.charCodeAt(0));
gFindBar._findField.inputField.dispatchEvent(event);
ok(eventTriggered, "find event should be triggered");
}
function testFindAgain(done) {
var eventTriggered = false;
- gFindBar.addEventListener("findagain", function(e) {
+ once(gFindBar, "findagain", function(e) {
eventTriggered = true;
e.preventDefault();
// Since we're preventing the default make sure nothing was selected.
checkSelection(done);
});
gFindBar.onFindAgainCommand();
ok(eventTriggered, "findagain event should be triggered");
}
function testCaseSensitivity() {
var eventTriggered = false;
- gFindBar.addEventListener("findcasesensitivitychange", function(e) {
+ once(gFindBar, "findcasesensitivitychange", function(e) {
eventTriggered = true;
ok(e.detail.caseSensitive, "find should be case sensitive");
});
var matchCaseCheckbox = gFindBar.getElement("find-case-sensitive");
matchCaseCheckbox.click();
ok(eventTriggered, "findcasesensitivitychange should be triggered");
+
+ // Changing case sensitivity does the search so clear the selected text
+ // before the next test.
+ gBrowser.contentWindow.getSelection().removeAllRanges();
}
function testHighlight(done) {
// Update the find state so the highlight button is clickable.
gFindBar.updateControlState(Ci.nsITypeAheadFind.FIND_FOUND, false);
var eventTriggered = false;
- gFindBar.addEventListener("findhighlightallchange", function(e) {
+ once(gFindBar, "findhighlightallchange", function(e) {
eventTriggered = true;
ok(e.detail.highlightAll, "find event should have highlight all set");
e.preventDefault();
// Since we're preventing the default make sure nothing was highlighted.
SimpleTest.executeSoon(function() {
checkSelection(done);
});
});
--- a/toolkit/content/tests/chrome/findbar_window.xul
+++ b/toolkit/content/tests/chrome/findbar_window.xul
@@ -17,16 +17,19 @@
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"/>
<script type="application/javascript"><![CDATA[
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;
+ const Cu = Components.utils;
+ const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+ var { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
const SAMPLE_URL = "http://www.mozilla.org/";
const SAMPLE_TEXT = "Some text in a text field.";
const SEARCH_TEXT = "Text Test";
var gFindBar = null;
var gBrowser;
@@ -50,16 +53,19 @@
},
onBeforeLinkTraversal: function() { }
};
function ok(condition, message) {
window.opener.wrappedJSObject.SimpleTest.ok(condition, message);
}
+ function is(a, b, message) {
+ window.opener.wrappedJSObject.SimpleTest.is(a, b, message);
+ }
function finish() {
window.close();
window.opener.wrappedJSObject.SimpleTest.finish();
}
function onLoad() {
window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
@@ -78,46 +84,46 @@
setTimeout(_delayedOnLoad, 1000);
}
function _delayedOnPageShow() {
// setTimeout to the test runs after painting suppression ends
setTimeout(onPageShow, 0);
}
- function onPageShow() {
+ let onPageShow = Task.async(function* () {
testNormalFind();
gFindBar.close();
ok(gFindBar.hidden, "Failed to close findbar after testNormalFind");
testNormalFindWithComposition();
gFindBar.close();
ok(gFindBar.hidden, "findbar should be hidden after testNormalFindWithComposition");
testAutoCaseSensitivityUI();
testQuickFindText();
gFindBar.close();
ok(gFindBar.hidden, "Failed to close findbar after testQuickFindText");
testFindWithHighlight();
gFindBar.close();
ok(gFindBar.hidden, "Failed to close findbar after testFindWithHighlight");
testFindbarSelection();
testDrop();
testQuickFindLink();
- if (gHasFindClipboard)
- testStatusText(afterStatusText);
- else
- afterStatusText();
-
- function afterStatusText() {
- testFindCountUI(function() {
- gFindBar.close();
- ok(gFindBar.hidden, "Failed to close findbar after testFindCountUI");
- testQuickFindClose();
- });
+ if (gHasFindClipboard) {
+ yield testStatusText();
}
- }
+ yield testFindCountUI();
+ gFindBar.close();
+ ok(gFindBar.hidden, "Failed to close findbar after testFindCountUI");
+ yield testFindAfterCaseChanged();
+ gFindBar.close();
+ yield testFailedStringReset();
+ gFindBar.close();
+ yield testQuickFindClose();
+ finish();
+ });
function testFindbarSelection() {
function checkFindbarState(aTestName, aExpSelection) {
document.getElementById("cmd_find").doCommand();
ok(!gFindBar.hidden, "testFindbarSelection: failed to open findbar: " + aTestName);
ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField,
"testFindbarSelection: find field is not focused: " + aTestName);
if (!gHasFindClipboard) {
@@ -153,36 +159,40 @@
}
function testDrop()
{
gFindBar.open();
// use an dummy image to start the drag so it doesn't get interrupted by a selection
var img = gBrowser.contentDocument.getElementById("img");
synthesizeDrop(img, gFindBar._findField, [[ {type: "text/plain", data: "Rabbits" } ]], "copy", window);
- window.opener.wrappedJSObject.SimpleTest.is(gFindBar._findField.inputField.value, "Rabbits", "drop on findbar");
+ is(gFindBar._findField.inputField.value, "Rabbits", "drop on findbar");
gFindBar.close();
}
function testQuickFindClose() {
+ let deferred = Promise.defer();
var _isClosedCallback = function() {
ok(gFindBar.hidden,
"_isClosedCallback: Failed to auto-close quick find bar after " +
gFindBar._quickFindTimeoutLength + "ms");
- finish();
+ deferred.resolve();
};
setTimeout(_isClosedCallback, gFindBar._quickFindTimeoutLength + 100);
+ return deferred.promise;
}
- function testStatusText(aCallback) {
+ function testStatusText() {
+ let deferred = Promise.defer();
var _delayedCheckStatusText = function() {
ok(gStatusText == SAMPLE_URL, "testStatusText: Failed to set status text of found link");
- aCallback();
+ deferred.resolve();
};
setTimeout(_delayedCheckStatusText, 100);
+ return deferred.promise;
}
function enterStringIntoFindField(aString) {
for (var i=0; i < aString.length; i++) {
var event = document.createEvent("KeyEvents");
event.initKeyEvent("keypress", true, true, null, false, false,
false, false, 0, aString.charCodeAt(i));
gFindBar._findField.inputField.dispatchEvent(event);
@@ -387,16 +397,18 @@
enterStringIntoFindField(SEARCH_TEXT);
ok(gBrowser.contentWindow.getSelection() == SEARCH_TEXT,
"testQuickFindText: failed to find '" + SEARCH_TEXT + "'");
testClipboardSearchString(SEARCH_TEXT);
}
function testFindCountUI(callback) {
+ let deferred = Promise.defer();
+
clearFocus();
document.getElementById("cmd_find").doCommand();
ok(!gFindBar.hidden, "testFindCountUI: failed to open findbar");
ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField,
"testFindCountUI: find field is not focused");
let matchCase = gFindBar.getElement("find-case-sensitive");
@@ -420,19 +432,19 @@
text: "texxx",
current: 0,
total: 0
}];
let regex = /([\d]*)\sof\s([\d]*)/;
let timeout = gFindBar._matchesCountTimeoutLength + 20;
function assertMatches(aTest, aMatches) {
- window.opener.wrappedJSObject.SimpleTest.is(aMatches[1], aTest.current,
+ is(aMatches[1], aTest.current,
"Currently highlighted match should be at " + aTest.current);
- window.opener.wrappedJSObject.SimpleTest.is(aMatches[2], aTest.total,
+ is(aMatches[2], aTest.total,
"Total amount of matches should be " + aTest.total);
}
function* generatorTest() {
for (let test of tests) {
gFindBar.clear();
yield;
enterStringIntoFindField(test.text);
@@ -449,26 +461,74 @@
let current = (test.current + i - 1) % test.total + 1;
assertMatches({
current: current,
total: test.total
}, foundMatches.value.match(regex));
}
}
}
- callback();
+ deferred.resolve();
}
let test = generatorTest();
let resultListener = {
onMatchesCountResult: function() {
test.next();
}
};
gFindBar.browser.finder.addResultListener(resultListener);
test.next();
+ return deferred.promise;
+ }
+
+ // See bug 1051187.
+ function testFindAfterCaseChanged() {
+ let deferred = Promise.defer();
+ document.getElementById("cmd_find").doCommand();
+
+ // Search to set focus on "Text Test" so that searching for "t" selects first
+ // (upper case!) "T".
+ enterStringIntoFindField(SEARCH_TEXT);
+ gFindBar.clear();
+
+ let prefsvc = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ prefsvc.setIntPref("accessibility.typeaheadfind.casesensitive", 0);
+
+ enterStringIntoFindField("t");
+ is(gBrowser.contentWindow.getSelection(), "T", "First T should be selected.");
+
+ prefsvc.setIntPref("accessibility.typeaheadfind.casesensitive", 1);
+ setTimeout(function() {
+ is(gBrowser.contentWindow.getSelection(), "t", "First t should be selected.");
+ deferred.resolve();
+ }, 0);
+ return deferred.promise;
+ }
+
+ // Make sure that _findFailedString is cleared:
+ // 1. Do a search that fails with case sensitivity but matches with no case sensitivity.
+ // 2. Uncheck case sensitivity button to match the string.
+ function testFailedStringReset(aCallback) {
+ let deferred = Promise.defer();
+ document.getElementById("cmd_find").doCommand();
+
+ var prefsvc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Components.interfaces.nsIPrefBranch);
+ prefsvc.setIntPref("accessibility.typeaheadfind.casesensitive", 1);
+
+ enterStringIntoFindField(SEARCH_TEXT.toUpperCase());
+ is(gBrowser.contentWindow.getSelection(), "", "Not found.");
+
+ prefsvc.setIntPref("accessibility.typeaheadfind.casesensitive", 0);
+ setTimeout(function() {
+ is(gBrowser.contentWindow.getSelection(), SEARCH_TEXT, "Search text should be selected.");
+ deferred.resolve();
+ }, 0);
+ return deferred.resolve();
}
function testClipboardSearchString(aExpected) {
if (!gHasFindClipboard)
return;
if (!aExpected)
aExpected = "";
--- a/toolkit/content/widgets/findbar.xml
+++ b/toolkit/content/widgets/findbar.xml
@@ -533,16 +533,18 @@
- 2 - auto = case sensitive iff match string contains upper case letters
- @see _shouldBeCaseSensitive
-->
<method name="_setCaseSensitivity">
<parameter name="aCaseSensitivity"/>
<body><![CDATA[
this._typeAheadCaseSensitive = aCaseSensitivity;
this._updateCaseSensitivity();
+ this._findFailedString = null;
+ this._find();
if (this.getElement("highlight").checked)
this._setHighlightTimeout();
this._dispatchFindEvent("casesensitivitychange");
]]></body>
</method>
<field name="_strBundle">null</field>
--- a/toolkit/content/widgets/radio.xml
+++ b/toolkit/content/widgets/radio.xml
@@ -448,20 +448,20 @@
// Just clear out the parent's cached list of radio children
var control = this.control;
if (control)
control._radioChildren = null;
]]>
</constructor>
<destructor>
<![CDATA[
- if (!this.radioGroup)
+ if (!this.control)
return;
- var radioList = this.radioGroup.mRadioChildren;
+ var radioList = this.control._radioChildren;
if (!radioList)
return;
for (var i = 0; i < radioList.length; ++i) {
if (radioList[i] == this) {
radioList.splice(i, 1);
return;
}
}
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -1508,16 +1508,20 @@
addListener(this.controlsSpacer, "click", this.clickToPlayClickHandler);
addListener(this.controlsSpacer, "dblclick", this.toggleFullscreen);
addListener(this.videocontrols, "resizevideocontrols", this.adjustControlSize);
addListener(this.videocontrols, "transitionend", this.onTransitionEnd);
addListener(this.video.ownerDocument, "mozfullscreenchange", this.onFullscreenChange);
addListener(this.video, "keypress", this.keyHandler);
+ addListener(this.videocontrols, "dragstart", function(event) {
+ event.preventDefault(); //prevent dragging of controls image (bug 517114)
+ });
+
this.log("--- videocontrols initialized ---");
}
};
this.Utils.init(this);
]]>
</constructor>
<destructor>
<![CDATA[
--- a/toolkit/xre/nsSigHandlers.cpp
+++ b/toolkit/xre/nsSigHandlers.cpp
@@ -51,24 +51,26 @@ static unsigned int _gdb_sleep_duration
#include "nsStackWalk.h"
// NB: keep me up to date with the same variable in
// ipc/chromium/chrome/common/ipc_channel_posix.cc
static const int kClientChannelFd = 3;
extern "C" {
-static void PrintStackFrame(void *aPC, void *aSP, void *aClosure)
+static void PrintStackFrame(uint32_t aFrameNumber, void *aPC, void *aSP,
+ void *aClosure)
{
char buf[1024];
nsCodeAddressDetails details;
NS_DescribeCodeAddress(aPC, &details);
- NS_FormatCodeAddressDetails(aPC, &details, buf, sizeof(buf));
- fputs(buf, stdout);
+ NS_FormatCodeAddressDetails(buf, sizeof(buf), aFrameNumber, aPC, &details);
+ fprintf(stdout, "%s\n", buf);
+ fflush(stdout);
}
}
void
ah_crap_handler(int signum)
{
printf("\nProgram %s (pid = %d) received signal %d.\n",
--- a/tools/profiler/ProfilerMarkers.cpp
+++ b/tools/profiler/ProfilerMarkers.cpp
@@ -164,8 +164,23 @@ TouchDataPayload::TouchDataPayload(const
void
TouchDataPayload::streamPayloadImpl(JSStreamWriter& b)
{
b.BeginObject();
b.NameValue("x", mPoint.x);
b.NameValue("y", mPoint.y);
b.EndObject();
}
+
+VsyncPayload::VsyncPayload(mozilla::TimeStamp aVsyncTimestamp)
+ : ProfilerMarkerPayload(aVsyncTimestamp, aVsyncTimestamp, nullptr)
+ , mVsyncTimestamp(aVsyncTimestamp)
+{
+}
+
+void
+VsyncPayload::streamPayloadImpl(JSStreamWriter& b)
+{
+ b.BeginObject();
+ b.NameValue("vsync", profiler_time(mVsyncTimestamp));
+ b.NameValue("category", "VsyncTimestamp");
+ b.EndObject();
+}
--- a/tools/profiler/ProfilerMarkers.h
+++ b/tools/profiler/ProfilerMarkers.h
@@ -163,9 +163,27 @@ protected:
virtual void
streamPayload(JSStreamWriter& b) { return streamPayloadImpl(b); }
private:
void streamPayloadImpl(JSStreamWriter& b);
mozilla::ScreenIntPoint mPoint;
};
+/**
+ * Tracks when a vsync occurs according to the HardwareComposer.
+ */
+class VsyncPayload : public ProfilerMarkerPayload
+{
+public:
+ explicit VsyncPayload(mozilla::TimeStamp aVsyncTimestamp);
+ virtual ~VsyncPayload() {}
+
+protected:
+ virtual void
+ streamPayload(JSStreamWriter& b) { return streamPayloadImpl(b); }
+
+private:
+ void streamPayloadImpl(JSStreamWriter& b);
+ mozilla::TimeStamp mVsyncTimestamp;
+};
+
#endif // PROFILER_MARKERS_H
--- a/tools/profiler/TableTicker.cpp
+++ b/tools/profiler/TableTicker.cpp
@@ -538,17 +538,18 @@ void mergeStacksIntoProfile(ThreadProfil
MOZ_ASSERT(nativeIndex >= 0);
aProfile.addTag(ProfileEntry('l', (void*)aNativeStack.pc_array[nativeIndex]));
nativeIndex--;
}
}
#ifdef USE_NS_STACKWALK
static
-void StackWalkCallback(void* aPC, void* aSP, void* aClosure)
+void StackWalkCallback(uint32_t aFrameNumber, void* aPC, void* aSP,
+ void* aClosure)
{
NativeStack* nativeStack = static_cast<NativeStack*>(aClosure);
MOZ_ASSERT(nativeStack->count < nativeStack->size);
nativeStack->sp_array[nativeStack->count] = aSP;
nativeStack->pc_array[nativeStack->count] = aPC;
nativeStack->count++;
}
@@ -562,18 +563,21 @@ void TableTicker::doNativeBacktrace(Thre
void* sp_array[1000];
NativeStack nativeStack = {
pc_array,
sp_array,
mozilla::ArrayLength(pc_array),
0
};
- // Start with the current function.
- StackWalkCallback(aSample->pc, aSample->sp, &nativeStack);
+ // Start with the current function. We use 0 as the frame number here because
+ // the FramePointerStackWalk() and NS_StackWalk() calls below will use 1..N.
+ // This is a bit weird but it doesn't matter because StackWalkCallback()
+ // doesn't use the frame number argument.
+ StackWalkCallback(/* frameNumber */ 0, aSample->pc, aSample->sp, &nativeStack);
uint32_t maxFrames = uint32_t(nativeStack.size - nativeStack.count);
#ifdef XP_MACOSX
pthread_t pt = GetProfiledThread(aSample->threadProfile->GetPlatformData());
void *stackEnd = reinterpret_cast<void*>(-1);
if (pt)
stackEnd = static_cast<char*>(pthread_get_stackaddr_np(pt));
nsresult rv = NS_OK;
--- a/tools/rb/fix_linux_stack.py
+++ b/tools/rb/fix_linux_stack.py
@@ -1,26 +1,17 @@
#!/usr/bin/python
# vim:sw=4:ts=4:et:
# 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/.
-# This script uses addr2line (part of binutils) to process the output of
-# nsTraceRefcnt's Linux stack walking code. This is useful for two
-# things:
-# (1) Getting line number information out of
-# |nsTraceRefcnt::WalkTheStack|'s output in debug builds.
-# (2) Getting function names out of |nsTraceRefcnt::WalkTheStack|'s
-# output on optimized builds (where it mostly prints UNKNOWN
-# because only a handful of symbols are exported from component
-# libraries).
-#
-# Use the script by piping output containing stacks (such as raw stacks
-# or make-tree.pl balance trees) through this script.
+# This script uses addr2line (part of binutils) to post-process the entries
+# produced by NS_FormatCodeAddress(), which on Linux often lack a function
+# name, a file name and a line number.
import subprocess
import sys
import re
import os
import pty
import termios
from StringIO import StringIO
@@ -291,35 +282,30 @@ def addressToSymbol(file, address):
else:
(converter, address_adjustment, cache) = addr2lines[file]
if address in cache:
return cache[address]
result = converter.convert(hex(int(address, 16) + address_adjustment))
cache[address] = result
return result
-line_re = re.compile("^(.*) ?\[([^ ]*) \+(0x[0-9A-F]{1,8})\](.*)$")
-balance_tree_re = re.compile("^([ \|0-9-]*)(.*)$")
+# Matches lines produced by NS_FormatCodeAddress().
+line_re = re.compile("^(.*#\d+: )(.+)\[(.+) \+(0x[0-9A-Fa-f]+)\](.*)$")
def fixSymbols(line):
result = line_re.match(line)
if result is not None:
- # before allows preservation of balance trees
- # after allows preservation of counts
- (before, file, address, after) = result.groups()
+ (before, fn, file, address, after) = result.groups()
if os.path.exists(file) and os.path.isfile(file):
- # throw away the bad symbol, but keep balance tree structure
- (before, badsymbol) = balance_tree_re.match(before).groups()
-
(name, fileline) = addressToSymbol(file, address)
# If addr2line gave us something useless, keep what we had before.
if name == "??":
- name = badsymbol
+ name = fn
if fileline == "??:0" or fileline == "??:?":
fileline = file
nl = '\n' if line[-1] == '\n' else ''
return "%s%s (%s)%s%s" % (before, name, fileline, after, nl)
else:
sys.stderr.write("Warning: File \"" + file + "\" does not exist.\n")
return line
--- a/tools/rb/fix_macosx_stack.py
+++ b/tools/rb/fix_macosx_stack.py
@@ -1,24 +1,17 @@
#!/usr/bin/python
# vim:sw=4:ts=4:et:
# 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/.
-# This script uses atos to process the output of nsTraceRefcnt's Mac OS
-# X stack walking code. This is useful for two things:
-# (1) Getting line number information out of
-# |nsTraceRefcnt::WalkTheStack|'s output in debug builds.
-# (2) Getting function names out of |nsTraceRefcnt::WalkTheStack|'s
-# output on all builds (where it mostly prints UNKNOWN because only
-# a handful of symbols are exported from component libraries).
-#
-# Use the script by piping output containing stacks (such as raw stacks
-# or make-tree.pl balance trees) through this script.
+# This script uses |atos| to post-process the entries produced by
+# NS_FormatCodeAddress(), which on Mac often lack a file name and a line
+# number.
import subprocess
import sys
import re
import os
import pty
import termios
@@ -94,26 +87,24 @@ def cxxfilt(sym):
globals()["cxxfilt_proc"] = subprocess.Popen(['c++filt',
'--no-strip-underscores',
'--format', 'gnu-v3'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
cxxfilt_proc.stdin.write(sym + "\n")
return cxxfilt_proc.stdout.readline().rstrip("\n")
-line_re = re.compile("^(.*) ?\[([^ ]*) \+(0x[0-9a-fA-F]{1,8})\](.*)$")
-balance_tree_re = re.compile("^([ \|0-9-]*)")
+# Matches lines produced by NS_FormatCodeAddress().
+line_re = re.compile("^(.*#\d+: )(.+)\[(.+) \+(0x[0-9A-Fa-f]+)\](.*)$")
atos_name_re = re.compile("^(.+) \(in ([^)]+)\) \((.+)\)$")
def fixSymbols(line):
result = line_re.match(line)
if result is not None:
- # before allows preservation of balance trees
- # after allows preservation of counts
- (before, file, address, after) = result.groups()
+ (before, fn, file, address, after) = result.groups()
address = int(address, 16)
if os.path.exists(file) and os.path.isfile(file):
address += address_adjustment(file)
info = addressToSymbol(file, address)
# atos output seems to have three forms:
# address
@@ -124,19 +115,16 @@ def fixSymbols(line):
# Print the first two forms as-is, and transform the third
(name, library, fileline) = name_result.groups()
# atos demangles, but occasionally it fails. cxxfilt can mop
# up the remaining cases(!), which will begin with '_Z'.
if (name.startswith("_Z")):
name = cxxfilt(name)
info = "%s (%s, in %s)" % (name, fileline, library)
- # throw away the bad symbol, but keep balance tree structure
- before = balance_tree_re.match(before).groups()[0]
-
nl = '\n' if line[-1] == '\n' else ''
return before + info + after + nl
else:
sys.stderr.write("Warning: File \"" + file + "\" does not exist.\n")
return line
else:
return line
--- a/tools/rb/fix_stack_using_bpsyms.py
+++ b/tools/rb/fix_stack_using_bpsyms.py
@@ -1,14 +1,18 @@
#!/usr/bin/env python
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+# This script uses breakpad symbols to post-process the entries produced by
+# NS_FormatCodeAddress(), which on TBPL builds often lack a file name and a
+# line number (and on Linux even the symbol is often bad).
+
from __future__ import with_statement
import sys
import os
import re
import bisect
def prettyFileName(name):
@@ -107,28 +111,24 @@ def getSymbolFile(file, symbolsDir):
def addressToSymbol(file, address, symbolsDir):
p = getSymbolFile(file, symbolsDir)
if p:
return p.addrToSymbol(address)
else:
return ""
-line_re = re.compile("^(.*) ?\[([^ ]*) \+(0x[0-9A-F]{1,16})\](.*)$")
-balance_tree_re = re.compile("^([ \|0-9-]*)")
+# Matches lines produced by NS_FormatCodeAddress().
+line_re = re.compile("^(.*#\d+: )(.+)\[(.+) \+(0x[0-9A-Fa-f]+)\](.*)$")
def fixSymbols(line, symbolsDir):
result = line_re.match(line)
if result is not None:
- # before allows preservation of balance trees
- # after allows preservation of counts
- (before, file, address, after) = result.groups()
+ (before, fn, file, address, after) = result.groups()
address = int(address, 16)
- # throw away the bad symbol, but keep balance tree structure
- before = balance_tree_re.match(before).groups()[0]
symbol = addressToSymbol(file, address, symbolsDir)
if not symbol:
symbol = "%s + 0x%x" % (os.path.basename(file), address)
return before + symbol + after + "\n"
else:
return line
if __name__ == "__main__":
--- a/tools/rb/make-tree.pl
+++ b/tools/rb/make-tree.pl
@@ -92,21 +92,26 @@ sub read_data($$$) {
my $sno = shift(@fields);
next LINE unless ($obj eq $::opt_object);
my $op = shift(@fields);
next LINE unless ($op eq $plus || $op eq $minus);
my $cnt = shift(@fields);
- # Collect the remaining lines to create a stack trace.
+ # Collect the remaining lines to create a stack trace. We need to
+ # filter out the frame numbers so that frames that differ only in
+ # their frame number are considered equivalent. However, we need to
+ # keep a frame number on each line so that the fix*.py scripts can
+ # parse the output. So we set the frame number to 0 for every frame.
my @stack;
CALLSITE: while (<$INFILE>) {
chomp;
last CALLSITE if (/^$/);
+ $_ =~ s/#\d+: /#00: /; # replace frame number with 0
$stack[++$#stack] = $_;
}
# Reverse the remaining fields to produce the call stack, with the
# oldest frame at the front of the array.
if (! $::opt_reverse) {
@stack = reverse(@stack);
}
--- a/tools/trace-malloc/lib/nsTraceMalloc.c
+++ b/tools/trace-malloc/lib/nsTraceMalloc.c
@@ -888,17 +888,17 @@ calltree(void **stack, size_t num_stack_
return NULL;
}
/*
* Buffer the stack from top at low index to bottom at high, so that we can
* reverse it in calltree.
*/
static void
-stack_callback(void *pc, void *sp, void *closure)
+stack_callback(uint32_t frameNumber, void *pc, void *sp, void *closure)
{
stack_buffer_info *info = (stack_buffer_info*) closure;
/*
* If we run out of buffer, keep incrementing entries so that
* backtrace can call us again with a bigger buffer.
*/
if (info->entries < info->size)
--- a/widget/gonk/HwcComposer2D.cpp
+++ b/widget/gonk/HwcComposer2D.cpp
@@ -26,16 +26,21 @@
#include "mozilla/layers/PLayerTransaction.h"
#include "mozilla/layers/ShadowLayerUtilsGralloc.h"
#include "mozilla/layers/TextureHostOGL.h" // for TextureHostOGL
#include "mozilla/StaticPtr.h"
#include "cutils/properties.h"
#include "gfx2DGlue.h"
#include "GeckoTouchDispatcher.h"
+#ifdef MOZ_ENABLE_PROFILER_SPS
+#include "GeckoProfiler.h"
+#include "ProfilerMarkers.h"
+#endif
+
#if ANDROID_VERSION >= 17
#include "libdisplay/FramebufferSurface.h"
#include "gfxPrefs.h"
#include "nsThreadUtils.h"
#ifndef HWC_BLIT
#define HWC_BLIT (HWC_FRAMEBUFFER_TARGET + 1)
#endif
@@ -65,16 +70,18 @@
using namespace android;
using namespace mozilla::gfx;
using namespace mozilla::layers;
namespace mozilla {
#if ANDROID_VERSION >= 17
+nsecs_t sAndroidInitTime = 0;
+mozilla::TimeStamp sMozInitTime;
static void
HookInvalidate(const struct hwc_procs* aProcs)
{
HwcComposer2D::GetInstance()->Invalidate();
}
static void
HookVsync(const struct hwc_procs* aProcs, int aDisplay,
@@ -149,16 +156,18 @@ HwcComposer2D::Init(hwc_display_t dpy, h
mRBSwapSupport = !!supported;
}
} else {
mColorFill = false;
mRBSwapSupport = false;
}
if (RegisterHwcEventCallback()) {
+ sAndroidInitTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ sMozInitTime = TimeStamp::Now();
EnableVsync(true);
}
#else
char propValue[PROPERTY_VALUE_MAX];
property_get("ro.display.colorfill", propValue, "0");
mColorFill = (atoi(propValue) == 1) ? true : false;
mRBSwapSupport = true;
#endif
@@ -224,19 +233,27 @@ HwcComposer2D::RunVsyncEventControl(bool
HwcDevice* device = (HwcDevice*)GetGonkDisplay()->GetHWCDevice();
if (device && device->eventControl) {
device->eventControl(device, HWC_DISPLAY_PRIMARY, HWC_EVENT_VSYNC, aEnable);
}
}
}
void
-HwcComposer2D::Vsync(int aDisplay, int64_t aTimestamp)
+HwcComposer2D::Vsync(int aDisplay, nsecs_t aVsyncTimestamp)
{
- GeckoTouchDispatcher::NotifyVsync(aTimestamp);
+#ifdef MOZ_ENABLE_PROFILER_SPS
+ if (profiler_is_active()) {
+ nsecs_t timeSinceInit = aVsyncTimestamp - sAndroidInitTime;
+ TimeStamp vsyncTime = sMozInitTime + TimeDuration::FromMicroseconds(timeSinceInit / 1000);
+ CompositorParent::PostInsertVsyncProfilerMarker(vsyncTime);
+ }
+#endif
+
+ GeckoTouchDispatcher::NotifyVsync(aVsyncTimestamp);
}
// Called on the "invalidator" thread (run from HAL).
void
HwcComposer2D::Invalidate()
{
if (!Initialized()) {
LOGE("HwcComposer2D::Invalidate failed!");
--- a/xpcom/base/CodeAddressService.h
+++ b/xpcom/base/CodeAddressService.h
@@ -84,22 +84,19 @@ class CodeAddressService
void Replace(const void* aPc, const char* aFunction,
const char* aLibrary, ptrdiff_t aLOffset,
const char* aFileName, unsigned long aLineNo)
{
mPc = aPc;
// Convert "" to nullptr. Otherwise, make a copy of the name.
StringAlloc::free(mFunction);
- mFunction =
- !aFunction[0] ? nullptr : StringAlloc::copy(aFunction);
+ mFunction = !aFunction[0] ? nullptr : StringAlloc::copy(aFunction);
StringAlloc::free(mFileName);
- mFileName =
- !aFileName[0] ? nullptr : StringAlloc::copy(aFileName);
-
+ mFileName = !aFileName[0] ? nullptr : StringAlloc::copy(aFileName);
mLibrary = aLibrary;
mLOffset = aLOffset;
mLineNo = aLineNo;
mInUse = 1;
}
@@ -127,17 +124,18 @@ class CodeAddressService
size_t mNumCacheMisses;
public:
CodeAddressService()
: mEntries(), mNumCacheHits(0), mNumCacheMisses(0)
{
}
- void GetLocation(const void* aPc, char* aBuf, size_t aBufLen)
+ void GetLocation(uint32_t aFrameNumber, const void* aPc, char* aBuf,
+ size_t aBufLen)
{
MOZ_ASSERT(DescribeCodeAddressLock::IsLocked());
uint32_t index = HashGeneric(aPc) & kMask;
MOZ_ASSERT(index < kNumEntries);
Entry& entry = mEntries[index];
if (!entry.mInUse || entry.mPc != aPc) {
@@ -160,36 +158,19 @@ public:
details.filename, details.lineno);
} else {
mNumCacheHits++;
}
MOZ_ASSERT(entry.mPc == aPc);
- uintptr_t entryPc = (uintptr_t)(entry.mPc);
- // Sometimes we get nothing useful. Just print "???" for the entire entry
- // so that fix_linux_stack.py doesn't complain about an empty filename.
- if (!entry.mFunction && !entry.mLibrary[0] && entry.mLOffset == 0) {
- snprintf(aBuf, aBufLen, "??? 0x%" PRIxPTR, entryPc);
- } else {
- // Use "???" for unknown functions.
- const char* entryFunction = entry.mFunction ? entry.mFunction : "???";
- if (entry.mFileName) {
- // On Windows we can get the filename and line number at runtime.
- snprintf(aBuf, aBufLen, "%s (%s:%u) 0x%" PRIxPTR,
- entryFunction, entry.mFileName, entry.mLineNo, entryPc);
- } else {
- // On Linux and Mac we cannot get the filename and line number at
- // runtime, so we print the offset in a form that fix_linux_stack.py and
- // fix_macosx_stack.py can post-process.
- snprintf(aBuf, aBufLen, "%s[%s +0x%" PRIXPTR "] 0x%" PRIxPTR,
- entryFunction, entry.mLibrary, entry.mLOffset, entryPc);
- }
- }
+ NS_FormatCodeAddress(aBuf, aBufLen, aFrameNumber, entry.mPc,
+ entry.mFunction, entry.mLibrary, entry.mLOffset,
+ entry.mFileName, entry.mLineNo);
}
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
size_t n = aMallocSizeOf(this);
for (uint32_t i = 0; i < kNumEntries; i++) {
n += mEntries[i].SizeOfExcludingThis(aMallocSizeOf);
}
--- a/xpcom/base/nsIMemoryInfoDumper.idl
+++ b/xpcom/base/nsIMemoryInfoDumper.idl
@@ -54,89 +54,39 @@ interface nsIMemoryInfoDumper : nsISuppo
* @param aFilename The output file.
*
* @param aFinishDumping The callback called on completion.
*
* @param aFinishDumpingData The environment for the callback.
*
* @param aAnonymize Should the reports be anonymized?
*
- * Sample output:
+ * Sample output, annotated with comments for explanatory purposes.
*
* {
+ * // The version number of the format, which will be incremented each time
+ * // backwards-incompatible changes are made. A mandatory integer.
* "version": 1
+ *
+ * // Equal to nsIMemoryReporterManager::hasMozMallocUsableSize. A
+ * // mandatory boolean.
* "hasMozMallocUsableSize": true,
+ *
+ * // The memory reports. A mandatory array.
* "reports": [
+ * // The properties correspond to the arguments of
+ * // nsIHandleReportCallback::callback. Every one is mandatory.
* {"process":"Main Process (pid 12345)", "path":"explicit/foo/bar",
* "kind":1, "units":0, "amount":2000000, "description":"Foo bar."},
* {"process":"Main Process (pid 12345)", "path":"heap-allocated",
* "kind":1, "units":0, "amount":3000000, "description":"Heap allocated."},
* {"process":"Main Process (pid 12345)", "path":"vsize",
* "kind":1, "units":0, "amount":10000000, "description":"Vsize."}
* ]
* }
- *
- * JSON schema for the output.
- *
- * {
- * "properties": {
- * "version": {
- * "type": "integer",
- * "description": "Version number of this schema.",
- * "required": true
- * },
- * "hasMozMallocUsableSize": {
- * "type": "boolean",
- * "description": "nsIMemoryReporterManager::hasMozMallocUsableSize",
- * "required": true
- * },
- * "reports": {
- * "type": "array",
- * "description": "The memory reports.",
- * "required": true
- * "minItems": 1,
- * "items": {
- * "type": "object",
- * "properties": {
- * "process": {
- * "type": "string",
- * "description": "nsIMemoryReporter::process",
- * "required": true
- * },
- * "path": {
- * "type": "string",
- * "description": "nsIMemoryReporter::path",
- * "required": true,
- * "minLength": 1
- * },
- * "kind": {
- * "type": "integer",
- * "description": "nsIMemoryReporter::kind",
- * "required": true
- * },
- * "units": {
- * "type": "integer",
- * "description": "nsIMemoryReporter::units",
- * "required": true
- * },
- * "amount": {
- * "type": "integer",
- * "description": "nsIMemoryReporter::amount",
- * "required": true
- * },
- * "description": {
- * "type": "string",
- * "description": "nsIMemoryReporter::description",
- * "required": true
- * }
- * }
- * }
- * }
- * }
- * }
*/
void dumpMemoryReportsToNamedFile(in AString aFilename,
in nsIFinishDumpingCallback aFinishDumping,
in nsISupports aFinishDumpingData,
in boolean aAnonymize);
/**
* Similar to dumpMemoryReportsToNamedFile, this method dumps gzipped memory
--- a/xpcom/base/nsMemoryInfoDumper.cpp
+++ b/xpcom/base/nsMemoryInfoDumper.cpp
@@ -425,38 +425,16 @@ MakeFilename(const char* aPrefix, const
int aPid, const char* aSuffix, nsACString& aResult)
{
aResult = nsPrintfCString("%s-%s-%d.%s",
aPrefix,
NS_ConvertUTF16toUTF8(aIdentifier).get(),
aPid, aSuffix);
}
-#ifdef MOZ_DMD
-struct DMDWriteState
-{
- static const size_t kBufSize = 4096;
- char mBuf[kBufSize];
- nsRefPtr<nsGZFileWriter> mGZWriter;
-
- DMDWriteState(nsGZFileWriter* aGZWriter)
- : mGZWriter(aGZWriter)
- {
- }
-};
-
-static void
-DMDWrite(void* aState, const char* aFmt, va_list ap)
-{
- DMDWriteState* state = (DMDWriteState*)aState;
- vsnprintf(state->mBuf, state->kBufSize, aFmt, ap);
- unused << state->mGZWriter->Write(state->mBuf);
-}
-#endif
-
// This class wraps GZFileWriter so it can be used with JSONWriter, overcoming
// the following two problems:
// - It provides a JSONWriterFunc::Write() that calls nsGZFileWriter::Write().
// - It can be stored as a UniquePtr, whereas nsGZFileWriter is refcounted.
class GZWriterWrapper : public JSONWriteFunc
{
public:
explicit GZWriterWrapper(nsGZFileWriter* aGZWriter)
@@ -786,20 +764,20 @@ nsresult
nsMemoryInfoDumper::OpenDMDFile(const nsAString& aIdentifier, int aPid,
FILE** aOutFile)
{
if (!dmd::IsRunning()) {
*aOutFile = nullptr;
return NS_OK;
}
- // Create a filename like dmd-<identifier>-<pid>.txt.gz, which will be used
+ // Create a filename like dmd-<identifier>-<pid>.json.gz, which will be used
// if DMD is enabled.
nsCString dmdFilename;
- MakeFilename("dmd", aIdentifier, aPid, "txt.gz", dmdFilename);
+ MakeFilename("dmd", aIdentifier, aPid, "json.gz", dmdFilename);
// Open a new DMD file named |dmdFilename| in NS_OS_TEMP_DIR for writing,
// and dump DMD output to it. This must occur after the memory reporters
// have been run (above), but before the memory-reports file has been
// renamed (so scripts can detect the DMD file, if present).
nsresult rv;
nsCOMPtr<nsIFile> dmdFile;
@@ -821,25 +799,24 @@ nsMemoryInfoDumper::OpenDMDFile(const ns
dmd::StatusMsg("opened %s for writing\n", path.get());
return rv;
}
nsresult
nsMemoryInfoDumper::DumpDMDToFile(FILE* aFile)
{
- nsRefPtr<nsGZFileWriter> dmdWriter = new nsGZFileWriter();
- nsresult rv = dmdWriter->InitANSIFileDesc(aFile);
+ nsRefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter();
+ nsresult rv = gzWriter->InitANSIFileDesc(aFile);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Dump DMD's memory reports analysis to the file.
- DMDWriteState state(dmdWriter);
- dmd::Writer w(DMDWrite, &state);
- dmd::AnalyzeReports(w);
+ JSONWriter jsonWriter(MakeUnique<GZWriterWrapper>(gzWriter));
+ dmd::AnalyzeReports(jsonWriter);
- rv = dmdWriter->Finish();
+ rv = gzWriter->Finish();
NS_WARN_IF(NS_FAILED(rv));
return rv;
}
#endif // MOZ_DMD
--- a/xpcom/base/nsStackWalk.cpp
+++ b/xpcom/base/nsStackWalk.cpp
@@ -8,16 +8,20 @@
#include "mozilla/Assertions.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/StackWalk.h"
#include "nsStackWalkPrivate.h"
#include "nsStackWalk.h"
+#ifdef XP_WIN
+#define snprintf _snprintf
+#endif
+
using namespace mozilla;
// The presence of this address is the stack must stop the stack walk. If
// there is no such address, the structure will be {nullptr, true}.
struct CriticalAddress
{
void* mAddr;
bool mInit;
@@ -49,17 +53,17 @@ static CriticalAddress gCriticalAddress;
typedef void
malloc_logger_t(uint32_t aType,
uintptr_t aArg1, uintptr_t aArg2, uintptr_t aArg3,
uintptr_t aResult, uint32_t aNumHotFramesToSkip);
extern malloc_logger_t* malloc_logger;
static void
-stack_callback(void* aPc, void* aSp, void* aClosure)
+stack_callback(uint32_t aFrameNumber, void* aPc, void* aSp, void* aClosure)
{
const char* name = static_cast<char*>(aClosure);
Dl_info info;
// On Leopard dladdr returns the wrong value for "new_sem_from_pool". The
// stack shows up as having two pthread_cond_wait$UNIX2003 frames. The
// correct one is the first that we find on our way up, so the
// following check for gCriticalAddress.mAddr is critical.
@@ -607,17 +611,17 @@ NS_StackWalk(NS_WalkStackCallback aCallb
::CloseHandle(data.eventStart);
::CloseHandle(data.eventEnd);
}
::CloseHandle(myThread);
for (uint32_t i = 0; i < data.pc_count; ++i) {
- (*aCallback)(data.pcs[i], data.sps[i], aClosure);
+ (*aCallback)(i + 1, data.pcs[i], data.sps[i], aClosure);
}
return data.pc_count == 0 ? NS_ERROR_FAILURE : NS_OK;
}
static BOOL CALLBACK
callbackEspecial64(
@@ -822,48 +826,16 @@ NS_DescribeCodeAddress(void* aPC, nsCode
sizeof(aDetails->function));
aDetails->foffset = static_cast<ptrdiff_t>(displacement);
}
LeaveCriticalSection(&gDbgHelpCS); // release our lock
return NS_OK;
}
-EXPORT_XPCOM_API(nsresult)
-NS_FormatCodeAddressDetails(void* aPC, const nsCodeAddressDetails* aDetails,
- char* aBuffer, uint32_t aBufferSize)
-{
- if (aDetails->function[0]) {
- _snprintf(aBuffer, aBufferSize, "%s+0x%08lX [%s +0x%016lX]",
- aDetails->function, aDetails->foffset,
- aDetails->library, aDetails->loffset);
- } else if (aDetails->library[0]) {
- _snprintf(aBuffer, aBufferSize, "UNKNOWN [%s +0x%016lX]",
- aDetails->library, aDetails->loffset);
- } else {
- _snprintf(aBuffer, aBufferSize, "UNKNOWN 0x%016lX", aPC);
- }
-
- aBuffer[aBufferSize - 1] = '\0';
-
- uint32_t len = strlen(aBuffer);
- if (aDetails->filename[0]) {
- _snprintf(aBuffer + len, aBufferSize - len, " (%s, line %d)\n",
- aDetails->filename, aDetails->lineno);
- } else {
- aBuffer[len] = '\n';
- if (++len != aBufferSize) {
- aBuffer[len] = '\0';
- }
- }
- aBuffer[aBufferSize - 2] = '\n';
- aBuffer[aBufferSize - 1] = '\0';
- return NS_OK;
-}
-
// i386 or PPC Linux stackwalking code
#elif HAVE_DLADDR && (HAVE__UNWIND_BACKTRACE || NSSTACKWALK_SUPPORTS_LINUX || NSSTACKWALK_SUPPORTS_MACOSX)
#include <stdlib.h>
#include <string.h>
#include "nscore.h"
#include <stdio.h>
#include "plstr.h"
@@ -942,18 +914,18 @@ FramePointerStackWalk(NS_WalkStackCallba
if (IsCriticalAddress(pc)) {
return NS_ERROR_UNEXPECTED;
}
if (--skip < 0) {
// Assume that the SP points to the BP of the function
// it called. We can't know the exact location of the SP
// but this should be sufficient for our use the SP
// to order elements on the stack.
- (*aCallback)(pc, bp, aClosure);
numFrames++;
+ (*aCallback)(numFrames, pc, bp, aClosure);
if (aMaxFrames != 0 && numFrames == aMaxFrames) {
break;
}
}
bp = next;
}
return numFrames == 0 ? NS_ERROR_FAILURE : NS_OK;
}
@@ -986,17 +958,16 @@ NS_StackWalk(NS_WalkStackCallback aCallb
void* stackEnd;
#if HAVE___LIBC_STACK_END
stackEnd = __libc_stack_end;
#else
stackEnd = reinterpret_cast<void*>(-1);
#endif
return FramePointerStackWalk(aCallback, aSkipFrames, aMaxFrames,
aClosure, bp, stackEnd);
-
}
#elif defined(HAVE__UNWIND_BACKTRACE)
// libgcc_s.so symbols _Unwind_Backtrace@@GCC_3.3 and _Unwind_GetIP@@GCC_3.0
#include <unwind.h>
struct unwind_info
@@ -1018,18 +989,18 @@ unwind_callback(struct _Unwind_Context*
if (IsCriticalAddress(pc)) {
info->isCriticalAbort = true;
// We just want to stop the walk, so any error code will do. Using
// _URC_NORMAL_STOP would probably be the most accurate, but it is not
// defined on Android for ARM.
return _URC_FOREIGN_EXCEPTION_CAUGHT;
}
if (--info->skip < 0) {
- (*info->callback)(pc, nullptr, info->closure);
info->numFrames++;
+ (*info->callback)(info->numFrames, pc, nullptr, info->closure);
if (info->maxFrames != 0 && info->numFrames == info->maxFrames) {
// Again, any error code that stops the walk will do.
return _URC_FOREIGN_EXCEPTION_CAUGHT;
}
}
return _URC_NO_REASON;
}
@@ -1097,34 +1068,16 @@ NS_DescribeCodeAddress(void* aPC, nsCode
// Just use the mangled symbol if demangling failed.
PL_strncpyz(aDetails->function, symbol, sizeof(aDetails->function));
}
aDetails->foffset = (char*)aPC - (char*)info.dli_saddr;
return NS_OK;
}
-EXPORT_XPCOM_API(nsresult)
-NS_FormatCodeAddressDetails(void* aPC, const nsCodeAddressDetails* aDetails,
- char* aBuffer, uint32_t aBufferSize)
-{
- if (!aDetails->library[0]) {
- snprintf(aBuffer, aBufferSize, "UNKNOWN %p\n", aPC);
- } else if (!aDetails->function[0]) {
- snprintf(aBuffer, aBufferSize, "UNKNOWN [%s +0x%08" PRIXPTR "]\n",
- aDetails->library, aDetails->loffset);
- } else {
- snprintf(aBuffer, aBufferSize, "%s+0x%08" PRIXPTR
- " [%s +0x%08" PRIXPTR "]\n",
- aDetails->function, aDetails->foffset,
- aDetails->library, aDetails->loffset);
- }
- return NS_OK;
-}
-
#else // unsupported platform.
EXPORT_XPCOM_API(nsresult)
NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames,
uint32_t aMaxFrames, void* aClosure, uintptr_t aThread,
void* aPlatformData)
{
MOZ_ASSERT(!aThread);
@@ -1148,17 +1101,49 @@ NS_DescribeCodeAddress(void* aPC, nsCode
aDetails->loffset = 0;
aDetails->filename[0] = '\0';
aDetails->lineno = 0;
aDetails->function[0] = '\0';
aDetails->foffset = 0;
return NS_ERROR_NOT_IMPLEMENTED;
}
-EXPORT_XPCOM_API(nsresult)
-NS_FormatCodeAddressDetails(void* aPC, const nsCodeAddressDetails* aDetails,
- char* aBuffer, uint32_t aBufferSize)
+#endif
+
+EXPORT_XPCOM_API(void)
+NS_FormatCodeAddressDetails(char* aBuffer, uint32_t aBufferSize,
+ uint32_t aFrameNumber, void* aPC,
+ const nsCodeAddressDetails* aDetails)
{
- aBuffer[0] = '\0';
- return NS_ERROR_NOT_IMPLEMENTED;
+ NS_FormatCodeAddress(aBuffer, aBufferSize,
+ aFrameNumber, aPC, aDetails->function,
+ aDetails->library, aDetails->loffset,
+ aDetails->filename, aDetails->lineno);
}
-#endif
+EXPORT_XPCOM_API(void)
+NS_FormatCodeAddress(char* aBuffer, uint32_t aBufferSize, uint32_t aFrameNumber,
+ const void* aPC, const char* aFunction,
+ const char* aLibrary, ptrdiff_t aLOffset,
+ const char* aFileName, uint32_t aLineNo)
+{
+ const char* function = aFunction && aFunction[0] ? aFunction : "???";
+ if (aFileName && aFileName[0]) {
+ // We have a filename and (presumably) a line number. Use them.
+ snprintf(aBuffer, aBufferSize,
+ "#%02u: %s (%s:%u)",
+ aFrameNumber, function, aFileName, aLineNo);
+ } else if (aLibrary && aLibrary[0]) {
+ // We have no filename, but we do have a library name. Use it and the
+ // library offset, and print them in a way that scripts like
+ // fix_{linux,macosx}_stacks.py can easily post-process.
+ snprintf(aBuffer, aBufferSize,
+ "#%02u: %s[%s +0x%" PRIxPTR "]",
+ aFrameNumber, function, aLibrary, aLOffset);
+ } else {
+ // We have nothing useful to go on. (The format string is split because
+ // '??)' is a trigraph and causes a warning, sigh.)
+ snprintf(aBuffer, aBufferSize,
+ "#%02u: ??? (???:???" ")",
+ aFrameNumber);
+ }
+}
+
--- a/xpcom/base/nsStackWalk.h
+++ b/xpcom/base/nsStackWalk.h
@@ -13,21 +13,30 @@
#include "nscore.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
-// aSP will be the best approximation possible of what the stack pointer will be
-// pointing to when the execution returns to executing that at that PC.
-// If no approximation can be made it will be nullptr.
+/**
+ * The callback for NS_StackWalk.
+ *
+ * @param aFrameNumber The frame number (starts at 1, not 0).
+ * @param aPC The program counter value.
+ * @param aSP The best approximation possible of what the stack
+ * pointer will be pointing to when the execution returns
+ * to executing that at aPC. If no approximation can
+ * be made it will be nullptr.
+ * @param aClosure Extra data passed in via NS_StackWalk().
+ */
typedef void
-(*NS_WalkStackCallback)(void* aPC, void* aSP, void* aClosure);
+(*NS_WalkStackCallback)(uint32_t aFrameNumber, void* aPC, void* aSP,
+ void* aClosure);
/**
* Call aCallback for the C/C++ stack frames on the current thread, from
* the caller of NS_StackWalk to main (or above).
*
* @param aCallback Callback function, called once per frame.
* @param aSkipFrames Number of initial frames to skip. 0 means that
* the first callback will be for the caller of
@@ -104,27 +113,58 @@ NS_DescribeCodeAddress(void* aPC, nsCode
/**
* Format the information about a code address in a format suitable for
* stack traces on the current platform. When available, this string
* should contain the function name, source file, and line number. When
* these are not available, library and offset should be reported, if
* possible.
*
- * @param aPC The code address.
- * @param aDetails The value filled in by NS_DescribeCodeAddress(aPC).
- * @param aBuffer A string to be filled in with the description.
- * The string will always be null-terminated.
- * @param aBufferSize The size, in bytes, of aBuffer, including
- * room for the terminating null. If the information
- * to be printed would be larger than aBuffer, it
- * will be truncated so that aBuffer[aBufferSize-1]
- * is the terminating null.
+ * Note that this output is parsed by several scripts including the fix*.py and
+ * make-tree.pl scripts in tools/rb/. It should only be change with care, and
+ * in conjunction with those scripts.
+ *
+ * @param aBuffer A string to be filled in with the description.
+ * The string will always be null-terminated.
+ * @param aBufferSize The size, in bytes, of aBuffer, including
+ * room for the terminating null. If the information
+ * to be printed would be larger than aBuffer, it
+ * will be truncated so that aBuffer[aBufferSize-1]
+ * is the terminating null.
+ * @param aFrameNumber The frame number.
+ * @param aPC The code address.
+ * @param aFunction The function name. Possibly null or the empty string.
+ * @param aLibrary The library name. Possibly null or the empty string.
+ * @param aLOffset The library offset.
+ * @param aFileName The filename. Possibly null or the empty string.
+ * @param aLineNo The line number. Possibly zero.
*/
-XPCOM_API(nsresult)
-NS_FormatCodeAddressDetails(void* aPC, const nsCodeAddressDetails* aDetails,
- char* aBuffer, uint32_t aBufferSize);
+XPCOM_API(void)
+NS_FormatCodeAddress(char* aBuffer, uint32_t aBufferSize, uint32_t aFrameNumber,
+ const void* aPC, const char* aFunction,
+ const char* aLibrary, ptrdiff_t aLOffset,
+ const char* aFileName, uint32_t aLineNo);
+
+/**
+ * Format the information about a code address in the same fashion as
+ * NS_FormatCodeAddress.
+ *
+ * @param aBuffer A string to be filled in with the description.
+ * The string will always be null-terminated.
+ * @param aBufferSize The size, in bytes, of aBuffer, including
+ * room for the terminating null. If the information
+ * to be printed would be larger than aBuffer, it
+ * will be truncated so that aBuffer[aBufferSize-1]
+ * is the terminating null.
+ * @param aFrameNumber The frame number.
+ * @param aPC The code address.
+ * @param aDetails The value filled in by NS_DescribeCodeAddress(aPC).
+ */
+XPCOM_API(void)
+NS_FormatCodeAddressDetails(char* aBuffer, uint32_t aBufferSize,
+ uint32_t aFrameNumber, void* aPC,
+ const nsCodeAddressDetails* aDetails);
#ifdef __cplusplus
}
#endif
#endif /* !defined(nsStackWalk_h_) */
--- a/xpcom/base/nsTraceRefcnt.cpp
+++ b/xpcom/base/nsTraceRefcnt.cpp
@@ -924,35 +924,38 @@ InitTraceLog()
}
#endif
extern "C" {
#ifdef MOZ_STACKWALKING
static void
-PrintStackFrame(void* aPC, void* aSP, void* aClosure)
+PrintStackFrame(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure)
{
FILE* stream = (FILE*)aClosure;
nsCodeAddressDetails details;
char buf[1024];
NS_DescribeCodeAddress(aPC, &details);
- NS_FormatCodeAddressDetails(aPC, &details, buf, sizeof(buf));
- fputs(buf, stream);
+ NS_FormatCodeAddressDetails(buf, sizeof(buf), aFrameNumber, aPC, &details);
+ fprintf(stream, "%s\n", buf);
+ fflush(stream);
}
static void
-PrintStackFrameCached(void* aPC, void* aSP, void* aClosure)
+PrintStackFrameCached(uint32_t aFrameNumber, void* aPC, void* aSP,
+ void* aClosure)
{
auto stream = static_cast<FILE*>(aClosure);
static const size_t buflen = 1024;
char buf[buflen];
- gCodeAddressService->GetLocation(aPC, buf, buflen);
+ gCodeAddressService->GetLocation(aFrameNumber, aPC, buf, buflen);
fprintf(stream, " %s\n", buf);
+ fflush(stream);
}
#endif
}
void
nsTraceRefcnt::WalkTheStack(FILE* aStream)
{
--- a/xpcom/build/LateWriteChecks.cpp
+++ b/xpcom/build/LateWriteChecks.cpp
@@ -74,17 +74,17 @@ public:
mFile = nullptr;
}
private:
FILE* mFile;
SHA1Sum mSHA1;
};
static void
-RecordStackWalker(void* aPC, void* aSP, void* aClosure)
+RecordStackWalker(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure)
{
std::vector<uintptr_t>* stack =
static_cast<std::vector<uintptr_t>*>(aClosure);
stack->push_back(reinterpret_cast<uintptr_t>(aPC));
}
/**************************** Late-Write Observer ****************************/
--- a/xpcom/glue/BlockingResourceBase.cpp
+++ b/xpcom/glue/BlockingResourceBase.cpp
@@ -43,17 +43,18 @@ const char* const BlockingResourceBase::
#ifdef DEBUG
PRCallOnceType BlockingResourceBase::sCallOnce;
unsigned BlockingResourceBase::sResourceAcqnChainFrontTPI = (unsigned)-1;
BlockingResourceBase::DDT* BlockingResourceBase::sDeadlockDetector;
void
-BlockingResourceBase::StackWalkCallback(void* aPc, void* aSp, void* aClosure)
+BlockingResourceBase::StackWalkCallback(uint32_t aFrameNumber, void* aPc,
+ void* aSp, void* aClosure)
{
#ifndef MOZ_CALLSTACK_DISABLED
AcquisitionState* state = (AcquisitionState*)aClosure;
state->AppendElement(aPc);
#endif
}
void
@@ -62,18 +63,17 @@ BlockingResourceBase::GetStackTrace(Acqu
#ifndef MOZ_CALLSTACK_DISABLED
// Skip this function and the calling function.
const uint32_t kSkipFrames = 2;
aState.Clear();
// NB: Ignore the return value, there's nothing useful we can do if this
// this fails.
- NS_StackWalk(StackWalkCallback, kSkipFrames,
- 24, &aState, 0, nullptr);
+ NS_StackWalk(StackWalkCallback, kSkipFrames, 24, &aState, 0, nullptr);
#endif
}
/**
* PrintCycle
* Append to |aOut| detailed information about the circular
* dependency in |aCycle|. Returns true if it *appears* that this
* cycle may represent an imminent deadlock, but this is merely a
@@ -180,17 +180,17 @@ BlockingResourceBase::Print(nsACString&
#ifdef MOZ_CALLSTACK_DISABLED
fputs(" [stack trace unavailable]\n", stderr);
#else
const AcquisitionState& state = acquired ? mAcquired : mFirstSeen;
WalkTheStackCodeAddressService addressService;
for (uint32_t i = 0; i < state.Length(); i++) {
- const size_t kMaxLength = 4096;
+ const size_t kMaxLength = 1024;
char buffer[kMaxLength];
addressService.GetLocation(state[i], buffer, kMaxLength);
const char* fmt = " %s\n";
aOut += nsPrintfCString(fmt, buffer);
fprintf(stderr, fmt, buffer);
}
#endif
--- a/xpcom/glue/BlockingResourceBase.h
+++ b/xpcom/glue/BlockingResourceBase.h
@@ -314,17 +314,18 @@ private:
/**
* Shutdown
* Free static members.
*
* *NOT* thread safe.
*/
static void Shutdown();
- static void StackWalkCallback(void* aPc, void* aSp, void* aClosure);
+ static void StackWalkCallback(uint32_t aFrameNumber, void* aPc,
+ void* aSp, void* aClosure);
static void GetStackTrace(AcquisitionState& aState);
# ifdef MOZILLA_INTERNAL_API
// so it can call BlockingResourceBase::Shutdown()
friend void LogTerm();
# endif // ifdef MOZILLA_INTERNAL_API
#else // non-DEBUG implementation
--- a/xpcom/threads/HangMonitor.cpp
+++ b/xpcom/threads/HangMonitor.cpp
@@ -109,17 +109,17 @@ Crash()
NS_LITERAL_CSTRING("1"));
#endif
NS_RUNTIMEABORT("HangMonitor triggered");
}
#ifdef REPORT_CHROME_HANGS
static void
-ChromeStackWalker(void* aPC, void* aSP, void* aClosure)
+ChromeStackWalker(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure)
{
MOZ_ASSERT(aClosure);
std::vector<uintptr_t>* stack =
static_cast<std::vector<uintptr_t>*>(aClosure);
if (stack->size() == MAX_CALL_STACK_PCS) {
return;
}
MOZ_ASSERT(stack->size() < MAX_CALL_STACK_PCS);