author | Ehsan Akhgari <ehsan@mozilla.com> |
Fri, 14 Oct 2011 10:37:35 -0400 | |
changeset 78736 | 349f3d4b2d877b99502b6aff9e826fe5250de028 |
parent 78727 | fef552fcd2fcab54b2598f76118e7f5869ae117b (current diff) |
parent 78735 | fa65bb9a09092e9cffa8cf352ddd4eaf8d3bef00 (diff) |
child 78737 | 80d7e83b765985cae8aeff60b47b311d0422bbe1 |
child 81541 | 7603c5ce42602e6246e02690fa7e43d2a0c4985a |
push id | 21329 |
push user | eakhgari@mozilla.com |
push date | Fri, 14 Oct 2011 14:37:50 +0000 |
treeherder | mozilla-central@349f3d4b2d87 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 10.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -184,16 +184,17 @@ struct nsShortcutCandidate { } PRUint32 mCharCode; bool mIgnoreShift; }; class nsContentUtils { friend class nsAutoScriptBlockerSuppressNodeRemoved; + friend class mozilla::AutoRestore<bool>; typedef mozilla::dom::Element Element; typedef mozilla::TimeDuration TimeDuration; public: static nsresult Init(); /** * Get a JSContext from the document's scope object. @@ -1056,32 +1057,36 @@ public: * @param aSourceBuffer the string being set as innerHTML * @param aTargetNode the target container * @param aContextLocalName local name of context node * @param aContextNamespace namespace of context node * @param aQuirks true to make <table> not close <p> * @param aPreventScriptExecution true to prevent scripts from executing; * don't set to false when parsing into a target node that has been * bound to tree. + * @return NS_ERROR_DOM_INVALID_STATE_ERR if a re-entrant attempt to parse + * fragments is made and NS_OK otherwise. */ - static void ParseFragmentHTML(const nsAString& aSourceBuffer, - nsIContent* aTargetNode, - nsIAtom* aContextLocalName, - PRInt32 aContextNamespace, - bool aQuirks, - bool aPreventScriptExecution); + static nsresult ParseFragmentHTML(const nsAString& aSourceBuffer, + nsIContent* aTargetNode, + nsIAtom* aContextLocalName, + PRInt32 aContextNamespace, + bool aQuirks, + bool aPreventScriptExecution); /** * Invoke the fragment parsing algorithm (innerHTML) using the XML parser. * * @param aSourceBuffer the string being set as innerHTML * @param aTargetNode the target container * @param aTagStack the namespace mapping context * @param aPreventExecution whether to mark scripts as already started * @param aReturn the result fragment + * @return NS_ERROR_DOM_INVALID_STATE_ERR if a re-entrant attempt to parse + * fragments is made, a return code from the XML parser. */ static nsresult ParseFragmentXML(const nsAString& aSourceBuffer, nsIDocument* aDocument, nsTArray<nsString>& aTagStack, bool aPreventScriptExecution, nsIDOMDocumentFragment** aReturn); /** @@ -1906,16 +1911,21 @@ private: static bool sTrustedFullScreenOnly; static bool sFullScreenKeyInputRestricted; static PRUint32 sHandlingInputTimeout; static nsHtml5Parser* sHTMLFragmentParser; static nsIParser* sXMLFragmentParser; static nsIFragmentContentSink* sXMLFragmentSink; + /** + * True if there's a fragment parser activation on the stack. + */ + static bool sFragmentParsingActive; + static nsString* sShiftText; static nsString* sControlText; static nsString* sMetaText; static nsString* sAltText; static nsString* sModifierSeparator; }; #define NS_HOLD_JS_OBJECTS(obj, clazz) \
--- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -267,16 +267,17 @@ bool nsContentUtils::sIsFullScreenApiEna bool nsContentUtils::sTrustedFullScreenOnly = true; bool nsContentUtils::sFullScreenKeyInputRestricted = true; PRUint32 nsContentUtils::sHandlingInputTimeout = 1000; nsHtml5Parser* nsContentUtils::sHTMLFragmentParser = nsnull; nsIParser* nsContentUtils::sXMLFragmentParser = nsnull; nsIFragmentContentSink* nsContentUtils::sXMLFragmentSink = nsnull; +bool nsContentUtils::sFragmentParsingActive = false; static PLDHashTable sEventListenerManagersHash; class EventListenerManagerMapEntry : public PLDHashEntryHdr { public: EventListenerManagerMapEntry(const void *aKey) : mKey(aKey) @@ -3504,37 +3505,38 @@ nsContentUtils::CreateContextualFragment if (contextAsContent && !contextAsContent->IsElement()) { contextAsContent = contextAsContent->GetParent(); if (contextAsContent && !contextAsContent->IsElement()) { // can this even happen? contextAsContent = nsnull; } } + nsresult rv; nsCOMPtr<nsIContent> fragment = do_QueryInterface(frag); if (contextAsContent && !contextAsContent->IsHTML(nsGkAtoms::html)) { - ParseFragmentHTML(aFragment, - fragment, - contextAsContent->Tag(), - contextAsContent->GetNameSpaceID(), - (document->GetCompatibilityMode() == - eCompatibility_NavQuirks), - aPreventScriptExecution); + rv = ParseFragmentHTML(aFragment, + fragment, + contextAsContent->Tag(), + contextAsContent->GetNameSpaceID(), + (document->GetCompatibilityMode() == + eCompatibility_NavQuirks), + aPreventScriptExecution); } else { - ParseFragmentHTML(aFragment, - fragment, - nsGkAtoms::body, - kNameSpaceID_XHTML, - (document->GetCompatibilityMode() == - eCompatibility_NavQuirks), - aPreventScriptExecution); + rv = ParseFragmentHTML(aFragment, + fragment, + nsGkAtoms::body, + kNameSpaceID_XHTML, + (document->GetCompatibilityMode() == + eCompatibility_NavQuirks), + aPreventScriptExecution); } frag.forget(aReturn); - return NS_OK; + return rv; } nsAutoTArray<nsString, 32> tagStack; nsAutoString uriStr, nameStr; nsCOMPtr<nsIContent> content = do_QueryInterface(aContextNode); // just in case we have a text node if (content && !content->IsElement()) content = content->GetParent(); @@ -3606,46 +3608,59 @@ nsContentUtils::DropFragmentParsers() /* static */ void nsContentUtils::XPCOMShutdown() { nsContentUtils::DropFragmentParsers(); } /* static */ -void +nsresult nsContentUtils::ParseFragmentHTML(const nsAString& aSourceBuffer, nsIContent* aTargetNode, nsIAtom* aContextLocalName, PRInt32 aContextNamespace, bool aQuirks, bool aPreventScriptExecution) { + if (nsContentUtils::sFragmentParsingActive) { + NS_NOTREACHED("Re-entrant fragment parsing attempted."); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + mozilla::AutoRestore<bool> guard(nsContentUtils::sFragmentParsingActive); + nsContentUtils::sFragmentParsingActive = true; if (!sHTMLFragmentParser) { sHTMLFragmentParser = static_cast<nsHtml5Parser*>(nsHtml5Module::NewHtml5Parser().get()); // Now sHTMLFragmentParser owns the object } sHTMLFragmentParser->ParseHtml5Fragment(aSourceBuffer, aTargetNode, aContextLocalName, aContextNamespace, aQuirks, aPreventScriptExecution); sHTMLFragmentParser->Reset(); + return NS_OK; } /* static */ nsresult nsContentUtils::ParseFragmentXML(const nsAString& aSourceBuffer, nsIDocument* aDocument, nsTArray<nsString>& aTagStack, bool aPreventScriptExecution, nsIDOMDocumentFragment** aReturn) { + if (nsContentUtils::sFragmentParsingActive) { + NS_NOTREACHED("Re-entrant fragment parsing attempted."); + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + mozilla::AutoRestore<bool> guard(nsContentUtils::sFragmentParsingActive); + nsContentUtils::sFragmentParsingActive = true; if (!sXMLFragmentParser) { nsCOMPtr<nsIParser> parser = do_CreateInstance(kCParserCID); parser.forget(&sXMLFragmentParser); // sXMLFragmentParser now owns the parser } if (!sXMLFragmentSink) { NS_NewXMLFragmentContentSink(&sXMLFragmentSink); // sXMLFragmentSink now owns the sink
--- a/content/html/content/src/nsGenericHTMLElement.cpp +++ b/content/html/content/src/nsGenericHTMLElement.cpp @@ -765,23 +765,23 @@ nsGenericHTMLElement::SetInnerHTML(const } nsAutoScriptLoaderDisabler sld(doc); nsCOMPtr<nsIDOMDocumentFragment> df; if (doc->IsHTML()) { PRInt32 oldChildCount = GetChildCount(); - nsContentUtils::ParseFragmentHTML(aInnerHTML, - this, - Tag(), - GetNameSpaceID(), - doc->GetCompatibilityMode() == - eCompatibility_NavQuirks, - PR_TRUE); + rv = nsContentUtils::ParseFragmentHTML(aInnerHTML, + this, + Tag(), + GetNameSpaceID(), + doc->GetCompatibilityMode() == + eCompatibility_NavQuirks, + PR_TRUE); // HTML5 parser has notified, but not fired mutation events. FireMutationEventsForDirectParsing(doc, this, oldChildCount); } else { rv = nsContentUtils::CreateContextualFragment(this, aInnerHTML, PR_TRUE, getter_AddRefs(df)); nsCOMPtr<nsINode> fragment = do_QueryInterface(df); if (NS_SUCCEEDED(rv)) { @@ -836,48 +836,49 @@ nsGenericHTMLElement::InsertAdjacentHTML // Needed when insertAdjacentHTML is used in combination with contenteditable mozAutoDocUpdate updateBatch(doc, UPDATE_CONTENT_MODEL, PR_TRUE); nsAutoScriptLoaderDisabler sld(doc); // Batch possible DOMSubtreeModified events. mozAutoSubtreeModified subtree(doc, nsnull); + nsresult rv; // Parse directly into destination if possible if (doc->IsHTML() && (position == eBeforeEnd || (position == eAfterEnd && !GetNextSibling()) || (position == eAfterBegin && !GetFirstChild()))) { PRInt32 oldChildCount = destination->GetChildCount(); PRInt32 contextNs = destination->GetNameSpaceID(); nsIAtom* contextLocal = destination->Tag(); if (contextLocal == nsGkAtoms::html && contextNs == kNameSpaceID_XHTML) { // For compat with IE6 through IE9. Willful violation of HTML5 as of // 2011-04-06. CreateContextualFragment does the same already. // Spec bug: http://www.w3.org/Bugs/Public/show_bug.cgi?id=12434 contextLocal = nsGkAtoms::body; } - nsContentUtils::ParseFragmentHTML(aText, - destination, - contextLocal, - contextNs, - doc->GetCompatibilityMode() == - eCompatibility_NavQuirks, - PR_TRUE); + rv = nsContentUtils::ParseFragmentHTML(aText, + destination, + contextLocal, + contextNs, + doc->GetCompatibilityMode() == + eCompatibility_NavQuirks, + PR_TRUE); // HTML5 parser has notified, but not fired mutation events. FireMutationEventsForDirectParsing(doc, destination, oldChildCount); - return NS_OK; + return rv; } // couldn't parse directly nsCOMPtr<nsIDOMDocumentFragment> df; - nsresult rv = nsContentUtils::CreateContextualFragment(destination, - aText, - PR_TRUE, - getter_AddRefs(df)); + rv = nsContentUtils::CreateContextualFragment(destination, + aText, + PR_TRUE, + getter_AddRefs(df)); nsCOMPtr<nsINode> fragment = do_QueryInterface(df); NS_ENSURE_SUCCESS(rv, rv); // Suppress assertion about node removal mutation events that can't have // listeners anyway, because no one has had the chance to register mutation // listeners on the fragment that comes from the parser. nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
--- a/editor/libeditor/html/nsHTMLDataTransfer.cpp +++ b/editor/libeditor/html/nsHTMLDataTransfer.cpp @@ -2702,33 +2702,34 @@ nsresult nsHTMLEditor::CreateDOMFragment nsresult nsHTMLEditor::ParseFragment(const nsAString & aFragStr, nsIAtom* aContextLocalName, nsIDocument* aTargetDocument, nsCOMPtr<nsIDOMNode> *outNode, bool aTrustedInput) { + nsresult rv; nsCOMPtr<nsIDOMDocumentFragment> frag; NS_NewDocumentFragment(getter_AddRefs(frag), aTargetDocument->NodeInfoManager()); nsCOMPtr<nsIContent> fragment = do_QueryInterface(frag); - nsContentUtils::ParseFragmentHTML(aFragStr, - fragment, - aContextLocalName ? - aContextLocalName : nsGkAtoms::body, - kNameSpaceID_XHTML, - PR_FALSE, - PR_TRUE); + rv = nsContentUtils::ParseFragmentHTML(aFragStr, + fragment, + aContextLocalName ? + aContextLocalName : nsGkAtoms::body, + kNameSpaceID_XHTML, + PR_FALSE, + PR_TRUE); if (!aTrustedInput) { nsTreeSanitizer sanitizer(!!aContextLocalName, !aContextLocalName); sanitizer.Sanitize(fragment); } *outNode = do_QueryInterface(frag); - return NS_OK; + return rv; } nsresult nsHTMLEditor::CreateListOfNodesToPaste(nsIDOMNode *aFragmentAsNode, nsCOMArray<nsIDOMNode>& outNodeList, nsIDOMNode *aStartNode, PRInt32 aStartOffset, nsIDOMNode *aEndNode, PRInt32 aEndOffset)
--- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -3110,45 +3110,54 @@ AccountStorageForTextRun(gfxTextRun *aTe bytes &= ~(sizeof(gfxTextRun::CompressedGlyph) - 1); } bytes += sizeof(gfxTextRun); gTextRunStorage += bytes*aSign; gTextRunStorageHighWaterMark = NS_MAX(gTextRunStorageHighWaterMark, gTextRunStorage); } #endif +static PRUint64 +GlyphStorageAllocCount(PRUint32 aLength, PRUint32 aFlags) +{ + // always need to allocate storage for the glyph data + PRUint64 allocCount = aLength; + + // if the text is not persistent, we also need space for a copy + if (!(aFlags & gfxTextRunFactory::TEXT_IS_PERSISTENT)) { + // figure out number of extra CompressedGlyph elements we need to + // get sufficient space for the text + typedef gfxTextRun::CompressedGlyph CompressedGlyph; + if (aFlags & gfxTextRunFactory::TEXT_IS_8BIT) { + allocCount += (aLength + sizeof(CompressedGlyph) - 1) / + sizeof(CompressedGlyph); + } else { + allocCount += (aLength * sizeof(PRUnichar) + + sizeof(CompressedGlyph) - 1) / + sizeof(CompressedGlyph); + } + } + return allocCount; +} + // Helper for textRun creation to preallocate storage for glyphs and text; // this function returns a pointer to the newly-allocated glyph storage, // AND modifies the aText parameter if TEXT_IS_PERSISTENT was not set. // In that case, the text is appended to the glyph storage, so a single // delete[] operation in the textRun destructor will free both. // Returns nsnull if allocation fails. gfxTextRun::CompressedGlyph * gfxTextRun::AllocateStorage(const void*& aText, PRUint32 aLength, PRUint32 aFlags) { // Here, we rely on CompressedGlyph being the largest unit we care about for // allocation/alignment of either glyph data or text, so we allocate an array // of CompressedGlyphs, then take the last chunk of that and cast a pointer to // PRUint8* or PRUnichar* for text storage. - // always need to allocate storage for the glyph data - PRUint64 allocCount = aLength; - - // if the text is not persistent, we also need space for a copy - if (!(aFlags & gfxTextRunFactory::TEXT_IS_PERSISTENT)) { - // figure out number of extra CompressedGlyph elements we need to - // get sufficient space for the text - if (aFlags & gfxTextRunFactory::TEXT_IS_8BIT) { - allocCount += (aLength + sizeof(CompressedGlyph)-1) - / sizeof(CompressedGlyph); - } else { - allocCount += (aLength*sizeof(PRUnichar) + sizeof(CompressedGlyph)-1) - / sizeof(CompressedGlyph); - } - } + PRUint64 allocCount = GlyphStorageAllocCount(aLength, aFlags); // allocate the storage we need, returning nsnull on failure rather than // throwing an exception (because web content can create huge runs) CompressedGlyph *storage = new (std::nothrow) CompressedGlyph[allocCount]; if (!storage) { NS_WARNING("failed to allocate glyph/text storage for text run!"); return nsnull; } @@ -4466,16 +4475,41 @@ gfxTextRun::ClusterIterator::ClusterAdva { if (mCurrentChar == PRUint32(-1)) { return 0; } return mTextRun->GetAdvanceWidth(mCurrentChar, ClusterLength(), aProvider); } +PRUint64 +gfxTextRun::ComputeSize() +{ + PRUint64 total = moz_malloc_usable_size(this); + if (total == 0) { + total = sizeof(gfxTextRun); + } + + PRUint64 glyphDataSize = moz_malloc_usable_size(mCharacterGlyphs); + if (glyphDataSize == 0) { + // calculate how much gfxTextRun::AllocateStorage would have allocated + glyphDataSize = sizeof(CompressedGlyph) * + GlyphStorageAllocCount(mCharacterCount, mFlags); + } + total += glyphDataSize; + + if (mDetailedGlyphs) { + total += mDetailedGlyphs->SizeOf(); + } + + total += mGlyphRuns.SizeOf(); + + return total; +} + #ifdef DEBUG void gfxTextRun::Dump(FILE* aOutput) { if (!aOutput) { aOutput = stdout; }
--- a/gfx/thebes/gfxFont.h +++ b/gfx/thebes/gfxFont.h @@ -1348,17 +1348,25 @@ public: * required for legible text should still be enabled. */ TEXT_DISABLE_OPTIONAL_LIGATURES = 0x0080, /** * When set, the textrun should favour speed of construction over * quality. This may involve disabling ligatures and/or kerning or * other effects. */ - TEXT_OPTIMIZE_SPEED = 0x0100 + TEXT_OPTIMIZE_SPEED = 0x0100, + /** + * For internal use by the memory reporter when accounting for + * storage used by textruns. + * Because the reporter may visit each textrun multiple times while + * walking the frame trees and textrun cache, it needs to mark + * textruns that have been seen so as to avoid multiple-accounting. + */ + TEXT_RUN_SIZE_ACCOUNTED = 0x0200 }; /** * This record contains all the parameters needed to initialize a textrun. */ struct Parameters { // A reference context suggesting where the textrun will be rendered gfxContext *mContext; @@ -2033,16 +2041,31 @@ public: bool mClipBeforePart; bool mClipAfterPart; }; // user font set generation when text run was created PRUint64 GetUserFontSetGeneration() { return mUserFontSetGeneration; } + // return storage used by this run, for memory reporter; + // nsTransformedTextRun needs to override this as it holds additional data + virtual PRUint64 ComputeSize(); + + void AccountForSize(PRUint64* aTotal) { + if (mFlags & gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED) { + return; + } + mFlags |= gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED; + *aTotal += ComputeSize(); + } + void ClearSizeAccounted() { + mFlags &= ~gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED; + } + #ifdef DEBUG // number of entries referencing this textrun in the gfxTextRunWordCache PRUint32 mCachedWords; // generation of gfxTextRunWordCache that refers to this textrun; // if the cache gets cleared, then mCachedWords is no longer meaningful PRUint32 mCacheGeneration; void Dump(FILE* aOutput); @@ -2203,16 +2226,21 @@ private: if (!mOffsetToIndex.InsertElementSorted(DGRec(aOffset, detailIndex), CompareRecordOffsets())) { return nsnull; } } return details; } + PRUint32 SizeOf() { + return sizeof(DetailedGlyphStore) + + mDetails.SizeOf() + mOffsetToIndex.SizeOf(); + } + private: struct DGRec { DGRec(const PRUint32& aOffset, const PRUint32& aIndex) : mOffset(aOffset), mIndex(aIndex) { } PRUint32 mOffset; // source character offset in the textrun PRUint32 mIndex; // index where this char's DetailedGlyphs begin };
--- a/gfx/thebes/gfxTextRunWordCache.cpp +++ b/gfx/thebes/gfxTextRunWordCache.cpp @@ -130,16 +130,18 @@ public: */ void Flush() { mCache.Clear(); #ifdef DEBUG mGeneration++; #endif } + void ComputeStorage(PRUint64 *aTotal); + #ifdef DEBUG PRUint32 mGeneration; void Dump(); #endif protected: struct CacheHashKey { void *mFontOrGroup; @@ -212,16 +214,21 @@ protected: void FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun, const gfxFontGroup::Parameters *aParams, const nsTArray<DeferredWord>& aDeferredWords, bool aSuccessful); void RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash); void Uninit(); + static PLDHashOperator AccountForStorage(CacheHashEntry *aEntry, + void *aUserData); + static PLDHashOperator ClearSizeAccounted(CacheHashEntry *aEntry, + void *aUserData); + nsTHashtable<CacheHashEntry> mCache; PRInt32 mBidiNumeral; #ifdef DEBUG static PLDHashOperator CacheDumpEntry(CacheHashEntry* aEntry, void* userArg); #endif }; @@ -902,16 +909,48 @@ TextRunWordCache::RemoveTextRun(gfxTextR } RemoveWord(aTextRun, wordStart, i, hash); #ifdef DEBUG NS_ASSERTION(aTextRun->mCachedWords == 0, "Textrun was not completely removed from the cache!"); #endif } +/*static*/ PLDHashOperator +TextRunWordCache::AccountForStorage(CacheHashEntry *aEntry, void *aUserData) +{ + gfxTextRun *run = aEntry->mTextRun; + if (run) { + PRUint64 *total = static_cast<PRUint64*>(aUserData); + run->AccountForSize(total); + } + return PL_DHASH_NEXT; +} + +/*static*/ PLDHashOperator +TextRunWordCache::ClearSizeAccounted(CacheHashEntry *aEntry, void *) +{ + gfxTextRun *run = aEntry->mTextRun; + if (run) { + run->ClearSizeAccounted(); + } + return PL_DHASH_NEXT; +} + +void +TextRunWordCache::ComputeStorage(PRUint64 *aTotal) +{ + if (aTotal) { + *aTotal += mCache.SizeOf(); + mCache.EnumerateEntries(AccountForStorage, aTotal); + } else { + mCache.EnumerateEntries(ClearSizeAccounted, nsnull); + } +} + static bool CompareDifferentWidthStrings(const PRUint8 *aStr1, const PRUnichar *aStr2, PRUint32 aLength) { PRUint32 i; for (i = 0; i < aLength; ++i) { if (aStr1[i] != aStr2[i]) return PR_FALSE; @@ -1056,8 +1095,18 @@ gfxTextRunWordCache::RemoveTextRun(gfxTe void gfxTextRunWordCache::Flush() { if (!gTextRunWordCache) return; gTextRunWordCache->Flush(); } + +void +gfxTextRunWordCache::ComputeStorage(PRUint64 *aTotal) +{ + if (!gTextRunWordCache) { + return; + } + gTextRunWordCache->ComputeStorage(aTotal); +} +
--- a/gfx/thebes/gfxTextRunWordCache.h +++ b/gfx/thebes/gfxTextRunWordCache.h @@ -100,16 +100,25 @@ public: static void RemoveTextRun(gfxTextRun *aTextRun); /** * Flush the textrun cache. This must be called if a configuration * change that would affect textruns is applied. */ static void Flush(); + /** + * If aTotal is NULL, just clears the TEXT_RUN_MEMORY_ACCOUNTED flag + * on each textRun found. + * If aTotal is non-NULL, adds the storage used for each textRun to the + * total, and sets the TEXT_RUN_MEMORY_ACCOUNTED flag to avoid double- + * accounting. (Runs with this flag already set will be skipped.) + */ + static void ComputeStorage(PRUint64 *aTotal); + protected: friend class gfxPlatform; static nsresult Init(); static void Shutdown(); }; #endif /* GFX_TEXT_RUN_WORD_CACHE_H */
--- a/ipc/glue/GeckoChildProcessHost.cpp +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -38,17 +38,17 @@ #include "GeckoChildProcessHost.h" #include "base/command_line.h" #include "base/path_service.h" #include "base/string_util.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/process_watcher.h" -#ifdef XP_MACOSX +#ifdef MOZ_WIDGET_COCOA #include "chrome/common/mach_ipc_mac.h" #include "base/rand_util.h" #include "nsILocalFileMac.h" #endif #include "prprf.h" #include "prenv.h" @@ -101,17 +101,17 @@ GeckoChildProcessHost::GeckoChildProcess base::WaitableEventWatcher::Delegate* aDelegate) : ChildProcessHost(RENDER_PROCESS), // FIXME/cjones: we should own this enum mProcessType(aProcessType), mMonitor("mozilla.ipc.GeckChildProcessHost.mMonitor"), mLaunched(false), mChannelInitialized(false), mDelegate(aDelegate), mChildProcessHandle(0) -#if defined(XP_MACOSX) +#if defined(MOZ_WIDGET_COCOA) , mChildTask(MACH_PORT_NULL) #endif { MOZ_COUNT_CTOR(GeckoChildProcessHost); MessageLoop* ioLoop = XRE_GetIOMessageLoop(); ioLoop->PostTask(FROM_HERE, NewRunnableMethod(this, @@ -127,17 +127,17 @@ GeckoChildProcessHost::~GeckoChildProces if (mChildProcessHandle > 0) ProcessWatcher::EnsureProcessTerminated(mChildProcessHandle #if defined(NS_BUILD_REFCNT_LOGGING) , false // don't "force" #endif ); -#if defined(XP_MACOSX) +#if defined(MOZ_WIDGET_COCOA) if (mChildTask != MACH_PORT_NULL) mach_port_deallocate(mach_task_self(), mChildTask); #endif } void GetPathToBinary(FilePath& exePath) { #if defined(OS_WIN) @@ -150,17 +150,17 @@ void GetPathToBinary(FilePath& exePath) NS_ASSERTION(directoryService, "Expected XPCOM to be available"); if (directoryService) { nsCOMPtr<nsIFile> greDir; nsresult rv = directoryService->Get(NS_GRE_DIR, NS_GET_IID(nsIFile), getter_AddRefs(greDir)); if (NS_SUCCEEDED(rv)) { nsCString path; greDir->GetNativePath(path); exePath = FilePath(path.get()); -#ifdef OS_MACOSX +#ifdef MOZ_WIDGET_COCOA // We need to use an App Bundle on OS X so that we can hide // the dock icon. See Bug 557225. exePath = exePath.AppendASCII(MOZ_CHILD_PROCESS_BUNDLE); #endif } } } @@ -168,17 +168,17 @@ void GetPathToBinary(FilePath& exePath) exePath = FilePath(CommandLine::ForCurrentProcess()->argv()[0]); exePath = exePath.DirName(); } exePath = exePath.AppendASCII(MOZ_CHILD_PROCESS_NAME); #endif } -#ifdef XP_MACOSX +#ifdef MOZ_WIDGET_COCOA class AutoCFTypeObject { public: AutoCFTypeObject(CFTypeRef object) { mObject = object; } ~AutoCFTypeObject() { @@ -188,17 +188,17 @@ private: CFTypeRef mObject; }; #endif nsresult GeckoChildProcessHost::GetArchitecturesForBinary(const char *path, uint32 *result) { *result = 0; -#ifdef XP_MACOSX +#ifdef MOZ_WIDGET_COCOA CFURLRef url = ::CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8*)path, strlen(path), false); if (!url) { return NS_ERROR_FAILURE; } AutoCFTypeObject autoPluginContainerURL(url); @@ -234,17 +234,17 @@ nsresult GeckoChildProcessHost::GetArchi return (*result ? NS_OK : NS_ERROR_FAILURE); #else return NS_ERROR_NOT_IMPLEMENTED; #endif } uint32 GeckoChildProcessHost::GetSupportedArchitecturesForProcessType(GeckoProcessType type) { -#ifdef XP_MACOSX +#ifdef MOZ_WIDGET_COCOA if (type == GeckoProcessType_Plugin) { // Cache this, it shouldn't ever change. static uint32 pluginContainerArchs = 0; if (pluginContainerArchs == 0) { FilePath exePath; GetPathToBinary(exePath); nsresult rv = GetArchitecturesForBinary(exePath.value().c_str(), &pluginContainerArchs); NS_ASSERTION(NS_SUCCEEDED(rv) && pluginContainerArchs != 0, "Getting architecture of plugin container failed!"); @@ -566,22 +566,22 @@ GeckoChildProcessHost::PerformAsyncLaunc mFileMap.push_back(std::pair<int,int>(childCrashFd, childCrashRemapFd)); // "true" == crash reporting enabled childArgv.push_back("true"); } else { // "false" == crash reporting disabled childArgv.push_back("false"); } -# elif defined(XP_MACOSX) +# elif defined(MOZ_WIDGET_COCOA) childArgv.push_back(CrashReporter::GetChildNotificationPipe()); # endif // OS_LINUX #endif -#ifdef XP_MACOSX +#ifdef MOZ_WIDGET_COCOA // Add a mach port to the command line so the child can communicate its // 'task_t' back to the parent. // // Put a random number into the channel name, so that a compromised renderer // can't pretend being the child that's forked off. std::string mach_connection_name = StringPrintf("org.mozilla.machname.%d", base::RandInt(0, std::numeric_limits<int>::max())); childArgv.push_back(mach_connection_name.c_str()); @@ -594,17 +594,17 @@ GeckoChildProcessHost::PerformAsyncLaunc #endif base::LaunchApp(childArgv, mFileMap, #if defined(OS_LINUX) || defined(OS_MACOSX) newEnvVars, #endif false, &process, arch); -#ifdef XP_MACOSX +#ifdef MOZ_WIDGET_COCOA // Wait for the child process to send us its 'task_t' data. const int kTimeoutMs = 10000; MachReceiveMessage child_message; ReceivePort parent_recv_port(mach_connection_name.c_str()); kern_return_t err = parent_recv_port.WaitForMessage(&child_message, kTimeoutMs); if (err != KERN_SUCCESS) { std::string errString = StringPrintf("0x%x %s", err, mach_error_string(err)); @@ -685,17 +685,17 @@ GeckoChildProcessHost::PerformAsyncLaunc #else # error Sorry #endif if (!process) { return false; } SetHandle(process); -#if defined(XP_MACOSX) +#if defined(MOZ_WIDGET_COCOA) mChildTask = child_task; #endif return true; } void GeckoChildProcessHost::OnChannelConnected(int32 peer_pid)
--- a/js/public/Utility.h +++ b/js/public/Utility.h @@ -341,17 +341,17 @@ unsigned char _BitScanReverse64(unsigned */ #define JS_FLOOR_LOG2W(n) (JS_ASSERT((n) != 0), js_FloorLog2wImpl(n)) #if JS_BYTES_PER_WORD == 4 # ifdef JS_HAS_BUILTIN_BITSCAN32 # define js_FloorLog2wImpl(n) \ ((size_t)(JS_BITS_PER_WORD - 1 - js_bitscan_clz32(n))) # else -# define js_FloorLog2wImpl(n) ((size_t)JS_FloorLog2(n)) +extern size_t js_FloorLog2wImpl(size_t n); # endif #elif JS_BYTES_PER_WORD == 8 # ifdef JS_HAS_BUILTIN_BITSCAN64 # define js_FloorLog2wImpl(n) \ ((size_t)(JS_BITS_PER_WORD - 1 - js_bitscan_clz64(n))) # else extern size_t js_FloorLog2wImpl(size_t n); # endif
--- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -43,66 +43,180 @@ #include "builtin/RegExp.h" #include "vm/RegExpObject-inl.h" #include "vm/RegExpStatics-inl.h" using namespace js; using namespace js::types; -/* - * Return: - * - The original if no escaping need be performed. - * - A new string if escaping need be performed. - * - NULL on error. - */ -static JSString * -EscapeNakedForwardSlashes(JSContext *cx, JSString *unescaped) +class RegExpMatchBuilder +{ + JSContext * const cx; + JSObject * const array; + + bool setProperty(JSAtom *name, Value v) { + return !!js_DefineProperty(cx, array, ATOM_TO_JSID(name), &v, + JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE); + } + + public: + RegExpMatchBuilder(JSContext *cx, JSObject *array) : cx(cx), array(array) {} + + bool append(uint32 index, Value v) { + JS_ASSERT(!array->getOps()->getElement); + return !!js_DefineElement(cx, array, index, &v, JS_PropertyStub, JS_StrictPropertyStub, + JSPROP_ENUMERATE); + } + + bool setIndex(int index) { + return setProperty(cx->runtime->atomState.indexAtom, Int32Value(index)); + } + + bool setInput(JSString *str) { + JS_ASSERT(str); + return setProperty(cx->runtime->atomState.inputAtom, StringValue(str)); + } +}; + +static bool +CreateRegExpMatchResult(JSContext *cx, JSString *input, const jschar *chars, size_t length, + MatchPairs *matchPairs, Value *rval) +{ + /* + * Create the (slow) result array for a match. + * + * Array contents: + * 0: matched string + * 1..pairCount-1: paren matches + * input: input string + * index: start index for the match + */ + JSObject *array = NewSlowEmptyArray(cx); + if (!array) + return false; + + if (!input) { + input = js_NewStringCopyN(cx, chars, length); + if (!input) + return false; + } + + RegExpMatchBuilder builder(cx, array); + + for (size_t i = 0; i < matchPairs->pairCount(); ++i) { + MatchPair pair = matchPairs->pair(i); + + JSString *captured; + if (pair.isUndefined()) { + JS_ASSERT(i != 0); /* Since we had a match, first pair must be present. */ + if (!builder.append(i, UndefinedValue())) + return false; + } else { + captured = js_NewDependentString(cx, input, pair.start, pair.length()); + if (!captured || !builder.append(i, StringValue(captured))) + return false; + } + } + + if (!builder.setIndex(matchPairs->pair(0).start) || !builder.setInput(input)) + return false; + + *rval = ObjectValue(*array); + return true; +} + +template <class T> +bool +ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, T *re, JSLinearString *input, + const jschar *chars, size_t length, + size_t *lastIndex, RegExpExecType type, Value *rval) +{ + LifoAllocScope allocScope(&cx->tempLifoAlloc()); + MatchPairs *matchPairs = NULL; + RegExpRunStatus status = re->execute(cx, chars, length, lastIndex, allocScope, &matchPairs); + + switch (status) { + case RegExpRunStatus_Error: + return false; + case RegExpRunStatus_Success_NotFound: + *rval = NullValue(); + return true; + default: + JS_ASSERT(status == RegExpRunStatus_Success); + JS_ASSERT(matchPairs); + } + + if (res) + res->updateFromMatchPairs(cx, input, matchPairs); + + *lastIndex = matchPairs->pair(0).limit; + + if (type == RegExpTest) { + *rval = BooleanValue(true); + return true; + } + + return CreateRegExpMatchResult(cx, input, chars, length, matchPairs, rval); +} + +bool +js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpPrivate *rep, JSLinearString *input, + const jschar *chars, size_t length, + size_t *lastIndex, RegExpExecType type, Value *rval) +{ + return ExecuteRegExpImpl(cx, res, rep, input, chars, length, lastIndex, type, rval); +} + +bool +js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject *reobj, JSLinearString *input, + const jschar *chars, size_t length, + size_t *lastIndex, RegExpExecType type, Value *rval) +{ + return ExecuteRegExpImpl(cx, res, reobj, input, chars, length, lastIndex, type, rval); +} + +/* Note: returns the original if no escaping need be performed. */ +static JSLinearString * +EscapeNakedForwardSlashes(JSContext *cx, JSLinearString *unescaped) { size_t oldLen = unescaped->length(); - const jschar *oldChars = unescaped->getChars(cx); - if (!oldChars) - return NULL; + const jschar *oldChars = unescaped->chars(); + JS::Anchor<JSString *> anchor(unescaped); - js::Vector<jschar, 128> newChars(cx); + /* We may never need to use |sb|. Start using it lazily. */ + StringBuffer sb(cx); + for (const jschar *it = oldChars; it < oldChars + oldLen; ++it) { if (*it == '/' && (it == oldChars || it[-1] != '\\')) { - if (!newChars.length()) { - if (!newChars.reserve(oldLen + 1)) + /* There's a forward slash that needs escaping. */ + if (sb.empty()) { + /* This is the first one we've seen, copy everything up to this point. */ + if (!sb.reserve(oldLen + 1)) return NULL; - newChars.infallibleAppend(oldChars, size_t(it - oldChars)); + sb.infallibleAppend(oldChars, size_t(it - oldChars)); } - if (!newChars.append('\\')) + if (!sb.append('\\')) return NULL; } - if (!newChars.empty() && !newChars.append(*it)) + if (!sb.empty() && !sb.append(*it)) return NULL; } - if (newChars.empty()) - return unescaped; - - size_t len = newChars.length(); - if (!newChars.append('\0')) - return NULL; - jschar *chars = newChars.extractRawBuffer(); - JSString *escaped = js_NewString(cx, chars, len); - if (!escaped) - cx->free_(chars); - return escaped; + return sb.empty() ? unescaped : sb.finishString(); } static bool ResetRegExpObjectWithStatics(JSContext *cx, RegExpObject *reobj, - JSString *str, RegExpFlag flags = RegExpFlag(0)) + JSLinearString *str, RegExpFlag flags = RegExpFlag(0)) { flags = RegExpFlag(flags | cx->regExpStatics()->getFlags()); - return ResetRegExpObject(cx, reobj, str, flags); + return reobj->reset(cx, str, flags); } /* * Compile a new |RegExpPrivate| for the |RegExpObject|. * * Per ECMAv5 15.10.4.1, we act on combinations of (pattern, flags) as * arguments: * @@ -120,61 +234,62 @@ CompileRegExpObject(JSContext *cx, RegEx *rval = ObjectValue(*obj); return true; } Value sourceValue = argv[0]; if (ValueIsRegExp(sourceValue)) { /* * If we get passed in a |RegExpObject| source we return a new - * object with the same |RegExpPrivate|. + * object with the same source/flags. * * Note: the regexp static flags are not taken into consideration here. */ JSObject &sourceObj = sourceValue.toObject(); if (argc >= 2 && !argv[1].isUndefined()) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NEWREGEXP_FLAGGED); return false; } - RegExpPrivate *rep = sourceObj.asRegExp()->getPrivate(); - if (!rep) - return false; - - rep->incref(cx); - if (!ResetRegExpObject(cx, obj, AlreadyIncRefed<RegExpPrivate>(rep))) + if (!obj->reset(cx, sourceObj.asRegExp())) return false; *rval = ObjectValue(*obj); return true; } - JSString *sourceStr; + JSLinearString *sourceStr; if (sourceValue.isUndefined()) { sourceStr = cx->runtime->emptyString; } else { /* Coerce to string and compile. */ - sourceStr = js_ValueToString(cx, sourceValue); + JSString *str = js_ValueToString(cx, sourceValue); + if (!str) + return false; + sourceStr = str->ensureLinear(cx); if (!sourceStr) return false; } RegExpFlag flags = RegExpFlag(0); if (argc > 1 && !argv[1].isUndefined()) { JSString *flagStr = js_ValueToString(cx, argv[1]); if (!flagStr) return false; argv[1].setString(flagStr); if (!ParseRegExpFlags(cx, flagStr, &flags)) return false; } - JSString *escapedSourceStr = EscapeNakedForwardSlashes(cx, sourceStr); + JSLinearString *escapedSourceStr = EscapeNakedForwardSlashes(cx, sourceStr); if (!escapedSourceStr) return false; + if (!RegExpPrivateCode::checkSyntax(cx, NULL, escapedSourceStr)) + return false; + if (!ResetRegExpObjectWithStatics(cx, obj, escapedSourceStr, flags)) return false; *rval = ObjectValue(*obj); return true; } static JSBool regexp_compile(JSContext *cx, uintN argc, Value *vp) @@ -182,19 +297,17 @@ regexp_compile(JSContext *cx, uintN argc CallArgs args = CallArgsFromVp(argc, vp); bool ok; JSObject *obj = NonGenericMethodGuard(cx, args, regexp_compile, &RegExpClass, &ok); if (!obj) return ok; RegExpObject *reobj = obj->asRegExp(); - ok = CompileRegExpObject(cx, reobj, args.length(), args.array(), &args.rval()); - JS_ASSERT_IF(ok, reobj->getPrivate()); - return ok; + return CompileRegExpObject(cx, reobj, args.length(), args.array(), &args.rval()); } static JSBool regexp_construct(JSContext *cx, uintN argc, Value *vp) { Value *argv = JS_ARGV(cx, vp); if (!IsConstructing(vp)) { @@ -208,26 +321,20 @@ regexp_construct(JSContext *cx, uintN ar return true; } } JSObject *obj = NewBuiltinClassInstance(cx, &RegExpClass); if (!obj) return false; - PreInitRegExpObject pireo(obj); - RegExpObject *reobj = pireo.get(); + if (!CompileRegExpObject(cx, obj->asRegExp(), argc, argv, &JS_RVAL(cx, vp))) + return false; - if (!CompileRegExpObject(cx, reobj, argc, argv, &JS_RVAL(cx, vp))) { - pireo.fail(); - return false; - } - - pireo.succeed(); - *vp = ObjectValue(*reobj); + *vp = ObjectValue(*obj); return true; } static JSBool regexp_toString(JSContext *cx, uintN argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); @@ -351,40 +458,19 @@ js_InitRegExpClass(JSContext *cx, JSObje JS_ASSERT(obj->isNative()); GlobalObject *global = obj->asGlobal(); JSObject *proto = global->createBlankPrototype(cx, &RegExpClass); if (!proto) return NULL; - { - AlreadyIncRefed<RegExpPrivate> rep = - RegExpPrivate::create(cx, cx->runtime->emptyString, RegExpFlag(0), NULL); - if (!rep) - return NULL; - - /* - * Associate the empty regular expression with |RegExp.prototype|, and define - * the initial non-method properties of any regular expression instance. - * These must be added before methods to preserve slot layout. - */ -#ifdef DEBUG - assertSameCompartment(cx, proto, rep->compartment); -#endif - - PreInitRegExpObject pireo(proto); - RegExpObject *reproto = pireo.get(); - if (!ResetRegExpObject(cx, reproto, rep)) { - pireo.fail(); - return NULL; - } - - pireo.succeed(); - } + RegExpObject *reproto = proto->asRegExp(); + if (!reproto->reset(cx, cx->runtime->emptyString, RegExpFlag(0))) + return NULL; if (!DefinePropertiesAndBrand(cx, proto, NULL, regexp_methods)) return NULL; JSFunction *ctor = global->createConstructor(cx, regexp_construct, &RegExpClass, CLASS_ATOM(cx, RegExp), 2); if (!ctor) return NULL; @@ -414,73 +500,78 @@ js_InitRegExpClass(JSContext *cx, JSObje } /* * ES5 15.10.6.2 (and 15.10.6.3, which calls 15.10.6.2). * * RegExp.prototype.test doesn't need to create a results array, and we use * |execType| to perform this optimization. */ -static JSBool +static bool ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); /* Step 1. */ bool ok; JSObject *obj = NonGenericMethodGuard(cx, args, native, &RegExpClass, &ok); if (!obj) return ok; RegExpObject *reobj = obj->asRegExp(); - RegExpPrivate *re = reobj->getPrivate(); - if (!re) + RegExpPrivate *rep = reobj->getOrCreatePrivate(cx); + if (!rep) return true; /* * Code execution under this call could swap out the guts of |reobj|, so we * have to take a defensive refcount here. */ - AutoRefCount<RegExpPrivate> arc(cx, NeedsIncRef<RegExpPrivate>(re)); + AutoRefCount<RegExpPrivate> arc(cx, NeedsIncRef<RegExpPrivate>(rep)); RegExpStatics *res = cx->regExpStatics(); /* Step 2. */ - JSString *input = js_ValueToString(cx, args.length() > 0 ? args[0] : UndefinedValue()); + JSString *input = js_ValueToString(cx, (args.length() > 0) ? args[0] : UndefinedValue()); if (!input) return false; - + /* Step 3. */ + JSLinearString *linearInput = input->ensureLinear(cx); + const jschar *chars = linearInput->chars(); size_t length = input->length(); /* Step 4. */ const Value &lastIndex = reobj->getLastIndex(); /* Step 5. */ jsdouble i; if (!ToInteger(cx, lastIndex, &i)) return false; /* Steps 6-7 (with sticky extension). */ - if (!re->global() && !re->sticky()) + if (!rep->global() && !rep->sticky()) i = 0; /* Step 9a. */ if (i < 0 || i > length) { reobj->zeroLastIndex(); args.rval() = NullValue(); return true; } /* Steps 8-21. */ + RegExpExecType execType = (native == regexp_test) ? RegExpTest : RegExpExec; size_t lastIndexInt(i); - if (!re->execute(cx, res, input, &lastIndexInt, native == regexp_test, &args.rval())) + if (!ExecuteRegExp(cx, res, rep, linearInput, chars, length, &lastIndexInt, execType, + &args.rval())) { return false; + } /* Step 11 (with sticky extension). */ - if (re->global() || (!args.rval().isNull() && re->sticky())) { + if (rep->global() || (!args.rval().isNull() && rep->sticky())) { if (args.rval().isNull()) reobj->zeroLastIndex(); else reobj->setLastIndex(lastIndexInt); } return true; }
--- a/js/src/builtin/RegExp.h +++ b/js/src/builtin/RegExp.h @@ -48,16 +48,31 @@ js_InitRegExpClass(JSContext *cx, JSObje /* * The following builtin natives are extern'd for pointer comparison in * other parts of the engine. */ namespace js { +/* + * |res| may be null if the |RegExpStatics| are not to be updated. + * |input| may be null if there is no |JSString| corresponding to + * |chars| and |length|. + */ +bool +ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject *reobj, JSLinearString *input, + const jschar *chars, size_t length, + size_t *lastIndex, RegExpExecType type, Value *rval); + +bool +ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpPrivate *rep, JSLinearString *input, + const jschar *chars, size_t length, + size_t *lastIndex, RegExpExecType type, Value *rval); + extern JSBool regexp_exec(JSContext *cx, uintN argc, Value *vp); extern JSBool regexp_test(JSContext *cx, uintN argc, Value *vp); } /* namespace js */
--- a/js/src/ds/LifoAlloc.h +++ b/js/src/ds/LifoAlloc.h @@ -319,16 +319,20 @@ class LifoAllocScope { mark = lifoAlloc->mark(); } ~LifoAllocScope() { if (shouldRelease) lifoAlloc->release(mark); } + LifoAlloc &alloc() { + return *lifoAlloc; + } + void releaseEarly() { JS_ASSERT(shouldRelease); lifoAlloc->release(mark); shouldRelease = false; } }; } /* namespace js */
--- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -5956,26 +5956,19 @@ JS_ClearRegExpStatics(JSContext *cx, JSO } JS_PUBLIC_API(JSBool) JS_ExecuteRegExp(JSContext *cx, JSObject *obj, JSObject *reobj, jschar *chars, size_t length, size_t *indexp, JSBool test, jsval *rval) { CHECK_REQUEST(cx); - RegExpPrivate *rep = reobj->asRegExp()->getPrivate(); - if (!rep) - return false; - - JSString *str = js_NewStringCopyN(cx, chars, length); - if (!str) - return false; - RegExpStatics *res = obj->asGlobal()->getRegExpStatics(); - return rep->execute(cx, res, str, indexp, test, rval); + return ExecuteRegExp(cx, res, reobj->asRegExp(), NULL, chars, length, + indexp, test ? RegExpTest : RegExpExec, rval); } JS_PUBLIC_API(JSObject *) JS_NewRegExpObjectNoStatics(JSContext *cx, char *bytes, size_t length, uintN flags) { CHECK_REQUEST(cx); jschar *chars = InflateString(cx, bytes, &length); if (!chars) @@ -5993,25 +5986,18 @@ JS_NewUCRegExpObjectNoStatics(JSContext } JS_PUBLIC_API(JSBool) JS_ExecuteRegExpNoStatics(JSContext *cx, JSObject *obj, jschar *chars, size_t length, size_t *indexp, JSBool test, jsval *rval) { CHECK_REQUEST(cx); - RegExpPrivate *rep = obj->asRegExp()->getPrivate(); - if (!rep) - return false; - - JSString *str = js_NewStringCopyN(cx, chars, length); - if (!str) - return false; - - return rep->executeNoStatics(cx, str, indexp, test, rval); + return ExecuteRegExp(cx, NULL, obj->asRegExp(), NULL, chars, length, indexp, + test ? RegExpTest : RegExpExec, rval); } JS_PUBLIC_API(JSBool) JS_ObjectIsRegExp(JSContext *cx, JSObject *obj) { JS_ASSERT(obj); return obj->isRegExp(); }
--- a/js/src/jslog2.cpp +++ b/js/src/jslog2.cpp @@ -49,16 +49,27 @@ JS_STATIC_ASSERT(sizeof(unsigned int) == JS_STATIC_ASSERT_IF(JS_BYTES_PER_WORD == 4, sizeof(unsigned int) == sizeof(JSUword)); #endif #ifdef JS_HAS_BUILTIN_BITSCAN64 JS_STATIC_ASSERT_IF(JS_BYTES_PER_WORD == 8, sizeof(unsigned long long) == sizeof(JSUword)); #endif +#if !defined(JS_HAS_BUILTIN_BITSCAN32) && JS_BYTES_PER_WORD == 4 + +size_t +js_FloorLog2wImpl(size_t n) +{ + size_t log2; + + JS_FLOOR_LOG2(log2, n); + return log2; +} +#endif /* * js_FloorLog2wImpl has to be defined only for 64-bit non-GCC case. */ #if !defined(JS_HAS_BUILTIN_BITSCAN64) && JS_BYTES_PER_WORD == 8 size_t js_FloorLog2wImpl(size_t n) {
--- a/js/src/jsprvtd.h +++ b/js/src/jsprvtd.h @@ -123,27 +123,35 @@ class JSAtom; struct JSDefinition; class JSWrapper; namespace js { struct ArgumentsData; struct Class; +class RegExpObject; class RegExpPrivate; class RegExpStatics; +class MatchPairs; enum RegExpFlag { IgnoreCaseFlag = JS_BIT(0), GlobalFlag = JS_BIT(1), MultilineFlag = JS_BIT(2), StickyFlag = JS_BIT(3) }; +enum RegExpExecType +{ + RegExpExec, + RegExpTest +}; + class AutoStringRooter; class ExecuteArgsGuard; class InvokeFrameGuard; class InvokeArgsGuard; class StringBuffer; class TraceRecorder; struct TraceMonitor;
--- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -67,16 +67,17 @@ #include "jsnum.h" #include "jsobj.h" #include "jsopcode.h" #include "jsprobes.h" #include "jsscope.h" #include "jsstr.h" #include "jsversion.h" +#include "builtin/RegExp.h" #include "vm/GlobalObject.h" #include "vm/RegExpObject.h" #include "jsinferinlines.h" #include "jsobjinlines.h" #include "jsautooplen.h" // generated headers last #include "vm/RegExpObject-inl.h" @@ -512,31 +513,31 @@ js_str_toString(JSContext *cx, uintN arg args.rval().setString(str); return true; } /* * Java-like string native methods. */ - + JS_ALWAYS_INLINE bool ValueToIntegerRange(JSContext *cx, const Value &v, int32 *out) { if (v.isInt32()) { *out = v.toInt32(); } else { double d; if (!ToInteger(cx, v, &d)) return false; if (d > INT32_MAX) *out = INT32_MAX; else if (d < INT32_MIN) *out = INT32_MIN; - else + else *out = int32(d); } return true; } static JSBool str_substring(JSContext *cx, uintN argc, Value *vp) @@ -1248,53 +1249,59 @@ class FlatMatch const jschar *pat; size_t patlen; int32 match_; friend class RegExpGuard; public: FlatMatch() : patstr(NULL) {} /* Old GCC wants this initialization. */ - JSString *pattern() const { return patstr; } + JSLinearString *pattern() const { return patstr; } size_t patternLength() const { return patlen; } /* * Note: The match is -1 when the match is performed successfully, * but no match is found. */ int32 match() const { return match_; } }; -/* A regexp and optional associated object. */ class RegExpPair { AutoRefCount<RegExpPrivate> rep_; - JSObject *reobj_; + RegExpObject *reobj_; explicit RegExpPair(RegExpPair &); + void operator=(const RegExpPair &); public: explicit RegExpPair(JSContext *cx) : rep_(cx) {} - void reset(JSObject &obj) { - reobj_ = &obj; - RegExpPrivate *rep = reobj_->asRegExp()->getPrivate(); - JS_ASSERT(rep); + bool resetWithObject(JSContext *cx, RegExpObject *reobj) { + reobj_ = reobj; + RegExpPrivate *rep = reobj_->asRegExp()->getOrCreatePrivate(cx); + if (!rep) + return false; rep_.reset(NeedsIncRef<RegExpPrivate>(rep)); + return true; } - void reset(AlreadyIncRefed<RegExpPrivate> rep) { + void resetWithPrivate(AlreadyIncRefed<RegExpPrivate> rep) { reobj_ = NULL; rep_.reset(rep); } - /* Note: May be null. */ - JSObject *reobj() const { return reobj_; } - bool hasRegExp() const { return !rep_.null(); } - RegExpPrivate &re() const { JS_ASSERT(hasRegExp()); return *rep_; } + bool null() const { return rep_.null(); } + + RegExpObject *reobj() const { return reobj_; } + + RegExpPrivate *getPrivate() const { + JS_ASSERT(!null()); + return rep_.get(); + } }; /* * RegExpGuard factors logic out of String regexp operations. * * @param optarg Indicates in which argument position RegExp * flags will be found, if present. This is a Mozilla * extension and not part of any ECMA spec. @@ -1309,17 +1316,17 @@ class RegExpGuard FlatMatch fm; /* * Upper bound on the number of characters we are willing to potentially * waste on searching for RegExp meta-characters. */ static const size_t MAX_FLAT_PAT_LEN = 256; - static JSString *flattenPattern(JSContext *cx, JSLinearString *patstr) { + static JSLinearString *flattenPattern(JSContext *cx, JSLinearString *patstr) { StringBuffer sb(cx); if (!sb.reserve(patstr->length())) return NULL; static const jschar ESCAPE_CHAR = '\\'; const jschar *chars = patstr->chars(); size_t len = patstr->length(); for (const jschar *it = chars; it != chars + len; ++it) { @@ -1338,23 +1345,23 @@ class RegExpGuard explicit RegExpGuard(JSContext *cx) : cx(cx), rep(cx) {} ~RegExpGuard() {} /* init must succeed in order to call tryFlatMatch or normalizeRegExp. */ bool init(uintN argc, Value *vp, bool convertVoid = false) { if (argc != 0 && ValueIsRegExp(vp[2])) { - rep.reset(vp[2].toObject()); + rep.resetWithObject(cx, vp[2].toObject().asRegExp()); } else { if (convertVoid && (argc == 0 || vp[2].isUndefined())) { fm.patstr = cx->runtime->emptyString; return true; } - + fm.patstr = ArgToRootedString(cx, argc, vp, 0); if (!fm.patstr) return false; } return true; } /* @@ -1365,17 +1372,17 @@ class RegExpGuard * @return Whether flat matching could be used. * * N.B. tryFlatMatch returns NULL on OOM, so the caller must check cx->isExceptionPending(). */ const FlatMatch * tryFlatMatch(JSContext *cx, JSString *textstr, uintN optarg, uintN argc, bool checkMetaChars = true) { - if (rep.hasRegExp()) + if (!rep.null()) return NULL; fm.pat = fm.patstr->chars(); fm.patlen = fm.patstr->length(); if (optarg < argc) return NULL; @@ -1398,56 +1405,56 @@ class RegExpGuard } return &fm; } /* If the pattern is not already a regular expression, make it so. */ const RegExpPair * normalizeRegExp(bool flat, uintN optarg, uintN argc, Value *vp) { - if (rep.hasRegExp()) + if (!rep.null()) return &rep; /* Build RegExp from pattern string. */ JSString *opt; if (optarg < argc) { opt = js_ValueToString(cx, vp[2 + optarg]); if (!opt) return NULL; } else { opt = NULL; } - JSString *patstr; + JSLinearString *patstr; if (flat) { patstr = flattenPattern(cx, fm.patstr); if (!patstr) return NULL; } else { patstr = fm.patstr; } JS_ASSERT(patstr); - AlreadyIncRefed<RegExpPrivate> re = RegExpPrivate::createFlagged(cx, patstr, opt, NULL); + AlreadyIncRefed<RegExpPrivate> re = RegExpPrivate::create(cx, patstr, opt, NULL); if (!re) return NULL; - rep.reset(re); + rep.resetWithPrivate(re); return &rep; } #if DEBUG - bool hasRegExpPair() const { return rep.hasRegExp(); } + bool hasRegExpPair() const { return !rep.null(); } #endif }; -/* js_ExecuteRegExp indicates success in two ways, based on the 'test' flag. */ +/* ExecuteRegExp indicates success in two ways, based on the 'test' flag. */ static JS_ALWAYS_INLINE bool -Matched(bool test, const Value &v) +Matched(RegExpExecType type, const Value &v) { - return test ? v.isTrue() : !v.isNull(); + return (type == RegExpTest) ? v.isTrue() : !v.isNull(); } typedef bool (*DoMatchCallback)(JSContext *cx, RegExpStatics *res, size_t count, void *data); /* * BitOR-ing these flags allows the DoMatch caller to control when how the * RegExp engine is called and when callbacks are fired. */ @@ -1458,43 +1465,50 @@ enum MatchControlFlags { MATCH_ARGS = TEST_GLOBAL_BIT, MATCHALL_ARGS = CALLBACK_ON_SINGLE_BIT, REPLACE_ARGS = TEST_GLOBAL_BIT | TEST_SINGLE_BIT | CALLBACK_ON_SINGLE_BIT }; /* Factor out looping and matching logic. */ static bool -DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpPair &rep, +DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpPair ®ExpPair, DoMatchCallback callback, void *data, MatchControlFlags flags, Value *rval) { - RegExpPrivate &re = rep.re(); - if (re.global()) { + RegExpPrivate *rep = regExpPair.getPrivate(); + JSLinearString *linearStr = str->ensureLinear(cx); + if (!linearStr) + return false; + const jschar *chars = linearStr->chars(); + size_t length = linearStr->length(); + + if (rep->global()) { /* global matching ('g') */ - bool testGlobal = flags & TEST_GLOBAL_BIT; - if (rep.reobj()) - rep.reobj()->asRegExp()->zeroLastIndex(); + RegExpExecType type = (flags & TEST_GLOBAL_BIT) ? RegExpTest : RegExpExec; + if (RegExpObject *reobj = regExpPair.reobj()) + reobj->zeroLastIndex(); + for (size_t count = 0, i = 0, length = str->length(); i <= length; ++count) { - if (!re.execute(cx, res, str, &i, testGlobal, rval)) + if (!ExecuteRegExp(cx, res, rep, linearStr, chars, length, &i, type, rval)) return false; - if (!Matched(testGlobal, *rval)) + if (!Matched(type, *rval)) break; if (!callback(cx, res, count, data)) return false; if (!res->matched()) ++i; } } else { /* single match */ - bool testSingle = !!(flags & TEST_SINGLE_BIT), - callbackOnSingle = !!(flags & CALLBACK_ON_SINGLE_BIT); + RegExpExecType type = (flags & TEST_SINGLE_BIT) ? RegExpTest : RegExpExec; + bool callbackOnSingle = !!(flags & CALLBACK_ON_SINGLE_BIT); size_t i = 0; - if (!re.execute(cx, res, str, &i, testSingle, rval)) + if (!ExecuteRegExp(cx, res, rep, linearStr, chars, length, &i, type, rval)) return false; - if (callbackOnSingle && Matched(testSingle, *rval) && !callback(cx, res, 0, data)) + if (callbackOnSingle && Matched(type, *rval) && !callback(cx, res, 0, data)) return false; } return true; } static bool BuildFlatMatchArray(JSContext *cx, JSString *textstr, const FlatMatch &fm, Value *vp) { @@ -1539,17 +1553,17 @@ MatchCallback(JSContext *cx, RegExpStati } JSBool js::str_match(JSContext *cx, uintN argc, Value *vp) { JSString *str = ThisToStringForStringProto(cx, vp); if (!str) return false; - + RegExpGuard g(cx); if (!g.init(argc, vp, true)) return false; if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, argc)) return BuildFlatMatchArray(cx, str, *fm, vp); if (cx->isExceptionPending()) /* from tryFlatMatch */ return false; @@ -1559,17 +1573,17 @@ js::str_match(JSContext *cx, uintN argc, AutoObjectRooter array(cx); MatchArgType arg = array.addr(); RegExpStatics *res = cx->regExpStatics(); Value rval; if (!DoMatch(cx, res, str, *rep, MatchCallback, arg, MATCH_ARGS, &rval)) return false; - if (rep->re().global()) + if (rep->getPrivate()->global()) vp->setObjectOrNull(array.object()); else *vp = rval; return true; } JSBool js::str_search(JSContext *cx, uintN argc, Value *vp) @@ -1582,24 +1596,30 @@ js::str_search(JSContext *cx, uintN argc if (!g.init(argc, vp, true)) return false; if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, argc)) { vp->setInt32(fm->match()); return true; } if (cx->isExceptionPending()) /* from tryFlatMatch */ return false; - + const RegExpPair *rep = g.normalizeRegExp(false, 1, argc, vp); if (!rep) return false; + JSLinearString *linearStr = str->ensureLinear(cx); + if (!linearStr) + return false; + const jschar *chars = linearStr->chars(); + size_t length = linearStr->length(); RegExpStatics *res = cx->regExpStatics(); + /* Per ECMAv5 15.5.4.12 (5) The last index property is ignored and left unchanged. */ size_t i = 0; - if (!rep->re().execute(cx, res, str, &i, true, vp)) + if (!ExecuteRegExp(cx, res, rep->getPrivate(), linearStr, chars, length, &i, RegExpTest, vp)) return false; if (vp->isTrue()) vp->setInt32(res->matchStart()); else vp->setInt32(-1); return true; } @@ -1652,17 +1672,17 @@ InterpretDollar(JSContext *cx, RegExpSta } if (num == 0) return false; *skip = cp - dp; JS_ASSERT(num <= res->parenCount()); - /* + /* * Note: we index to get the paren with the (1-indexed) pair * number, as opposed to a (0-indexed) paren number. */ res->getParen(num, out); return true; } *skip = 2; @@ -1810,17 +1830,17 @@ FindReplaceLength(JSContext *cx, RegExpS } else { dp++; } } *sizep = replen; return true; } -/* +/* * Precondition: |rdata.sb| already has necessary growth space reserved (as * derived from FindReplaceLength). */ static void DoReplace(JSContext *cx, RegExpStatics *res, ReplaceData &rdata) { JSLinearString *repstr = rdata.repstr; const jschar *cp; @@ -2207,17 +2227,17 @@ js::str_replace(JSContext *cx, uintN arg if (fm->match() < 0) { vp->setString(rdata.str); return true; } if (rdata.lambda) return str_replace_flat_lambda(cx, argc, vp, rdata, *fm); - /* + /* * Note: we could optimize the text.length == pattern.length case if we wanted, * even in the presence of dollar metachars. */ if (rdata.dollar) return BuildDollarReplacement(cx, rdata.str, rdata.repstr, rdata.dollar, *fm, vp); return BuildFlatReplacement(cx, rdata.str, rdata.repstr, *fm, vp); } @@ -2378,30 +2398,32 @@ SplitHelper(JSContext *cx, JSLinearStrin * The SplitMatch operation from ES5 15.5.4.14 is implemented using different * matchers for regular expression and string separators. * * The algorithm differs from the spec in that the matchers return the next * index at which a match happens. */ class SplitRegExpMatcher { RegExpStatics *res; - RegExpPrivate *re; + RegExpPrivate *rep; public: static const bool returnsCaptures = true; - SplitRegExpMatcher(RegExpPrivate *re, RegExpStatics *res) : res(res), re(re) {} + SplitRegExpMatcher(RegExpPrivate *rep, RegExpStatics *res) : res(res), rep(rep) {} inline bool operator()(JSContext *cx, JSLinearString *str, size_t index, SplitMatchResult *result) { Value rval #ifdef __GNUC__ /* quell GCC overwarning */ = UndefinedValue() #endif ; - if (!re->execute(cx, res, str, &index, true, &rval)) + const jschar *chars = str->chars(); + size_t length = str->length(); + if (!ExecuteRegExp(cx, res, rep, str, chars, length, &index, RegExpTest, &rval)) return false; if (!rval.isTrue()) { result->setFailure(); return true; } JSSubString sep; res->getLastMatch(&sep); @@ -2460,17 +2482,19 @@ js::str_split(JSContext *cx, uintN argc, } /* Step 8. */ RegExpPrivate *re = NULL; JSLinearString *sepstr = NULL; bool sepUndefined = (argc == 0 || vp[2].isUndefined()); if (!sepUndefined) { if (ValueIsRegExp(vp[2])) { - re = vp[2].toObject().asRegExp()->getPrivate(); + re = vp[2].toObject().asRegExp()->getOrCreatePrivate(cx); + if (!re) + return false; } else { JSString *sep = js_ValueToString(cx, vp[2]); if (!sep) return false; vp[2].setString(sep); sepstr = sep->ensureLinear(cx); if (!sepstr) @@ -2541,17 +2565,17 @@ str_substr(JSContext *cx, uintN argc, Va begin += length; /* length + INT_MIN will always be less then 0 */ if (begin < 0) begin = 0; } if (argc == 1 || vp[3].isUndefined()) { len = length - begin; } else { - if (!ValueToIntegerRange(cx, vp[3], &len)) + if (!ValueToIntegerRange(cx, vp[3], &len)) return false; if (len <= 0) { str = cx->runtime->emptyString; goto out; } if (uint32(length) < uint32(begin + len))
--- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -6588,19 +6588,19 @@ mjit::Compiler::jsop_regexp() types::OBJECT_FLAG_REGEXP_FLAGS_SET)) { prepareStubCall(Uses(0)); masm.move(ImmPtr(obj), Registers::ArgReg1); INLINE_STUBCALL(stubs::RegExp, REJOIN_FALLTHROUGH); frame.pushSynced(JSVAL_TYPE_OBJECT); return; } - RegExpPrivate *regexp = static_cast<RegExpObject *>(obj)->getPrivate(); - - DebugOnly<uint32> origFlags = regexp->getFlags(); + RegExpObject *reobj = obj->asRegExp(); + + DebugOnly<uint32> origFlags = reobj->getFlags(); DebugOnly<uint32> staticsFlags = res->getFlags(); JS_ASSERT((origFlags & staticsFlags) == staticsFlags); /* * JS semantics require regular expression literals to create different * objects every time they execute. We only need to do this cloning if the * script could actually observe the effect of such cloning, by getting * or setting properties on it. Particular RegExp and String natives take @@ -6642,20 +6642,16 @@ mjit::Compiler::jsop_regexp() Jump emptyFreeList = masm.getNewObject(cx, result, obj); stubcc.linkExit(emptyFreeList, Uses(0)); stubcc.leave(); stubcc.masm.move(ImmPtr(obj), Registers::ArgReg1); OOL_STUBCALL(stubs::RegExp, REJOIN_FALLTHROUGH); - /* Bump the refcount on the wrapped RegExp. */ - size_t *refcount = regexp->addressOfRefCount(); - masm.add32(Imm32(1), AbsoluteAddress(refcount)); - frame.pushTypedPayload(JSVAL_TYPE_OBJECT, result); stubcc.rejoin(Changes(1)); } bool mjit::Compiler::startLoop(jsbytecode *head, Jump entry, jsbytecode *entryTarget) {
new file mode 100644 --- /dev/null +++ b/js/src/vm/MatchPairs.h @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=99 ft=cpp: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript code. + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Chris Leary <cdleary@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef MatchPairs_h__ +#define MatchPairs_h__ + +/* + * RegExp match results are succinctly represented by pairs of integer + * indices delimiting (start, limit] segments of the input string. + * + * The pair count for a given RegExp match is the capturing parentheses + * count plus one for the "0 capturing paren" whole text match. + */ + +namespace js { + +struct MatchPair +{ + int start; + int limit; + + MatchPair(int start, int limit) : start(start), limit(limit) {} + + size_t length() const { + JS_ASSERT(!isUndefined()); + return limit - start; + } + + bool isUndefined() const { + return start == -1; + } + + void check() const { + JS_ASSERT(limit >= start); + JS_ASSERT_IF(!isUndefined(), start >= 0); + } +}; + +class MatchPairs +{ + size_t pairCount_; + int buffer_[1]; + + explicit MatchPairs(size_t pairCount) : pairCount_(pairCount) { + initPairValues(); + } + + void initPairValues() { + for (int *it = buffer_; it < buffer_ + 2 * pairCount_; ++it) + *it = -1; + } + + static size_t calculateSize(size_t backingPairCount) { + return sizeof(MatchPairs) - sizeof(int) + sizeof(int) * backingPairCount; + } + + int *buffer() { return buffer_; } + + friend class RegExpPrivate; + + public: + /* + * |backingPairCount| is necessary because PCRE uses extra space + * after the actual results in the buffer. + */ + static MatchPairs *create(LifoAlloc &alloc, size_t pairCount, size_t backingPairCount); + + size_t pairCount() const { return pairCount_; } + + MatchPair pair(size_t i) { + JS_ASSERT(i < pairCount()); + return MatchPair(buffer_[2 * i], buffer_[2 * i + 1]); + } + + void displace(size_t amount) { + if (!amount) + return; + + for (int *it = buffer_; it < buffer_ + 2 * pairCount_; ++it) + *it = (*it < 0) ? -1 : *it + amount; + } + + inline void checkAgainst(size_t length); +}; + +} /* namespace js */ + +#endif
--- a/js/src/vm/RegExpObject-inl.h +++ b/js/src/vm/RegExpObject-inl.h @@ -47,59 +47,21 @@ #include "jsobjinlines.h" #include "jsstrinlines.h" #include "RegExpStatics-inl.h" inline js::RegExpObject * JSObject::asRegExp() { JS_ASSERT(isRegExp()); - js::RegExpObject *reobj = static_cast<js::RegExpObject *>(this); - JS_ASSERT(reobj->getPrivate()); - return reobj; + return static_cast<js::RegExpObject *>(this); } namespace js { -/* - * Maintains the post-initialization invariant of having a RegExpPrivate. - * - * N.B. If initialization fails, the |RegExpPrivate| will be null, so - * finalization must consider that as a possibility. - */ -class PreInitRegExpObject -{ - RegExpObject *reobj; - DebugOnly<bool> gotResult; - - public: - explicit PreInitRegExpObject(JSObject *obj) { - JS_ASSERT(obj->isRegExp()); - reobj = static_cast<RegExpObject *>(obj); - gotResult = false; - } - - ~PreInitRegExpObject() { - JS_ASSERT(gotResult); - } - - RegExpObject *get() { return reobj; } - - void succeed() { - JS_ASSERT(!gotResult); - JS_ASSERT(reobj->getPrivate()); - gotResult = true; - } - - void fail() { - JS_ASSERT(!gotResult); - gotResult = true; - } -}; - inline bool ValueIsRegExp(const Value &v) { return !v.isPrimitive() && v.toObject().isRegExp(); } inline bool IsRegExpMetaChar(jschar c) @@ -120,78 +82,80 @@ HasRegExpMetaChars(const jschar *chars, { for (size_t i = 0; i < length; ++i) { if (IsRegExpMetaChar(chars[i])) return true; } return false; } -inline bool -ResetRegExpObject(JSContext *cx, RegExpObject *reobj, JSString *str, RegExpFlag flags) -{ - AlreadyIncRefed<RegExpPrivate> rep = RegExpPrivate::create(cx, str, flags, NULL); - if (!rep) - return false; - - return reobj->reset(cx, rep); -} - -inline bool -ResetRegExpObject(JSContext *cx, RegExpObject *reobj, AlreadyIncRefed<RegExpPrivate> rep) -{ - return reobj->reset(cx, rep); -} - inline RegExpObject * RegExpObject::create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length, - RegExpFlag flags, TokenStream *ts) + RegExpFlag flags, TokenStream *tokenStream) { RegExpFlag staticsFlags = res->getFlags(); - return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), ts); + return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream); } inline RegExpObject * RegExpObject::createNoStatics(JSContext *cx, const jschar *chars, size_t length, - RegExpFlag flags, TokenStream *ts) + RegExpFlag flags, TokenStream *tokenStream) { - JSString *str = js_NewStringCopyN(cx, chars, length); - if (!str) + JSLinearString *source = js_NewStringCopyN(cx, chars, length); + if (!source) return NULL; - /* - * |NewBuiltinClassInstance| can GC before we store |re| in the - * private field of the object. At that point the only reference to - * the source string could be from the malloc-allocated GC-invisible - * |re|. So we must anchor. - */ - JS::Anchor<JSString *> anchor(str); - AlreadyIncRefed<RegExpPrivate> rep = RegExpPrivate::create(cx, str, flags, ts); - if (!rep) + /* |NewBuiltinClassInstance| can GC. */ + JS::Anchor<JSString *> anchor(source); + + if (!RegExpPrivateCode::checkSyntax(cx, tokenStream, source)) return NULL; JSObject *obj = NewBuiltinClassInstance(cx, &RegExpClass); if (!obj) return NULL; - PreInitRegExpObject pireo(obj); - RegExpObject *reobj = pireo.get(); - if (!ResetRegExpObject(cx, reobj, rep)) { + RegExpObject *reobj = obj->asRegExp(); + return reobj->reset(cx, source, flags) ? reobj : NULL; +} + +inline void +RegExpObject::finalize(JSContext *cx) +{ + if (RegExpPrivate *rep = getPrivate()) rep->decref(cx); - pireo.fail(); - return NULL; - } - - pireo.succeed(); - return reobj; +#ifdef DEBUG + setPrivate((void *) 0x1); /* Non-null but still in the zero page. */ +#endif } inline bool RegExpObject::reset(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep) { + if (!reset(cx, rep->getSource(), rep->getFlags())) + return false; + + setPrivate(rep.get()); + return true; +} + +inline bool +RegExpObject::reset(JSContext *cx, RegExpObject *other) +{ + if (RegExpPrivate *rep = other->getPrivate()) { + rep->incref(cx); + return reset(cx, AlreadyIncRefed<RegExpPrivate>(rep)); + } + + return reset(cx, other->getSource(), other->getFlags()); +} + +inline bool +RegExpObject::reset(JSContext *cx, JSLinearString *source, RegExpFlag flags) +{ if (nativeEmpty()) { const js::Shape **shapep = &cx->compartment->initialRegExpShape; if (!*shapep) { *shapep = assignInitialShape(cx); if (!*shapep) return false; } setLastProperty(*shapep); @@ -203,114 +167,30 @@ RegExpObject::reset(JSContext *cx, Alrea JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->sourceAtom))->slot == SOURCE_SLOT); JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->globalAtom))->slot == GLOBAL_FLAG_SLOT); JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->ignoreCaseAtom))->slot == IGNORE_CASE_FLAG_SLOT); JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->multilineAtom))->slot == MULTILINE_FLAG_SLOT); JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->stickyAtom))->slot == STICKY_FLAG_SLOT); - setPrivate(rep.get()); zeroLastIndex(); - setSource(rep->getSource()); - setGlobal(rep->global()); - setIgnoreCase(rep->ignoreCase()); - setMultiline(rep->multiline()); - setSticky(rep->sticky()); + setPrivate(NULL); + setSource(source); + setGlobal(flags & GlobalFlag); + setIgnoreCase(flags & IgnoreCaseFlag); + setMultiline(flags & MultilineFlag); + setSticky(flags & StickyFlag); return true; } /* RegExpPrivate inlines. */ -class RegExpMatchBuilder -{ - JSContext * const cx; - JSObject * const array; - - bool setProperty(JSAtom *name, Value v) { - return !!js_DefineProperty(cx, array, ATOM_TO_JSID(name), &v, - JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE); - } - - public: - RegExpMatchBuilder(JSContext *cx, JSObject *array) : cx(cx), array(array) {} - - bool append(uint32 index, Value v) { - JS_ASSERT(!array->getOps()->getElement); - return !!js_DefineElement(cx, array, index, &v, JS_PropertyStub, JS_StrictPropertyStub, - JSPROP_ENUMERATE); - } - - bool setIndex(int index) { - return setProperty(cx->runtime->atomState.indexAtom, Int32Value(index)); - } - - bool setInput(JSString *str) { - JS_ASSERT(str); - return setProperty(cx->runtime->atomState.inputAtom, StringValue(str)); - } -}; - -inline void -RegExpPrivate::checkMatchPairs(JSString *input, int *buf, size_t matchItemCount) -{ -#if DEBUG - size_t inputLength = input->length(); - for (size_t i = 0; i < matchItemCount; i += 2) { - int start = buf[i]; - int limit = buf[i + 1]; - JS_ASSERT(limit >= start); /* Limit index must be larger than the start index. */ - if (start == -1) - continue; - JS_ASSERT(start >= 0); - JS_ASSERT(size_t(limit) <= inputLength); - } -#endif -} - -inline JSObject * -RegExpPrivate::createResult(JSContext *cx, JSString *input, int *buf, size_t matchItemCount) -{ - /* - * Create the result array for a match. Array contents: - * 0: matched string - * 1..pairCount-1: paren matches - */ - JSObject *array = NewSlowEmptyArray(cx); - if (!array) - return NULL; - - RegExpMatchBuilder builder(cx, array); - for (size_t i = 0; i < matchItemCount; i += 2) { - int start = buf[i]; - int end = buf[i + 1]; - - JSString *captured; - if (start >= 0) { - JS_ASSERT(start <= end); - JS_ASSERT(unsigned(end) <= input->length()); - captured = js_NewDependentString(cx, input, start, end - start); - if (!captured || !builder.append(i / 2, StringValue(captured))) - return NULL; - } else { - /* Missing parenthesized match. */ - JS_ASSERT(i != 0); /* Since we had a match, first pair must be present. */ - if (!builder.append(i / 2, UndefinedValue())) - return NULL; - } - } - - if (!builder.setIndex(buf[0]) || !builder.setInput(input)) - return NULL; - - return array; -} - inline AlreadyIncRefed<RegExpPrivate> -RegExpPrivate::create(JSContext *cx, JSString *source, RegExpFlag flags, TokenStream *ts) +RegExpPrivate::create(JSContext *cx, JSLinearString *source, RegExpFlag flags, TokenStream *ts) { typedef AlreadyIncRefed<RegExpPrivate> RetType; JSLinearString *flatSource = source->ensureLinear(cx); if (!flatSource) return RetType(NULL); RegExpPrivate *self = cx->new_<RegExpPrivate>(flatSource, flags, cx->compartment); @@ -411,43 +291,43 @@ RegExpPrivate::compile(JSContext *cx, To sb.infallibleAppend(postfix, JS_ARRAY_LENGTH(postfix)); JSLinearString *fakeySource = sb.finishString(); if (!fakeySource) return false; return code.compile(cx, *fakeySource, ts, &parenCount, getFlags()); } -inline RegExpPrivateCode::ExecuteResult -RegExpPrivateCode::execute(JSContext *cx, const jschar *chars, size_t start, size_t length, +inline RegExpRunStatus +RegExpPrivateCode::execute(JSContext *cx, const jschar *chars, size_t length, size_t start, int *output, size_t outputCount) { int result; #if ENABLE_YARR_JIT (void) cx; /* Unused. */ if (codeBlock.isFallBack()) result = JSC::Yarr::interpret(byteCode, chars, start, length, output); else result = JSC::Yarr::execute(codeBlock, chars, start, length, output); #else result = jsRegExpExecute(cx, compiled, chars, length, start, output, outputCount); #endif if (result == -1) - return Success_NotFound; + return RegExpRunStatus_Success_NotFound; #if !ENABLE_YARR_JIT if (result < 0) { reportPCREError(cx, result); - return Error; + return RegExpRunStatus_Error; } #endif JS_ASSERT(result >= 0); - return Success; + return RegExpRunStatus_Success; } inline void RegExpPrivate::incref(JSContext *cx) { #ifdef DEBUG assertSameCompartment(cx, compartment); #endif @@ -460,16 +340,25 @@ RegExpPrivate::decref(JSContext *cx) #ifdef DEBUG assertSameCompartment(cx, compartment); #endif if (--refCount == 0) cx->delete_(this); } inline RegExpPrivate * +RegExpObject::getOrCreatePrivate(JSContext *cx) +{ + if (RegExpPrivate *rep = getPrivate()) + return rep; + + return makePrivate(cx) ? getPrivate() : NULL; +} + +inline RegExpPrivate * RegExpObject::getPrivate() const { RegExpPrivate *rep = static_cast<RegExpPrivate *>(JSObject::getPrivate()); #ifdef DEBUG if (rep) CompartmentChecker::check(compartment(), rep->compartment); #endif return rep;
--- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -36,16 +36,17 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "jsscan.h" #include "vm/RegExpStatics.h" +#include "vm/MatchPairs.h" #include "jsobjinlines.h" #include "jsstrinlines.h" #include "vm/RegExpObject-inl.h" #ifdef JS_TRACER #include "jstracer.h" @@ -54,100 +55,109 @@ using namespace nanojit; using namespace js; JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD); JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB); JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE); JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY); -bool -RegExpPrivate::executeInternal(JSContext *cx, RegExpStatics *res, JSString *inputstr, - size_t *lastIndex, bool test, Value *rval) +MatchPairs * +MatchPairs::create(LifoAlloc &alloc, size_t pairCount, size_t backingPairCount) +{ + void *mem = alloc.alloc(calculateSize(backingPairCount)); + if (!mem) + return NULL; + + return new (mem) MatchPairs(pairCount); +} + +inline void +MatchPairs::checkAgainst(size_t inputLength) { - const size_t pairCount = parenCount + 1; - const size_t matchItemCount = pairCount * 2; - const size_t bufCount = RegExpPrivateCode::getOutputSize(pairCount); +#if DEBUG + for (size_t i = 0; i < pairCount(); ++i) { + MatchPair p = pair(i); + p.check(); + if (p.isUndefined()) + continue; + JS_ASSERT(size_t(p.limit) <= inputLength); + } +#endif +} - LifoAllocScope las(&cx->tempLifoAlloc()); - int *buf = cx->tempLifoAlloc().newArray<int>(bufCount); - if (!buf) - return false; +RegExpRunStatus +RegExpPrivate::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex, + LifoAllocScope &allocScope, MatchPairs **output) +{ + const size_t origLength = length; + size_t backingPairCount = RegExpPrivateCode::getOutputSize(pairCount()); + + MatchPairs *matchPairs = MatchPairs::create(allocScope.alloc(), pairCount(), backingPairCount); + if (!matchPairs) + return RegExpRunStatus_Error; /* - * The JIT regexp procedure doesn't always initialize matchPair values. - * Maybe we can make this faster by ensuring it does? + * |displacement| emulates sticky mode by matching from this offset + * into the char buffer and subtracting the delta off at the end. */ - for (int *it = buf; it != buf + matchItemCount; ++it) - *it = -1; - - JSLinearString *input = inputstr->ensureLinear(cx); - if (!input) - return false; - - JS::Anchor<JSString *> anchor(input); - size_t len = input->length(); - const jschar *chars = input->chars(); - - /* - * inputOffset emulates sticky mode by matching from this offset into the char buf and - * subtracting the delta off at the end. - */ - size_t inputOffset = 0; + size_t start = *lastIndex; + size_t displacement = 0; if (sticky()) { - /* Sticky matches at the last index for the regexp object. */ - chars += *lastIndex; - len -= *lastIndex; - inputOffset = *lastIndex; + displacement = *lastIndex; + chars += displacement; + length -= displacement; + start = 0; } - size_t start = *lastIndex - inputOffset; - RegExpPrivateCode::ExecuteResult result = code.execute(cx, chars, start, len, buf, bufCount); + RegExpRunStatus status = code.execute(cx, chars, length, start, + matchPairs->buffer(), backingPairCount); - switch (result) { - case RegExpPrivateCode::Error: - return false; - case RegExpPrivateCode::Success_NotFound: - *rval = NullValue(); - return true; + switch (status) { + case RegExpRunStatus_Error: + return status; + case RegExpRunStatus_Success_NotFound: + *output = matchPairs; + return status; default: - JS_ASSERT(result == RegExpPrivateCode::Success); + JS_ASSERT(status == RegExpRunStatus_Success); } - /* - * Adjust buf for the inputOffset. Use of sticky is rare and the matchItemCount is small, so - * just do another pass. - */ - if (JS_UNLIKELY(inputOffset)) { - for (size_t i = 0; i < matchItemCount; ++i) - buf[i] = buf[i] < 0 ? -1 : buf[i] + inputOffset; - } + matchPairs->displace(displacement); + matchPairs->checkAgainst(origLength); - /* Make sure the populated contents of |buf| are sane values against |input|. */ - checkMatchPairs(input, buf, matchItemCount); + *lastIndex = matchPairs->pair(0).limit; + *output = matchPairs; - if (res) - res->updateFromMatch(cx, input, buf, matchItemCount); - - *lastIndex = buf[1]; + return RegExpRunStatus_Success; +} - if (test) { - *rval = BooleanValue(true); - return true; - } - - JSObject *array = createResult(cx, input, buf, matchItemCount); - if (!array) +bool +RegExpObject::makePrivate(JSContext *cx) +{ + JS_ASSERT(!getPrivate()); + AlreadyIncRefed<RegExpPrivate> rep = RegExpPrivate::create(cx, getSource(), getFlags(), NULL); + if (!rep) return false; - *rval = ObjectValue(*array); + setPrivate(rep.get()); return true; } +RegExpRunStatus +RegExpObject::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex, + LifoAllocScope &allocScope, MatchPairs **output) +{ + if (!getPrivate() && !makePrivate(cx)) + return RegExpRunStatus_Error; + + return getPrivate()->execute(cx, chars, length, lastIndex, allocScope, output); +} + const Shape * RegExpObject::assignInitialShape(JSContext *cx) { JS_ASSERT(!cx->compartment->initialRegExpShape); JS_ASSERT(isRegExp()); JS_ASSERT(nativeEmpty()); JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0); @@ -221,18 +231,17 @@ js_XDRRegExpObject(JSXDRState *xdr, JSOb #define js_XDRRegExpObject NULL #endif /* !JS_HAS_XDR */ static void regexp_finalize(JSContext *cx, JSObject *obj) { - if (RegExpPrivate *rep = static_cast<RegExpObject *>(obj)->getPrivate()) - rep->decref(cx); + obj->asRegExp()->finalize(cx); } Class js::RegExpClass = { js_RegExp_str, JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp), JS_PropertyStub, /* addProperty */ @@ -289,19 +298,19 @@ RegExpPrivateCode::reportYarrError(JSCon void RegExpPrivateCode::reportPCREError(JSContext *cx, int error) { #define REPORT(msg_) \ JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_ERROR, js_GetErrorMessage, NULL, msg_); \ return switch (error) { case -2: REPORT(JSMSG_REGEXP_TOO_COMPLEX); - case 0: JS_NOT_REACHED("Precondition violation: an error must have occurred."); + case 0: JS_NOT_REACHED("Precondition violation: an error must have occurred."); case 1: REPORT(JSMSG_TRAILING_SLASH); - case 2: REPORT(JSMSG_TRAILING_SLASH); + case 2: REPORT(JSMSG_TRAILING_SLASH); case 3: REPORT(JSMSG_REGEXP_TOO_COMPLEX); case 4: REPORT(JSMSG_BAD_QUANTIFIER); case 5: REPORT(JSMSG_BAD_QUANTIFIER); case 6: REPORT(JSMSG_BAD_CLASS_RANGE); case 7: REPORT(JSMSG_REGEXP_TOO_COMPLEX); case 8: REPORT(JSMSG_BAD_CLASS_RANGE); case 9: REPORT(JSMSG_BAD_QUANTIFIER); case 10: REPORT(JSMSG_UNMATCHED_RIGHT_PAREN); @@ -312,16 +321,17 @@ RegExpPrivateCode::reportPCREError(JSCon case 15: REPORT(JSMSG_BAD_BACKREF); case 16: REPORT(JSMSG_REGEXP_TOO_COMPLEX); case 17: REPORT(JSMSG_REGEXP_TOO_COMPLEX); default: JS_NOT_REACHED("Precondition violation: unknown PCRE error code."); } #undef REPORT } + #endif /* ENABLE_YARR_JIT */ bool js::ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut) { size_t n = flagStr->length(); const jschar *s = flagStr->getChars(cx); if (!s) @@ -352,74 +362,68 @@ js::ParseRegExpFlags(JSContext *cx, JSSt } } #undef HANDLE_FLAG } return true; } AlreadyIncRefed<RegExpPrivate> -RegExpPrivate::createFlagged(JSContext *cx, JSString *str, JSString *opt, TokenStream *ts) +RegExpPrivate::create(JSContext *cx, JSLinearString *str, JSString *opt, TokenStream *ts) { if (!opt) return create(cx, str, RegExpFlag(0), ts); RegExpFlag flags = RegExpFlag(0); if (!ParseRegExpFlags(cx, opt, &flags)) return AlreadyIncRefed<RegExpPrivate>(NULL); return create(cx, str, flags, ts); } RegExpObject * -RegExpObject::clone(JSContext *cx, RegExpObject *obj, RegExpObject *proto) +RegExpObject::clone(JSContext *cx, RegExpObject *reobj, RegExpObject *proto) { JSObject *clone = NewNativeClassInstance(cx, &RegExpClass, proto, proto->getParent()); if (!clone) return NULL; /* * This clone functionality does not duplicate the JIT'd code blob, * which is necessary for cross-compartment cloning functionality. */ - assertSameCompartment(cx, obj, clone); + assertSameCompartment(cx, reobj, clone); RegExpStatics *res = cx->regExpStatics(); + RegExpObject *reclone = clone->asRegExp(); - /* + /* * Check that the RegExpPrivate for the original is okay to use in - * the clone -- if the RegExpStatics provides more flags we'll need - * a different RegExpPrivate. + * the clone -- if the |RegExpStatics| provides more flags we'll + * need a different |RegExpPrivate|. */ - AlreadyIncRefed<RegExpPrivate> rep(NULL); - { - RegExpFlag origFlags = obj->getFlags(); - RegExpFlag staticsFlags = res->getFlags(); - if ((origFlags & staticsFlags) != staticsFlags) { - RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags); - rep = RegExpPrivate::create(cx, obj->getSource(), newFlags, NULL); - if (!rep) - return NULL; - } else { - RegExpPrivate *toShare = obj->getPrivate(); - toShare->incref(cx); - rep = AlreadyIncRefed<RegExpPrivate>(toShare); - } + RegExpFlag origFlags = reobj->getFlags(); + RegExpFlag staticsFlags = res->getFlags(); + if ((origFlags & staticsFlags) != staticsFlags) { + RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags); + return reclone->reset(cx, reobj->getSource(), newFlags) ? reclone : NULL; } - JS_ASSERT(rep); - - PreInitRegExpObject pireo(clone); - RegExpObject *reclone = pireo.get(); - if (!ResetRegExpObject(cx, reclone, rep)) { - pireo.fail(); - return NULL; + RegExpPrivate *toShare = reobj->getPrivate(); + if (toShare) { + toShare->incref(cx); + if (!reclone->reset(cx, AlreadyIncRefed<RegExpPrivate>(toShare))) { + toShare->decref(cx); + return NULL; + } + } else { + if (!reclone->reset(cx, reobj)) + return NULL; } - pireo.succeed(); return reclone; } JSObject * JS_FASTCALL js_CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *proto) { JS_ASSERT(obj->isRegExp()); JS_ASSERT(proto->isRegExp()); @@ -430,35 +434,31 @@ js_CloneRegExpObject(JSContext *cx, JSOb #ifdef JS_TRACER JS_DEFINE_CALLINFO_3(extern, OBJECT, js_CloneRegExpObject, CONTEXT, OBJECT, OBJECT, 0, ACCSET_STORE_ANY) #endif JSFlatString * RegExpObject::toString(JSContext *cx) const { - RegExpPrivate *rep = getPrivate(); - if (!rep) - return cx->runtime->emptyString; - - JSLinearString *src = rep->getSource(); + JSLinearString *src = getSource(); StringBuffer sb(cx); if (size_t len = src->length()) { if (!sb.reserve(len + 2)) return NULL; sb.infallibleAppend('/'); sb.infallibleAppend(src->chars(), len); sb.infallibleAppend('/'); } else { if (!sb.append("/(?:)/")) return NULL; } - if (rep->global() && !sb.append('g')) + if (global() && !sb.append('g')) return NULL; - if (rep->ignoreCase() && !sb.append('i')) + if (ignoreCase() && !sb.append('i')) return NULL; - if (rep->multiline() && !sb.append('m')) + if (multiline() && !sb.append('m')) return NULL; - if (rep->sticky() && !sb.append('y')) + if (sticky() && !sb.append('y')) return NULL; return sb.finishString(); }
--- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -44,23 +44,29 @@ #include <stddef.h> #include "jsobj.h" #include "js/TemplateLib.h" #include "yarr/Yarr.h" #if ENABLE_YARR_JIT #include "yarr/YarrJIT.h" +#include "yarr/YarrSyntaxChecker.h" #else #include "yarr/pcre/pcre.h" #endif namespace js { -class RegExpPrivate; +enum RegExpRunStatus +{ + RegExpRunStatus_Error, + RegExpRunStatus_Success, + RegExpRunStatus_Success_NotFound +}; class RegExpObject : public ::JSObject { static const uintN LAST_INDEX_SLOT = 0; static const uintN SOURCE_SLOT = 1; static const uintN GLOBAL_FLAG_SLOT = 2; static const uintN IGNORE_CASE_FLAG_SLOT = 3; static const uintN MULTILINE_FLAG_SLOT = 4; @@ -82,79 +88,107 @@ class RegExpObject : public ::JSObject createNoStatics(JSContext *cx, const jschar *chars, size_t length, RegExpFlag flags, TokenStream *ts); static RegExpObject *clone(JSContext *cx, RegExpObject *obj, RegExpObject *proto); /* Note: fallible. */ JSFlatString *toString(JSContext *cx) const; + /* + * Run the regular expression over the input text. + * + * Results are placed in |output| as integer pairs. For eaxmple, + * |output[0]| and |output[1]| represent the text indices that make + * up the "0" (whole match) pair. Capturing parens will result in + * more output. + * + * N.B. it's the responsibility of the caller to hook the |output| + * into the |RegExpStatics| appropriately, if necessary. + */ + RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex, + LifoAllocScope &allocScope, MatchPairs **output); + /* Accessors. */ const Value &getLastIndex() const { return getSlot(LAST_INDEX_SLOT); } void setLastIndex(const Value &v) { setSlot(LAST_INDEX_SLOT, v); } void setLastIndex(double d) { setSlot(LAST_INDEX_SLOT, NumberValue(d)); } void zeroLastIndex() { setSlot(LAST_INDEX_SLOT, Int32Value(0)); } - JSString *getSource() const { - return getSlot(SOURCE_SLOT).toString(); + JSLinearString *getSource() const { + return &getSlot(SOURCE_SLOT).toString()->asLinear(); } - void setSource(JSString *source) { + void setSource(JSLinearString *source) { setSlot(SOURCE_SLOT, StringValue(source)); } RegExpFlag getFlags() const { uintN flags = 0; - flags |= getSlot(GLOBAL_FLAG_SLOT).toBoolean() ? GlobalFlag : 0; - flags |= getSlot(IGNORE_CASE_FLAG_SLOT).toBoolean() ? IgnoreCaseFlag : 0; - flags |= getSlot(MULTILINE_FLAG_SLOT).toBoolean() ? MultilineFlag : 0; - flags |= getSlot(STICKY_FLAG_SLOT).toBoolean() ? StickyFlag : 0; + flags |= global() ? GlobalFlag : 0; + flags |= ignoreCase() ? IgnoreCaseFlag : 0; + flags |= multiline() ? MultilineFlag : 0; + flags |= sticky() ? StickyFlag : 0; return RegExpFlag(flags); } - inline RegExpPrivate *getPrivate() const; /* Flags. */ void setIgnoreCase(bool enabled) { setSlot(IGNORE_CASE_FLAG_SLOT, BooleanValue(enabled)); } void setGlobal(bool enabled) { setSlot(GLOBAL_FLAG_SLOT, BooleanValue(enabled)); } void setMultiline(bool enabled) { setSlot(MULTILINE_FLAG_SLOT, BooleanValue(enabled)); } void setSticky(bool enabled) { setSlot(STICKY_FLAG_SLOT, BooleanValue(enabled)); } + bool ignoreCase() const { return getSlot(IGNORE_CASE_FLAG_SLOT).toBoolean(); } + bool global() const { return getSlot(GLOBAL_FLAG_SLOT).toBoolean(); } + bool multiline() const { return getSlot(MULTILINE_FLAG_SLOT).toBoolean(); } + bool sticky() const { return getSlot(STICKY_FLAG_SLOT).toBoolean(); } + + /* + * N.B. |RegExpObject|s can be mutated in place because of |RegExp.prototype.compile|, hence + * |reset| for re-initialization. + */ + + inline bool reset(JSContext *cx, RegExpObject *other); + inline bool reset(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep); + inline bool reset(JSContext *cx, JSLinearString *source, RegExpFlag flags); + + inline RegExpPrivate *getOrCreatePrivate(JSContext *cx); + inline void finalize(JSContext *cx); private: - /* N.B. |RegExpObject|s can be mutated in place because of |RegExp.prototype.compile|. */ - inline bool reset(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep); + /* The |RegExpPrivate| is lazily created at the time of use. */ + inline RegExpPrivate *getPrivate() const; - friend bool ResetRegExpObject(JSContext *, RegExpObject *, JSString *, RegExpFlag); + /* + * Precondition: the syntax for |source| has already been validated. + * Side effect: sets the private field. + */ + bool makePrivate(JSContext *cx); + + friend bool ResetRegExpObject(JSContext *, RegExpObject *, JSLinearString *, RegExpFlag); friend bool ResetRegExpObject(JSContext *, RegExpObject *, AlreadyIncRefed<RegExpPrivate>); /* * Compute the initial shape to associate with fresh RegExp objects, * encoding their initial properties. Return the shape after * changing this regular expression object's last property to it. */ const Shape *assignInitialShape(JSContext *cx); RegExpObject(); RegExpObject &operator=(const RegExpObject &reo); }; /* class RegExpObject */ -inline bool -ResetRegExpObject(JSContext *cx, RegExpObject *reobj, JSString *str, RegExpFlag flags); - -/* N.B. On failure, caller must decref |rep|. */ -inline bool -ResetRegExpObject(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep); - /* Abstracts away the gross |RegExpPrivate| backend details. */ class RegExpPrivateCode { #if ENABLE_YARR_JIT typedef JSC::Yarr::BytecodePattern BytecodePattern; typedef JSC::Yarr::ErrorCode ErrorCode; typedef JSC::Yarr::JSGlobalData JSGlobalData; typedef JSC::Yarr::YarrCodeBlock YarrCodeBlock; @@ -184,30 +218,41 @@ class RegExpPrivateCode if (byteCode) Foreground::delete_<BytecodePattern>(byteCode); #else if (compiled) jsRegExpFree(compiled); #endif } + static bool checkSyntax(JSContext *cx, TokenStream *tokenStream, JSLinearString *source) { +#if ENABLE_YARR_JIT + ErrorCode error = JSC::Yarr::checkSyntax(*source); + if (error == JSC::Yarr::NoError) + return true; + + reportYarrError(cx, tokenStream, error); + return false; +#else +# error "Syntax checking not implemented for !ENABLE_YARR_JIT" +#endif + } + #if ENABLE_YARR_JIT static inline bool isJITRuntimeEnabled(JSContext *cx); - void reportYarrError(JSContext *cx, TokenStream *ts, JSC::Yarr::ErrorCode error); + static void reportYarrError(JSContext *cx, TokenStream *ts, JSC::Yarr::ErrorCode error); #else - void reportPCREError(JSContext *cx, int error); + static void reportPCREError(JSContext *cx, int error); #endif inline bool compile(JSContext *cx, JSLinearString &pattern, TokenStream *ts, uintN *parenCount, RegExpFlag flags); - enum ExecuteResult { Error, Success, Success_NotFound }; - - inline ExecuteResult execute(JSContext *cx, const jschar *chars, size_t start, size_t length, - int *output, size_t outputCount); + inline RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t start, + int *output, size_t outputCount); static size_t getOutputSize(size_t pairCount) { #if ENABLE_YARR_JIT return pairCount * 2; #else return pairCount * 3; /* Should be x2, but PCRE has... needs. */ #endif } @@ -242,66 +287,49 @@ class RegExpPrivate RegExpPrivate(JSLinearString *source, RegExpFlag flags, JSCompartment *compartment) : source(source), refCount(1), parenCount(0), flags(flags), compartment(compartment) { } JS_DECLARE_ALLOCATION_FRIENDS_FOR_PRIVATE_CONSTRUCTOR; bool compile(JSContext *cx, TokenStream *ts); static inline void checkMatchPairs(JSString *input, int *buf, size_t matchItemCount); - static JSObject *createResult(JSContext *cx, JSString *input, int *buf, size_t matchItemCount); - bool executeInternal(JSContext *cx, RegExpStatics *res, JSString *input, - size_t *lastIndex, bool test, Value *rval); public: - /* - * Execute regexp on |input| at |*lastIndex|. - * - * On match: Update |*lastIndex| and RegExp class statics. - * Return true if test is true. Place an array in |*rval| if test is false. - * On mismatch: Make |*rval| null. - */ - bool execute(JSContext *cx, RegExpStatics *res, JSString *input, size_t *lastIndex, bool test, - Value *rval) { - JS_ASSERT(res); - return executeInternal(cx, res, input, lastIndex, test, rval); - } + static AlreadyIncRefed<RegExpPrivate> + create(JSContext *cx, JSLinearString *source, RegExpFlag flags, TokenStream *ts); - bool executeNoStatics(JSContext *cx, JSString *input, size_t *lastIndex, bool test, - Value *rval) { - return executeInternal(cx, NULL, input, lastIndex, test, rval); - } - - /* Factories */ + static AlreadyIncRefed<RegExpPrivate> + create(JSContext *cx, JSLinearString *source, JSString *flags, TokenStream *ts); - static AlreadyIncRefed<RegExpPrivate> create(JSContext *cx, JSString *source, RegExpFlag flags, - TokenStream *ts); - - /* Would overload |create|, but |0| resolves ambiguously against pointer and uint. */ - static AlreadyIncRefed<RegExpPrivate> createFlagged(JSContext *cx, JSString *source, - JSString *flags, TokenStream *ts); + RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex, + LifoAllocScope &allocScope, MatchPairs **output); /* Mutators */ void incref(JSContext *cx); void decref(JSContext *cx); /* For JIT access. */ size_t *addressOfRefCount() { return &refCount; } /* Accessors */ - JSLinearString *getSource() const { return source; } - size_t getParenCount() const { return parenCount; } - RegExpFlag getFlags() const { return flags; } + JSLinearString *getSource() const { return source; } + size_t getParenCount() const { return parenCount; } + + /* Accounts for the "0" (whole match) pair. */ + size_t pairCount() const { return parenCount + 1; } + + RegExpFlag getFlags() const { return flags; } bool ignoreCase() const { return flags & IgnoreCaseFlag; } bool global() const { return flags & GlobalFlag; } bool multiline() const { return flags & MultilineFlag; } bool sticky() const { return flags & StickyFlag; } -}; /* class RegExpPrivate */ +}; /* * Parse regexp flags. Report an error and return false if an invalid * sequence of flags is encountered (repeat/invalid flag). * * N.B. flagStr must be rooted. */ bool
--- a/js/src/vm/RegExpStatics.h +++ b/js/src/vm/RegExpStatics.h @@ -40,22 +40,24 @@ #ifndef RegExpStatics_h__ #define RegExpStatics_h__ #include "jscntxt.h" #include "js/Vector.h" +#include "vm/MatchPairs.h" + namespace js { class RegExpStatics { - typedef Vector<int, 20, SystemAllocPolicy> MatchPairs; - MatchPairs matchPairs; + typedef Vector<int, 20, SystemAllocPolicy> Pairs; + Pairs matchPairs; /* The input that was used to produce matchPairs. */ JSLinearString *matchPairsInput; /* The input last set on the statics. */ JSString *pendingInput; RegExpFlag flags; RegExpStatics *bufferLink; bool copied; @@ -157,27 +159,30 @@ class RegExpStatics public: RegExpStatics() : bufferLink(NULL), copied(false) { clear(); } static JSObject *create(JSContext *cx, GlobalObject *parent); /* Mutators. */ - bool updateFromMatch(JSContext *cx, JSLinearString *input, int *buf, size_t matchItemCount) { + bool updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs *newPairs) { + JS_ASSERT(input); aboutToWrite(); pendingInput = input; - if (!matchPairs.resizeUninitialized(matchItemCount)) { + if (!matchPairs.resizeUninitialized(2 * newPairs->pairCount())) { js_ReportOutOfMemory(cx); return false; } - for (size_t i = 0; i < matchItemCount; ++i) - matchPairs[i] = buf[i]; + for (size_t i = 0; i < newPairs->pairCount(); ++i) { + matchPairs[2 * i] = newPairs->pair(i).start; + matchPairs[2 * i + 1] = newPairs->pair(i).limit; + } matchPairsInput = input; return true; } inline void setMultiline(JSContext *cx, bool enabled); void clear() {
--- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -4278,16 +4278,49 @@ nsLayoutUtils::GetFontFacesForText(nsIFr curr); } while (aFollowContinuations && (curr = static_cast<nsTextFrame*>(curr->GetNextContinuation()))); return NS_OK; } /* static */ +nsresult +nsLayoutUtils::GetTextRunMemoryForFrames(nsIFrame* aFrame, PRUint64* aTotal) +{ + NS_PRECONDITION(aFrame, "NULL frame pointer"); + + if (aFrame->GetType() == nsGkAtoms::textFrame) { + nsTextFrame* textFrame = static_cast<nsTextFrame*>(aFrame); + gfxTextRun *run = textFrame->GetTextRun(); + if (run) { + if (aTotal) { + run->AccountForSize(aTotal); + } else { + run->ClearSizeAccounted(); + } + } + return NS_OK; + } + + nsAutoTArray<nsIFrame::ChildList,4> childListArray; + aFrame->GetChildLists(&childListArray); + + for (nsIFrame::ChildListArrayIterator childLists(childListArray); + !childLists.IsDone(); childLists.Next()) { + for (nsFrameList::Enumerator e(childLists.CurrentList()); + !e.AtEnd(); e.Next()) { + GetTextRunMemoryForFrames(e.get(), aTotal); + } + } + + return NS_OK; +} + +/* static */ void nsLayoutUtils::Shutdown() { if (sContentMap) { delete sContentMap; sContentMap = NULL; } }
--- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -1429,16 +1429,30 @@ public: */ static nsresult GetFontFacesForText(nsIFrame* aFrame, PRInt32 aStartOffset, PRInt32 aEndOffset, bool aFollowContinuations, nsFontFaceList* aFontFaceList); /** + * Walks the frame tree starting at aFrame looking for textRuns. + * If aTotal is NULL, just clears the TEXT_RUN_MEMORY_ACCOUNTED flag + * on each textRun found. + * If aTotal is non-NULL, adds the storage used for each textRun to the + * total, and sets the TEXT_RUN_MEMORY_ACCOUNTED flag to avoid double- + * accounting. (Runs with this flag already set will be skipped.) + * Expected usage pattern is therefore to call twice: + * rv = GetTextRunMemoryForFrames(rootFrame, NULL); + * rv = GetTextRunMemoryForFrames(rootFrame, &total); + */ + static nsresult GetTextRunMemoryForFrames(nsIFrame* aFrame, + PRUint64* aTotal); + + /** * Checks if CSS 3D transforms are currently enabled. */ static bool Are3DTransformsEnabled(); static void Shutdown(); #ifdef DEBUG /**
--- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -215,16 +215,18 @@ #define ANCHOR_SCROLL_FLAGS (SCROLL_OVERFLOW_HIDDEN | SCROLL_NO_PARENT_FRAMES) #include "nsContentCID.h" static NS_DEFINE_IID(kRangeCID, NS_RANGE_CID); /* for NS_MEMORY_REPORTER_IMPLEMENT */ #include "nsIMemoryReporter.h" +#include "gfxTextRunWordCache.h" + using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::layers; CapturingContentInfo nsIPresShell::gCaptureInfo = { PR_FALSE /* mAllowed */, PR_FALSE /* mRetargetToElement */, PR_FALSE /* mPreventDrag */, nsnull /* mContent */ }; nsIContent* nsIPresShell::gKeyDownTarget; @@ -636,50 +638,79 @@ PresShell::MemoryReporter::SizeEnumerato str += spec; } } str += NS_LITERAL_CSTRING(")"); NS_NAMED_LITERAL_CSTRING(kArenaDesc, "Memory used by layout PresShell, PresContext, and other related areas."); NS_NAMED_LITERAL_CSTRING(kStyleDesc, "Memory used by the style system."); + NS_NAMED_LITERAL_CSTRING(kTextRunsDesc, "Memory used for text-runs (glyph layout) in the PresShell's frame tree."); nsCAutoString arenaPath = str + NS_LITERAL_CSTRING("/arenas"); nsCAutoString stylePath = str + NS_LITERAL_CSTRING("/styledata"); + nsCAutoString textRunsPath = str + NS_LITERAL_CSTRING("/textruns"); PRUint32 arenasSize; arenasSize = aShell->EstimateMemoryUsed(); arenasSize += aShell->mPresContext->EstimateMemoryUsed(); PRUint32 styleSize; styleSize = aShell->StyleSet()->SizeOf(); + PRUint64 textRunsSize; + textRunsSize = aShell->ComputeTextRunMemoryUsed(); + data->callback-> Callback(EmptyCString(), arenaPath, nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, arenasSize, kArenaDesc, data->closure); data->callback-> Callback(EmptyCString(), stylePath, nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, styleSize, kStyleDesc, data->closure); + if (textRunsSize) { + data->callback-> + Callback(EmptyCString(), textRunsPath, nsIMemoryReporter::KIND_HEAP, + nsIMemoryReporter::UNITS_BYTES, textRunsSize, kTextRunsDesc, + data->closure); + } + return PL_DHASH_NEXT; } NS_IMETHODIMP PresShell::MemoryReporter::CollectReports(nsIMemoryMultiReporterCallback* aCb, nsISupports* aClosure) { MemoryReporterData data; data.callback = aCb; data.closure = aClosure; + // clear TEXT_RUN_SIZE_ACCOUNTED flag on cached runs + gfxTextRunWordCache::ComputeStorage(nsnull); + sLiveShells->EnumerateEntries(SizeEnumerator, &data); + NS_NAMED_LITERAL_CSTRING(kTextRunWordCachePath, + "explicit/gfx/textrun-word-cache"); + NS_NAMED_LITERAL_CSTRING(kTextRunWordCacheDesc, + "Memory used by cached text-runs that are " + "not owned by a PresShell's frame tree."); + + // now total up cached runs that aren't otherwise accounted for + PRUint64 textRunWordCacheSize = 0; + gfxTextRunWordCache::ComputeStorage(&textRunWordCacheSize); + + aCb->Callback(EmptyCString(), kTextRunWordCachePath, + nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, + textRunWordCacheSize, kTextRunWordCacheDesc, aClosure); + return NS_OK; } class nsAutoCauseReflowNotifier { public: nsAutoCauseReflowNotifier(PresShell* aShell) : mShell(aShell) @@ -8705,8 +8736,27 @@ PresShell::GetRootPresShell() if (mPresContext) { nsPresContext* rootPresContext = mPresContext->GetRootPresContext(); if (rootPresContext) { return static_cast<PresShell*>(rootPresContext->PresShell()); } } return nsnull; } + +PRUint64 +PresShell::ComputeTextRunMemoryUsed() +{ + nsIFrame* rootFrame = FrameManager()->GetRootFrame(); + if (!rootFrame) { + return 0; + } + + // clear the TEXT_RUN_MEMORY_ACCOUNTED flags + nsLayoutUtils::GetTextRunMemoryForFrames(rootFrame, nsnull); + + // collect the total memory in use for textruns + PRUint64 total = 0; + nsLayoutUtils::GetTextRunMemoryForFrames(rootFrame, &total); + + return total; +} +
--- a/layout/base/nsPresShell.h +++ b/layout/base/nsPresShell.h @@ -893,16 +893,18 @@ public: result += sizeof(PresShell); result += mStackArena.Size(); result += mFrameArena.Size(); return result; } + PRUint64 ComputeTextRunMemoryUsed(); + class MemoryReporter : public nsIMemoryMultiReporter { public: NS_DECL_ISUPPORTS NS_DECL_NSIMEMORYMULTIREPORTER protected: static PLDHashOperator SizeEnumerator(PresShellPtrKey *aEntry, void *userArg); };
--- a/layout/generic/nsTextRunTransformations.cpp +++ b/layout/generic/nsTextRunTransformations.cpp @@ -94,16 +94,38 @@ nsTransformedTextRun::SetPotentialLineBr bool changed = gfxTextRun::SetPotentialLineBreaks(aStart, aLength, aBreakBefore, aRefContext); if (changed) { mNeedsRebuild = PR_TRUE; } return changed; } +PRUint64 +nsTransformedTextRun::ComputeSize() +{ + PRUint32 total = gfxTextRun::ComputeSize(); + if (moz_malloc_usable_size(this) == 0) { + total += sizeof(nsTransformedTextRun) - sizeof(gfxTextRun); + } + total += mStyles.SizeOf(); + total += mCapitalize.SizeOf(); + if (mOwnsFactory) { + PRUint32 factorySize = moz_malloc_usable_size(mFactory); + if (factorySize == 0) { + // this may not quite account for everything + // (e.g. nsCaseTransformTextRunFactory adds a couple of members) + // but I'm not sure it's worth the effort to track more precisely + factorySize = sizeof(nsTransformingTextRunFactory); + } + total += factorySize; + } + return total; +} + nsTransformedTextRun* nsTransformingTextRunFactory::MakeTextRun(const PRUnichar* aString, PRUint32 aLength, const gfxTextRunFactory::Parameters* aParams, gfxFontGroup* aFontGroup, PRUint32 aFlags, nsStyleContext** aStyles, bool aOwnsFactory) { return nsTransformedTextRun::Create(aParams, this, aFontGroup, aString, aLength, aFlags, aStyles, aOwnsFactory);
--- a/layout/generic/nsTextRunTransformations.h +++ b/layout/generic/nsTextRunTransformations.h @@ -127,16 +127,19 @@ public: void FinishSettingProperties(gfxContext* aRefContext) { if (mNeedsRebuild) { mNeedsRebuild = PR_FALSE; mFactory->RebuildTextRun(this, aRefContext); } } + // override the gfxTextRun impl to account for additional members here + virtual PRUint64 ComputeSize(); + nsTransformingTextRunFactory *mFactory; nsTArray<nsRefPtr<nsStyleContext> > mStyles; nsTArray<bool> mCapitalize; bool mOwnsFactory; bool mNeedsRebuild; private: nsTransformedTextRun(const gfxTextRunFactory::Parameters* aParams,
--- a/netwerk/mime/nsMIMEHeaderParamImpl.cpp +++ b/netwerk/mime/nsMIMEHeaderParamImpl.cpp @@ -365,44 +365,37 @@ nsMIMEHeaderParamImpl::DoParameterIntern goto increment_str; } } // look for single quotation mark(') const char *sQuote1 = PL_strchr(valueStart, 0x27); const char *sQuote2 = (char *) (sQuote1 ? PL_strchr(sQuote1 + 1, 0x27) : nsnull); // Two single quotation marks must be present even in - // absence of charset and lang. - if (!sQuote1 || !sQuote2) - NS_WARNING("Mandatory two single quotes are missing in header parameter\n"); + // absence of charset and lang. + if (!sQuote1 || !sQuote2) { + // log the warning and skip to next parameter + NS_WARNING("Mandatory two single quotes are missing in header parameter, parameter ignored\n"); + goto increment_str; + } + if (aCharset && sQuote1 > valueStart && sQuote1 < valueEnd) { *aCharset = (char *) nsMemory::Clone(valueStart, sQuote1 - valueStart + 1); if (*aCharset) *(*aCharset + (sQuote1 - valueStart)) = 0; } - if (aLang && sQuote1 && sQuote2 && sQuote2 > sQuote1 + 1 && - sQuote2 < valueEnd) + if (aLang && sQuote2 > sQuote1 + 1 && sQuote2 < valueEnd) { *aLang = (char *) nsMemory::Clone(sQuote1 + 1, sQuote2 - (sQuote1 + 1) + 1); if (*aLang) *(*aLang + (sQuote2 - (sQuote1 + 1))) = 0; } - - // Be generous and handle gracefully when required - // single quotes are absent. - if (sQuote1) - { - if(!sQuote2) - sQuote2 = sQuote1; - } - else - sQuote2 = valueStart - 1; - - if (sQuote2 && sQuote2 + 1 < valueEnd) + + if (sQuote2 + 1 < valueEnd) { if (*aResult) { // caseA value already read, or caseC/D value already read // but we're now reading caseB: either way, drop old value nsMemory::Free(*aResult); haveCaseAValue = false; }
--- a/netwerk/test/unit/test_MIME_params.js +++ b/netwerk/test/unit/test_MIME_params.js @@ -246,16 +246,31 @@ var tests = [ // the actual bug, without 2231/5987 encoding ["attachment; filename X", "attachment", Cr.NS_ERROR_INVALID_ARG], // sanity check with WS on both sides ["attachment; filename = foo-A.html", "attachment", "foo-A.html"], + + // Bug 692574: RFC2231/5987 decoding should not tolerate missing single + // quotes + + // one missing + ["attachment; filename*=UTF-8'foo-%41.html", + "attachment", Cr.NS_ERROR_INVALID_ARG], + + // both missing + ["attachment; filename*=foo-%41.html", + "attachment", Cr.NS_ERROR_INVALID_ARG], + + // make sure fallback works + ["attachment; filename*=UTF-8'foo-%41.html; filename=bar.html", + "attachment", "bar.html"], ]; function do_tests(whichRFC) { var mhp = Components.classes["@mozilla.org/network/mime-hdrparam;1"] .getService(Components.interfaces.nsIMIMEHeaderParam); var unused = { value : null };
--- a/parser/html/nsHtml5TreeOpExecutor.cpp +++ b/parser/html/nsHtml5TreeOpExecutor.cpp @@ -347,26 +347,27 @@ nsHtml5TreeOpExecutor::UpdateStyleSheet( // Re-open update BeginDocUpdate(); } void nsHtml5TreeOpExecutor::FlushSpeculativeLoads() { - if (NS_UNLIKELY(!mParser)) { - return; - } nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue; mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue); const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements(); const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length(); for (nsHtml5SpeculativeLoad* iter = const_cast<nsHtml5SpeculativeLoad*>(start); iter < end; ++iter) { + if (NS_UNLIKELY(!mParser)) { + // An extension terminated the parser from a HTTP observer. + return; + } iter->Perform(this); } } class nsHtml5FlushLoopGuard { private: nsRefPtr<nsHtml5TreeOpExecutor> mExecutor; @@ -448,21 +449,31 @@ nsHtml5TreeOpExecutor::RunFlushLoop() // Make sure speculative loads never start after the corresponding // normal loads for the same URLs. const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements(); const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length(); for (nsHtml5SpeculativeLoad* iter = (nsHtml5SpeculativeLoad*)start; iter < end; ++iter) { iter->Perform(this); + if (NS_UNLIKELY(!mParser)) { + // An extension terminated the parser from a HTTP observer. + mOpQueue.Clear(); // clear in order to be able to assert in destructor + return; + } } } else { FlushSpeculativeLoads(); // Make sure speculative loads never start after // the corresponding normal loads for the same // URLs. + if (NS_UNLIKELY(!mParser)) { + // An extension terminated the parser from a HTTP observer. + mOpQueue.Clear(); // clear in order to be able to assert in destructor + return; + } // Not sure if this grip is still needed, but previously, the code // gripped before calling ParseUntilBlocked(); nsRefPtr<nsHtml5StreamParser> streamKungFuDeathGrip = GetParser()->GetStreamParser(); // Now parse content left in the document.write() buffer queue if any. // This may generate tree ops on its own or dequeue a speculation. GetParser()->ParseUntilBlocked(); } @@ -542,25 +553,25 @@ nsHtml5TreeOpExecutor::RunFlushLoop() } } } } void nsHtml5TreeOpExecutor::FlushDocumentWrite() { - if (!mParser) { + FlushSpeculativeLoads(); // Make sure speculative loads never start after the + // corresponding normal loads for the same URLs. + + if (NS_UNLIKELY(!mParser)) { // The parse has ended. mOpQueue.Clear(); // clear in order to be able to assert in destructor return; } - FlushSpeculativeLoads(); // Make sure speculative loads never start after the - // corresponding normal loads for the same URLs. - if (mFlushState != eNotFlushing) { // XXX Can this happen? In case it can, let's avoid crashing. return; } mFlushState = eInFlush; // avoid crashing near EOF
--- a/toolkit/components/feeds/nsScriptableUnescapeHTML.cpp +++ b/toolkit/components/feeds/nsScriptableUnescapeHTML.cpp @@ -175,22 +175,22 @@ nsScriptableUnescapeHTML::ParseFragment( tagStack, PR_TRUE, aReturn); fragment = do_QueryInterface(*aReturn); } else { NS_NewDocumentFragment(aReturn, document->NodeInfoManager()); fragment = do_QueryInterface(*aReturn); - nsContentUtils::ParseFragmentHTML(aFragment, - fragment, - nsGkAtoms::body, - kNameSpaceID_XHTML, - PR_FALSE, - PR_TRUE); + rv = nsContentUtils::ParseFragmentHTML(aFragment, + fragment, + nsGkAtoms::body, + kNameSpaceID_XHTML, + PR_FALSE, + PR_TRUE); // Now, set the base URI on all subtree roots. if (aBaseURI) { aBaseURI->GetSpec(spec); nsAutoString spec16; CopyUTF8toUTF16(spec, spec16); nsIContent* node = fragment->GetFirstChild(); while (node) { if (node->IsElement()) {
--- a/xpcom/glue/nsTArray-inl.h +++ b/xpcom/glue/nsTArray-inl.h @@ -50,43 +50,43 @@ template<class Alloc> nsTArray_base<Alloc>::~nsTArray_base() { if (mHdr != EmptyHdr() && !UsesAutoArrayBuffer()) { Alloc::Free(mHdr); } MOZ_COUNT_DTOR(nsTArray_base); } template<class Alloc> -nsTArrayHeader* nsTArray_base<Alloc>::GetAutoArrayBufferUnsafe(size_t elemAlign) { +const nsTArrayHeader* nsTArray_base<Alloc>::GetAutoArrayBufferUnsafe(size_t elemAlign) const { // Assuming |this| points to an nsAutoArray, we want to get a pointer to // mAutoBuf. So just cast |this| to nsAutoArray* and read &mAutoBuf! - void* autoBuf = &reinterpret_cast<nsAutoArrayBase<nsTArray<PRUint32>, 1>*>(this)->mAutoBuf; + const void* autoBuf = &reinterpret_cast<const nsAutoArrayBase<nsTArray<PRUint32>, 1>*>(this)->mAutoBuf; // If we're on a 32-bit system and elemAlign is 8, we need to adjust our // pointer to take into account the extra alignment in the auto array. // Check that the auto array is padded as we expect. PR_STATIC_ASSERT(sizeof(void*) != 4 || (MOZ_ALIGNOF(mozilla::AlignedElem<8>) == 8 && sizeof(nsAutoTArray<mozilla::AlignedElem<8>, 1>) == sizeof(void*) + sizeof(nsTArrayHeader) + 4 + sizeof(mozilla::AlignedElem<8>))); // We don't support alignments greater than 8 bytes. NS_ABORT_IF_FALSE(elemAlign <= 4 || elemAlign == 8, "unsupported alignment."); if (sizeof(void*) == 4 && elemAlign == 8) { - autoBuf = reinterpret_cast<char*>(autoBuf) + 4; + autoBuf = reinterpret_cast<const char*>(autoBuf) + 4; } - return reinterpret_cast<Header*>(autoBuf); + return reinterpret_cast<const Header*>(autoBuf); } template<class Alloc> -bool nsTArray_base<Alloc>::UsesAutoArrayBuffer() { +bool nsTArray_base<Alloc>::UsesAutoArrayBuffer() const { if (!mHdr->mIsAutoArray) { return PR_FALSE; } // This is nuts. If we were sane, we'd pass elemAlign as a parameter to // this function. Unfortunately this function is called in nsTArray_base's // destructor, at which point we don't know elem_type's alignment. // @@ -113,18 +113,18 @@ bool nsTArray_base<Alloc>::UsesAutoArray // Note that this means that we can't store elements with alignment 16 in an // nsTArray, because GetAutoArrayBuffer(16) could lie outside the memory // owned by this nsAutoTArray. We statically assert that elem_type's // alignment is 8 bytes or less in nsAutoArrayBase. PR_STATIC_ASSERT(sizeof(nsTArrayHeader) > 4); #ifdef DEBUG - PRPtrdiff diff = reinterpret_cast<char*>(GetAutoArrayBuffer(8)) - - reinterpret_cast<char*>(GetAutoArrayBuffer(4)); + PRPtrdiff diff = reinterpret_cast<const char*>(GetAutoArrayBuffer(8)) - + reinterpret_cast<const char*>(GetAutoArrayBuffer(4)); NS_ABORT_IF_FALSE(diff >= 0 && diff <= 4, "GetAutoArrayBuffer doesn't do what we expect."); #endif return mHdr == GetAutoArrayBuffer(4) || mHdr == GetAutoArrayBuffer(8); } template<class Alloc>
--- a/xpcom/glue/nsTArray.h +++ b/xpcom/glue/nsTArray.h @@ -251,33 +251,41 @@ protected: bool mIsAuto; }; // Helper function for SwapArrayElements. Ensures that if the array // is an nsAutoTArray that it doesn't use the built-in buffer. bool EnsureNotUsingAutoArrayBuffer(size_type elemSize); // Returns true if this nsTArray is an nsAutoTArray with a built-in buffer. - bool IsAutoArray() { + bool IsAutoArray() const { return mHdr->mIsAutoArray; } // Returns a Header for the built-in buffer of this nsAutoTArray. Header* GetAutoArrayBuffer(size_t elemAlign) { NS_ASSERTION(IsAutoArray(), "Should be an auto array to call this"); return GetAutoArrayBufferUnsafe(elemAlign); } + const Header* GetAutoArrayBuffer(size_t elemAlign) const { + NS_ASSERTION(IsAutoArray(), "Should be an auto array to call this"); + return GetAutoArrayBufferUnsafe(elemAlign); + } // Returns a Header for the built-in buffer of this nsAutoTArray, but doesn't // assert that we are an nsAutoTArray. - Header* GetAutoArrayBufferUnsafe(size_t elemAlign); + Header* GetAutoArrayBufferUnsafe(size_t elemAlign) { + return const_cast<Header*>(static_cast<const nsTArray_base<Alloc>*>(this)-> + GetAutoArrayBufferUnsafe(elemAlign)); + } + const Header* GetAutoArrayBufferUnsafe(size_t elemAlign) const; // Returns true if this is an nsAutoTArray and it currently uses the // built-in buffer to store its elements. - bool UsesAutoArrayBuffer(); + bool UsesAutoArrayBuffer() const; // The array's elements (prefixed with a Header). This pointer is never // null. If the array is empty, then this will point to sEmptyHdr. Header *mHdr; Header* Hdr() const { return mHdr; } @@ -470,17 +478,18 @@ public: nsTArray& operator=(const nsTArray<E, Allocator>& other) { ReplaceElementsAt(0, Length(), other.Elements(), other.Length()); return *this; } // @return The amount of memory taken used by this nsTArray, not including // sizeof(this) size_t SizeOf() const { - return this->Capacity() * sizeof(elem_type) + sizeof(*this->Hdr()); + return this->UsesAutoArrayBuffer() ? + 0 : this->Capacity() * sizeof(elem_type) + sizeof(*this->Hdr()); } // // Accessor methods // // This method provides direct access to the array elements. // @return A pointer to the first element of the array. If the array is